amanpay / web /src /auth /passkey.ts
MHamdan's picture
CI deploy 6ffe31f
45a105b verified
Raw
History Blame Contribute Delete
3.39 kB
// WebAuthn helpers. Challenges are ALWAYS generated by the backend; the browser only
// converts the base64url fields and runs navigator.credentials. Credential responses /
// authenticator data are never logged.
export function b64urlToBuf(s: string): ArrayBuffer {
const pad = '='.repeat((4 - (s.length % 4)) % 4)
const b64 = (s + pad).replace(/-/g, '+').replace(/_/g, '/')
const bin = atob(b64)
const bytes = new Uint8Array(bin.length)
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i)
return bytes.buffer
}
export function bufToB64url(buf: ArrayBuffer): string {
const bytes = new Uint8Array(buf)
let s = ''
for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i])
return btoa(s).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
}
export function isWebAuthnSupported(): boolean {
return typeof window !== 'undefined' && !!window.PublicKeyCredential
}
export async function platformAuthenticatorAvailable(): Promise<boolean> {
try {
return (
isWebAuthnSupported() &&
(await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable())
)
} catch {
return false
}
}
// Backend register-options -> browser create() input.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function toCreationOptions(opts: any): CredentialCreationOptions {
const publicKey = { ...opts }
publicKey.challenge = b64urlToBuf(opts.challenge)
publicKey.user = { ...opts.user, id: b64urlToBuf(opts.user.id) }
if (Array.isArray(opts.excludeCredentials)) {
publicKey.excludeCredentials = opts.excludeCredentials.map(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(c: any) => ({ ...c, id: b64urlToBuf(c.id) }),
)
}
return { publicKey }
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function toRequestOptions(opts: any): CredentialRequestOptions {
const publicKey = { ...opts }
publicKey.challenge = b64urlToBuf(opts.challenge)
if (Array.isArray(opts.allowCredentials)) {
publicKey.allowCredentials = opts.allowCredentials.map(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(c: any) => ({ ...c, id: b64urlToBuf(c.id) }),
)
}
return { publicKey }
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function serializeRegistration(cred: PublicKeyCredential): any {
const r = cred.response as AuthenticatorAttestationResponse
return {
id: cred.id,
rawId: bufToB64url(cred.rawId),
type: cred.type,
response: {
attestationObject: bufToB64url(r.attestationObject),
clientDataJSON: bufToB64url(r.clientDataJSON),
},
clientExtensionResults: cred.getClientExtensionResults?.() ?? {},
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function serializeAssertion(cred: PublicKeyCredential): any {
const r = cred.response as AuthenticatorAssertionResponse
return {
id: cred.id,
rawId: bufToB64url(cred.rawId),
type: cred.type,
response: {
authenticatorData: bufToB64url(r.authenticatorData),
clientDataJSON: bufToB64url(r.clientDataJSON),
signature: bufToB64url(r.signature),
userHandle: r.userHandle ? bufToB64url(r.userHandle) : null,
},
}
}
export class PasskeyCancelled extends Error {}
export class PasskeyUnsupported extends Error {}