teqo/src/lib/campaignWebAuthn.ts
Francisco Ignácio Fontoura Solla 763f64080a Deliver B40: biometric login for /campanha.
WebAuthn platform passkeys: opt-in enrollment after a password login, a
password-less sign-in that mints the same 14-day campaign-token, and
revocation from the profile. The session is minted through Payload's public
pieces (checkLoginPermission, addSessionToUser, getFieldsToSign, jwtSign)
inside a transaction with an advisory lock, so a passkey login neither
bypasses a lockout nor evicts the account's other sessions. The rpID is
derived from NEXT_PUBLIC_SITE_URL and resolves to null when the request host
disagrees, so previews never offer a ceremony the browser would refuse.
2026-07-28 10:59:23 -03:00

48 lines
1.5 KiB
TypeScript

/**
* Client-safe contract for the biometric login (WebAuthn) ceremonies shared by
* the browser islands and the `/campanha/webauthn/*` route handlers. Values
* only — the relying-party resolution and every verification live in
* `server-only` modules.
*/
/**
* Ceiling on passkeys per account. High enough for phone + tablet + laptop,
* low enough that a stolen session cannot seed an unbounded set of silent
* back doors into the account.
*/
export const CAMPAIGN_WEBAUTHN_MAX_CREDENTIALS = 5
/** Cap on the device label so the profile list cannot be used as free storage. */
export const CAMPAIGN_WEBAUTHN_DEVICE_LABEL_MAX_LENGTH = 60
export const CAMPAIGN_WEBAUTHN_ROUTES = {
loginOptions: '/campanha/webauthn/login-options',
login: '/campanha/webauthn/login',
registerOptions: '/campanha/webauthn/register-options',
register: '/campanha/webauthn/register',
} as const
/** A passkey as the profile list renders it. */
export type CampaignPasskeyView = {
id: number
deviceLabel: string
createdAt: string
lastUsedAt: string | null
}
type CampaignWebAuthnErrorResponse = {
status: 'error'
message: string
}
export type CampaignWebAuthnOptionsResponse<Options> =
| { status: 'success'; options: Options }
| CampaignWebAuthnErrorResponse
export type CampaignWebAuthnRegisterResponse =
| { status: 'success'; message: string; passkey: CampaignPasskeyView }
| CampaignWebAuthnErrorResponse
export type CampaignWebAuthnLoginResponse =
| { status: 'success'; redirectTo: string }
| CampaignWebAuthnErrorResponse