File size: 3,074 Bytes
45a105b de6cac5 45a105b de6cac5 45a105b de6cac5 45a105b de6cac5 45a105b | 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 | 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<DemoSeedResponse> {
const r = await apiRequest<DemoSeedResponse>('/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<EnrollResponse> {
const body: Record<string, string> = { user_id: userId, face_image: faceImage }
if (fingerprintImage) body.fingerprint_image = fingerprintImage
const r = await apiRequest<EnrollResponse>('/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<boolean> {
if (!isWebAuthnSupported()) throw new PasskeyUnsupported('WebAuthn not supported')
const options = await apiRequest<Record<string, unknown>>('/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<boolean> {
if (!isWebAuthnSupported()) throw new PasskeyUnsupported('WebAuthn not supported')
const options = await apiRequest<Record<string, unknown>>('/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
}
|