UNPKG

accounts

Version:

Tempo Accounts SDK

211 lines (180 loc) 7.1 kB
import { afterEach, describe, expect, test, vi } from 'vp/test' import * as WebAuthnCeremony from './WebAuthnCeremony.js' afterEach(() => vi.unstubAllGlobals()) describe('from', () => { test('default: returns the ceremony', () => { const ceremony = WebAuthnCeremony.from({ getRegistrationOptions: async () => ({ options: {} as any }), verifyRegistration: async () => ({ credentialId: 'cred-1', publicKey: '0x1234' }), getAuthenticationOptions: async () => ({ options: {} as any }), verifyAuthentication: async () => ({ credentialId: 'cred-1', publicKey: '0x1234' }), }) expect(ceremony).toBeDefined() expect(typeof ceremony.getRegistrationOptions).toMatchInlineSnapshot(`"function"`) expect(typeof ceremony.verifyRegistration).toMatchInlineSnapshot(`"function"`) expect(typeof ceremony.getAuthenticationOptions).toMatchInlineSnapshot(`"function"`) expect(typeof ceremony.verifyAuthentication).toMatchInlineSnapshot(`"function"`) }) }) describe('local', () => { test('default: creates a ceremony', () => { const ceremony = WebAuthnCeremony.local({ rpId: 'example.com' }) expect(ceremony).toBeDefined() }) test('behavior: getRegistrationOptions returns serialized options', async () => { const ceremony = WebAuthnCeremony.local({ rpId: 'example.com' }) const { options } = await ceremony.getRegistrationOptions({ name: 'Test' }) expect(options.publicKey).toBeDefined() expect(options.publicKey!.rp.id).toMatchInlineSnapshot(`"example.com"`) expect(options.publicKey!.rp.name).toMatchInlineSnapshot(`"example.com"`) expect(typeof options.publicKey!.challenge).toMatchInlineSnapshot(`"string"`) expect(options.publicKey!.authenticatorSelection).toBeDefined() }) test('behavior: getAuthenticationOptions returns serialized options', async () => { const ceremony = WebAuthnCeremony.local({ rpId: 'example.com' }) const { options } = await ceremony.getAuthenticationOptions() expect(options.publicKey).toBeDefined() expect(options.publicKey!.rpId).toMatchInlineSnapshot(`"example.com"`) expect(typeof options.publicKey!.challenge).toMatchInlineSnapshot(`"string"`) }) test('behavior: getAuthenticationOptions restricts authentication to multiple credentials', async () => { const ceremony = WebAuthnCeremony.local({ rpId: 'example.com' }) const { options } = await ceremony.getAuthenticationOptions({ credentialId: ['Y3JlZC0x', 'Y3JlZC0y'], }) expect(options.publicKey?.allowCredentials).toMatchInlineSnapshot(` [ { "id": "Y3JlZC0x", "type": "public-key", }, { "id": "Y3JlZC0y", "type": "public-key", }, ] `) }) test('behavior: verifyRegistration stores credential and returns publicKey', async () => { const ceremony = WebAuthnCeremony.local({ rpId: 'example.com' }) const result = await ceremony.verifyRegistration({ attestationObject: 'mock', clientDataJSON: 'mock', id: 'cred-1', publicKey: '0xabcd', raw: { id: 'cred-1', type: 'public-key', authenticatorAttachment: null, rawId: 'cred-1', response: { clientDataJSON: 'mock' }, }, }) expect(result).toMatchInlineSnapshot(` { "credentialId": "cred-1", "publicKey": "0xabcd", } `) }) test('behavior: verifyAuthentication returns stored publicKey', async () => { const ceremony = WebAuthnCeremony.local({ rpId: 'example.com' }) // Register first to store the credential await ceremony.verifyRegistration({ attestationObject: 'mock', clientDataJSON: 'mock', id: 'cred-1', publicKey: '0xabcd', raw: { id: 'cred-1', type: 'public-key', authenticatorAttachment: null, rawId: 'cred-1', response: { clientDataJSON: 'mock' }, }, }) const result = await ceremony.verifyAuthentication({ id: 'cred-1', metadata: { authenticatorData: '0x00', clientDataJSON: '{}', }, raw: { id: 'cred-1', type: 'public-key', authenticatorAttachment: null, rawId: 'cred-1', response: { clientDataJSON: 'mock' }, }, signature: '0x00', }) expect(result).toMatchInlineSnapshot(` { "credentialId": "cred-1", "publicKey": "0xabcd", } `) }) test('error: verifyAuthentication throws for unknown credential', async () => { const ceremony = WebAuthnCeremony.local({ rpId: 'example.com' }) await expect( ceremony.verifyAuthentication({ id: 'unknown', metadata: { authenticatorData: '0x00', clientDataJSON: '{}', }, raw: { id: 'unknown', type: 'public-key', authenticatorAttachment: null, rawId: 'unknown', response: { clientDataJSON: 'mock' }, }, signature: '0x00', }), ).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: Unknown credential: unknown]`) }) test('behavior: each call to getRegistrationOptions generates a unique challenge', async () => { const ceremony = WebAuthnCeremony.local({ rpId: 'example.com' }) const { options: a } = await ceremony.getRegistrationOptions({ name: 'Test' }) const { options: b } = await ceremony.getRegistrationOptions({ name: 'Test' }) expect(a.publicKey!.challenge).not.toBe(b.publicKey!.challenge) }) test('behavior: each call to getAuthenticationOptions generates a unique challenge', async () => { const ceremony = WebAuthnCeremony.local({ rpId: 'example.com' }) const { options: a } = await ceremony.getAuthenticationOptions() const { options: b } = await ceremony.getAuthenticationOptions() expect(a.publicKey!.challenge).not.toBe(b.publicKey!.challenge) }) }) describe('server', () => { test('uses the default credential mode when omitted', async () => { let request: RequestInit | undefined vi.stubGlobal('fetch', async (_input: string | URL, init?: RequestInit) => { request = init return Response.json({ options: {} }) }) const ceremony = WebAuthnCeremony.server({ url: 'https://wallet.example.com/webauthn' }) await ceremony.getRegistrationOptions({ name: 'Test' }) expect(request?.credentials).toMatchInlineSnapshot(`undefined`) }) test('includes configured credentials in ceremony requests', async () => { let request: RequestInit | undefined vi.stubGlobal('fetch', async (_input: string | URL, init?: RequestInit) => { request = init return Response.json({ options: {} }) }) const ceremony = WebAuthnCeremony.server({ credentials: 'include', url: 'https://wallet.example.com/webauthn', }) await ceremony.getRegistrationOptions({ name: 'Test' }) expect({ credentials: request?.credentials, method: request?.method }).toMatchInlineSnapshot(` { "credentials": "include", "method": "POST", } `) }) })