All files / src/lib webauthn.ts

100% Statements 25/25
100% Branches 10/10
100% Functions 8/8
100% Lines 23/23

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111          16x 16x 16x 50x   16x         14x 14x 12x   14x 14x 14x 66x   14x                   3x   3x               1x                           3x   3x       1x                           1x 1x                                   2x 2x                        
/**
 * Base64url encode/decode utilities for WebAuthn binary fields.
 */
 
export function base64urlEncode(buffer: ArrayBuffer): string {
  const bytes = new Uint8Array(buffer)
  let binary = ''
  for (let i = 0; i < bytes.byteLength; i++) {
    binary += String.fromCharCode(bytes[i])
  }
  return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
}
 
export function base64urlDecode(str: string): ArrayBuffer {
  // Add padding
  let base64 = str.replace(/-/g, '+').replace(/_/g, '/')
  while (base64.length % 4 !== 0) {
    base64 += '='
  }
  const binary = atob(base64)
  const bytes = new Uint8Array(binary.length)
  for (let i = 0; i < binary.length; i++) {
    bytes[i] = binary.charCodeAt(i)
  }
  return bytes.buffer
}
 
/**
 * Prepare PublicKeyCredentialCreationOptions from server JSON.
 * Converts base64url strings to ArrayBuffers where the WebAuthn API expects them.
 */
export function prepareRegistrationOptions(
  options: Record<string, unknown>
): PublicKeyCredentialCreationOptions {
  const publicKey = (options.publicKey ?? options) as Record<string, unknown>
 
  return {
    ...publicKey,
    challenge: base64urlDecode(publicKey.challenge as string),
    user: {
      ...(publicKey.user as Record<string, unknown>),
      id: base64urlDecode((publicKey.user as Record<string, string>).id),
    },
    excludeCredentials: ((publicKey.excludeCredentials as Array<Record<string, unknown>>) ?? []).map(
      (cred) => ({
        ...cred,
        id: base64urlDecode(cred.id as string),
      })
    ),
  } as PublicKeyCredentialCreationOptions
}
 
/**
 * Prepare PublicKeyCredentialRequestOptions from server JSON.
 */
export function prepareAuthenticationOptions(
  options: Record<string, unknown>
): PublicKeyCredentialRequestOptions {
  const publicKey = (options.publicKey ?? options) as Record<string, unknown>
 
  return {
    ...publicKey,
    challenge: base64urlDecode(publicKey.challenge as string),
    allowCredentials: ((publicKey.allowCredentials as Array<Record<string, unknown>>) ?? []).map(
      (cred) => ({
        ...cred,
        id: base64urlDecode(cred.id as string),
      })
    ),
  } as PublicKeyCredentialRequestOptions
}
 
/**
 * Serialize a registration credential response for sending to the server.
 */
export function serializeRegistrationCredential(
  credential: PublicKeyCredential
): Record<string, unknown> {
  const response = credential.response as AuthenticatorAttestationResponse
  return {
    id: credential.id,
    rawId: base64urlEncode(credential.rawId),
    type: credential.type,
    response: {
      attestationObject: base64urlEncode(response.attestationObject),
      clientDataJSON: base64urlEncode(response.clientDataJSON),
    },
    authenticatorAttachment: (credential as unknown as Record<string, string>).authenticatorAttachment,
  }
}
 
/**
 * Serialize an authentication credential response for sending to the server.
 */
export function serializeAuthenticationCredential(
  credential: PublicKeyCredential
): Record<string, unknown> {
  const response = credential.response as AuthenticatorAssertionResponse
  return {
    id: credential.id,
    rawId: base64urlEncode(credential.rawId),
    type: credential.type,
    response: {
      authenticatorData: base64urlEncode(response.authenticatorData),
      clientDataJSON: base64urlEncode(response.clientDataJSON),
      signature: base64urlEncode(response.signature),
      userHandle: response.userHandle ? base64urlEncode(response.userHandle) : null,
    },
  }
}
 
← Back to Dashboard