amanpay / web /src /api /auth.ts
MHamdan's picture
CI deploy 84761d0
de6cac5 verified
Raw
History Blame Contribute Delete
3.07 kB
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
}