import { describe, expect, it, vi } from 'vitest' import { encodeBase64UrlBytes } from 'relay-contracts' import { base64UrlToBuffer, bufferToBase64Url, createWebAuthnClient, WebAuthnError, } from '../src/webauthn' function ab(bytes: number[]): ArrayBuffer { return new Uint8Array(bytes).buffer } const assertionCred = { id: 'cred-1', type: 'public-key', rawId: ab([1, 2, 3]), response: { clientDataJSON: ab([4, 5]), authenticatorData: ab([6]), signature: ab([7, 8]), userHandle: null, }, } describe('webauthn wrapper (T7)', () => { it('base64url ↔ ArrayBuffer round-trip is lossless', () => { const bytes = [0, 1, 250, 255, 128, 64] const b64u = bufferToBase64Url(ab(bytes)) expect(new Uint8Array(base64UrlToBuffer(b64u))).toEqual(new Uint8Array(bytes)) }) it('authenticate maps the assertion to base64url fields', async () => { const container = { get: vi.fn(async () => assertionCred), create: vi.fn(), } as unknown as CredentialsContainer const wa = createWebAuthnClient(container) const res = await wa.authenticate({ challenge: ab([9]) } as PublicKeyCredentialRequestOptions) expect(res.rawId).toBe(encodeBase64UrlBytes(new Uint8Array([1, 2, 3]))) expect(res.response.signature).toBe(encodeBase64UrlBytes(new Uint8Array([7, 8]))) expect(res.response.userHandle).toBeNull() }) it('a user-cancelled prompt (NotAllowedError) maps to WebAuthnError("cancelled")', async () => { const container = { get: vi.fn(async () => { throw Object.assign(new Error('dismissed'), { name: 'NotAllowedError' }) }), create: vi.fn(), } as unknown as CredentialsContainer const wa = createWebAuthnClient(container) await expect( wa.authenticate({ challenge: ab([1]) } as PublicKeyCredentialRequestOptions), ).rejects.toMatchObject({ name: 'WebAuthnError', kind: 'cancelled' }) }) it('throws WebAuthnError("unsupported") when no credentials container is available', () => { expect(() => createWebAuthnClient(undefined)).toThrow(WebAuthnError) }) it('does not leak credential material to console', async () => { const spy = vi.spyOn(console, 'log').mockImplementation(() => {}) const container = { get: vi.fn(async () => assertionCred), create: vi.fn(), } as unknown as CredentialsContainer await createWebAuthnClient(container).authenticate({ challenge: ab([1]), } as PublicKeyCredentialRequestOptions) expect(spy).not.toHaveBeenCalled() spy.mockRestore() }) })