teqo-backup/src/lib/campaignWebAuthnSupport.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

40 lines
1.6 KiB
TypeScript

/**
* The always-loaded half of the browser side of WebAuthn (B40): the capability
* probe and the error type. It imports nothing, which is the point —
* `campaignWebAuthnClient.ts` pulls in `@simplewebauthn/browser` (~9 kB) and is
* therefore only ever reached through a dynamic `import()` inside a handler, so
* the ceremony code stays out of the First Load JS of every campaign route the
* enrollment toast is mounted on.
*/
/**
* A ceremony failure with a message already written for the person. Everything
* else bubbles as a plain `Error` the island turns into its own fallback copy.
*/
export class CampaignWebAuthnError extends Error {
/** True when the person simply closed the OS prompt — no alarm, no red. */
readonly cancelled: boolean
constructor(message: string, options: { cancelled?: boolean } = {}) {
super(message)
this.name = 'CampaignWebAuthnError'
this.cancelled = options.cancelled ?? false
}
}
/**
* Whether this browser can complete a platform ceremony at all. Awaiting this
* before rendering is what keeps a dead "Entrar com digital" button off a
* desktop with no enrolled authenticator.
*/
export const canUseCampaignBiometrics = async (): Promise<boolean> => {
if (typeof window === 'undefined') return false
if (!window.PublicKeyCredential?.isUserVerifyingPlatformAuthenticatorAvailable) return false
try {
return await window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
} catch {
// A browser that refuses to answer the probe cannot host the ceremony.
return false
}
}