export interface PublicKeyCredentialDescriptorJSON { type: "public-key"; id: string; transports?: AuthenticatorTransport[]; } export interface PublicKeyCredentialUserEntityJSON { id: string; name: string; displayName: string; } export interface PublicKeyCredentialRpEntityJSON { name: string; id?: string; } export interface PublicKeyCredentialParametersJSON { type: "public-key"; alg: number; } export interface AuthenticatorSelectionCriteriaJSON { authenticatorAttachment?: AuthenticatorAttachment; residentKey?: ResidentKeyRequirement; requireResidentKey?: boolean; userVerification?: UserVerificationRequirement; } export interface PublicKeyCredentialCreationOptionsJSON { rp: PublicKeyCredentialRpEntityJSON; user: PublicKeyCredentialUserEntityJSON; challenge: string; pubKeyCredParams: PublicKeyCredentialParametersJSON[]; timeout?: number; excludeCredentials?: PublicKeyCredentialDescriptorJSON[]; authenticatorSelection?: AuthenticatorSelectionCriteriaJSON; attestation?: AttestationConveyancePreference; } export interface PublicKeyCredentialRequestOptionsJSON { challenge: string; timeout?: number; rpId?: string; allowCredentials?: PublicKeyCredentialDescriptorJSON[]; userVerification?: UserVerificationRequirement; } export interface WebAuthnCreationOptionsResponse { publicKey: PublicKeyCredentialCreationOptionsJSON; } export interface WebAuthnRequestOptionsResponse { publicKey: PublicKeyCredentialRequestOptionsJSON; } export interface CredentialAssertionJSON { id: string; type: string; rawId: string; response: { clientDataJSON: string; authenticatorData: string; signature: string; userHandle: string | null; }; } export interface CredentialAttestationJSON { id: string; type: string; rawId: string; response: { clientDataJSON: string; attestationObject: string; }; } export function base64UrlToArrayBuffer(base64url: string): ArrayBuffer { const base64 = base64url.replace(/-/g, "+").replace(/_/g, "/"); const padded = base64 + "=".repeat((4 - (base64.length % 4)) % 4); const binary = atob(padded); return Uint8Array.from(binary, (char) => char.charCodeAt(0)).buffer; } export function arrayBufferToBase64Url(buffer: ArrayBuffer): string { const bytes = new Uint8Array(buffer); const binary = Array.from(bytes, (byte) => String.fromCharCode(byte)).join( "", ); return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); } export function prepareCreationOptions( options: WebAuthnCreationOptionsResponse, ): PublicKeyCredentialCreationOptions { const pk = options.publicKey; return { ...pk, challenge: base64UrlToArrayBuffer(pk.challenge), user: { ...pk.user, id: base64UrlToArrayBuffer(pk.user.id), }, excludeCredentials: (pk.excludeCredentials ?? []).map((cred) => ({ ...cred, id: base64UrlToArrayBuffer(cred.id), })), }; } export function prepareRequestOptions( options: WebAuthnRequestOptionsResponse, ): PublicKeyCredentialRequestOptions { const pk = options.publicKey; return { ...pk, challenge: base64UrlToArrayBuffer(pk.challenge), allowCredentials: (pk.allowCredentials ?? []).map((cred) => ({ ...cred, id: base64UrlToArrayBuffer(cred.id), })), }; } export function serializeAttestationResponse( credential: PublicKeyCredential, ): CredentialAttestationJSON { const response = credential.response as AuthenticatorAttestationResponse; return { id: credential.id, type: credential.type, rawId: arrayBufferToBase64Url(credential.rawId), response: { clientDataJSON: arrayBufferToBase64Url(response.clientDataJSON), attestationObject: arrayBufferToBase64Url(response.attestationObject), }, }; } export function serializeAssertionResponse( credential: PublicKeyCredential, ): CredentialAssertionJSON { const response = credential.response as AuthenticatorAssertionResponse; return { id: credential.id, type: credential.type, rawId: arrayBufferToBase64Url(credential.rawId), response: { clientDataJSON: arrayBufferToBase64Url(response.clientDataJSON), authenticatorData: arrayBufferToBase64Url(response.authenticatorData), signature: arrayBufferToBase64Url(response.signature), userHandle: response.userHandle ? arrayBufferToBase64Url(response.userHandle) : null, }, }; }