export function bufferFromBase64URL(value: string): ArrayBuffer { const padded = value.replace(/-/g, "+").replace(/_/g, "/") const pad = padded.length % 4 === 0 ? "" : "=".repeat(4 - (padded.length % 4)) const binary = atob(padded + pad) const bytes = new Uint8Array(binary.length) for (let i = 0; i < binary.length; i += 1) { bytes[i] = binary.charCodeAt(i) } return bytes.buffer } export function bufferToBase64URL(buffer: ArrayBuffer): string { const bytes = new Uint8Array(buffer) let binary = "" for (let i = 0; i < bytes.length; i += 1) { binary += String.fromCharCode(bytes[i]!) } return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "") } export function normalizeRequestOptions( options: PublicKeyCredentialRequestOptions ): PublicKeyCredentialRequestOptions { const challenge = options.challenge if (typeof challenge === "string") { return { ...options, challenge: bufferFromBase64URL(challenge) } } return options } function isPublicKeyRequestOptions(value: unknown): value is PublicKeyCredentialRequestOptions { if (!value || typeof value !== "object") return false const record = value as Record return ( typeof record.challenge === "string" || typeof record.rp === "object" || Array.isArray(record.allowCredentials) ) } export function readWebAuthnRequestOptions( source: Record | null | undefined ): PublicKeyCredentialRequestOptions | null { if (!source) return null const wrapped = source.request_options ?? source.requestOptions if (wrapped && typeof wrapped === "object") { const nested = wrapped as { publicKey?: PublicKeyCredentialRequestOptions } if (nested.publicKey) return nested.publicKey if (isPublicKeyRequestOptions(wrapped)) return wrapped } if (isPublicKeyRequestOptions(source)) { return source } const deviceChallenge = source.challenge if (deviceChallenge && typeof deviceChallenge === "object") { const nested = deviceChallenge as { publicKey?: PublicKeyCredentialRequestOptions } if (nested.publicKey) return nested.publicKey if (isPublicKeyRequestOptions(deviceChallenge)) { return deviceChallenge as PublicKeyCredentialRequestOptions } } return null } export function serializeWebAuthnCredential( credential: PublicKeyCredential ): Record { const response = credential.response as AuthenticatorAssertionResponse return { id: credential.id, rawId: bufferToBase64URL(credential.rawId), type: credential.type, response: { clientDataJSON: bufferToBase64URL(response.clientDataJSON), authenticatorData: bufferToBase64URL(response.authenticatorData), signature: bufferToBase64URL(response.signature), userHandle: response.userHandle ? bufferToBase64URL(response.userHandle) : null, }, } }