import { apiRequest } from './client' import { setActiveUser, setToken } from '../auth/session' import { serializeAssertion, serializeRegistration, toCreationOptions, toRequestOptions, isWebAuthnSupported, PasskeyCancelled, PasskeyUnsupported, } from '../auth/passkey' import type { DemoSeedResponse, EnrollResponse } from '../types' /** "Load demo identity": seed a tri-modal identity, receive a session token, and * mark this user as the active session user (used by the payment flow). */ export async function demoSeed(userId: string): Promise { const r = await apiRequest('/demo/seed', { body: { user_id: userId } }) if (r.token) setToken(r.token) setActiveUser({ userId: r.user_id, via: 'demo' }) return r } export async function enrollUnified( userId: string, faceImage: string, fingerprintImage?: string, ): Promise { const body: Record = { user_id: userId, face_image: faceImage } if (fingerprintImage) body.fingerprint_image = fingerprintImage const r = await apiRequest('/unified/enroll', { body }) if (r.token) setToken(r.token) return r } // ---- Passkey / WebAuthn (challenges always from the backend) ---- export async function registerPasskey(userId: string): Promise { if (!isWebAuthnSupported()) throw new PasskeyUnsupported('WebAuthn not supported') const options = await apiRequest>('/webauthn/register/begin', { body: { user_id: userId }, }) let cred: PublicKeyCredential | null try { cred = (await navigator.credentials.create(toCreationOptions(options))) as PublicKeyCredential | null } catch (e) { throw new PasskeyCancelled((e as Error)?.name ?? 'cancelled') } if (!cred) throw new PasskeyCancelled('no credential') const r = await apiRequest<{ success: boolean }>('/webauthn/register/complete', { body: { user_id: userId, credential: serializeRegistration(cred) }, }) return !!r.success } export async function authenticatePasskey(userId: string): Promise { if (!isWebAuthnSupported()) throw new PasskeyUnsupported('WebAuthn not supported') const options = await apiRequest>('/webauthn/authenticate/begin', { body: { user_id: userId }, }) let cred: PublicKeyCredential | null try { cred = (await navigator.credentials.get(toRequestOptions(options))) as PublicKeyCredential | null } catch (e) { throw new PasskeyCancelled((e as Error)?.name ?? 'cancelled') } if (!cred) throw new PasskeyCancelled('no credential') const r = await apiRequest<{ success: boolean }>('/webauthn/authenticate/complete', { body: { user_id: userId, credential: serializeAssertion(cred) }, }) // Passkey login establishes the active user for the payment flow. The demo backend // does not require AMANPAY_REQUIRE_AUTH in this trusted environment, so a bearer // token is optional here; the active user is what the Pay page uses. if (r.success) setActiveUser({ userId, via: 'passkey' }) return !!r.success }