The backend existed since 99abca9 with no way to reach it (QA-B1-01).
Now wired:
- LoginPage: requires_webauthn second step mirrors TOTP — the key prompt
fires automatically after the password; cancel/retry + back-to-sign-in
- ProfilePage: PasskeySection (mirrors TotpSection) — enroll with a name,
list keys, password-gated per-key remove + remove-all
- webauthnService: WEBAUTHN_ORIGIN stays authoritative; otherwise accept
the browser's origin when its hostname equals the RP ID — fixes real
dev/e2e enrollment where the Vite UI port differs from the API port
(the browser already enforces RP ID ⊆ origin, so no widened trust)
- Deploy safety: WEBAUTHN_RP_ID documented in .env.example + a boot-time
prod warning in utils/env.cts (localhost-bound keys silently fail
behind a real domain)
- e2e/webauthn.probe.spec.js: CDP virtual authenticator drives the full
lifecycle (enroll -> key sign-in -> password-gated removal), retiring
Cycle 1's 'needs a human with a key' assumption. Probe suite 18/18.
Server 252/252, client 52/52, typecheck + build clean.
Co-Authored-By: Claude Fable 5 <[email protected]>