Commit dd36b21
feat(auth): passkey (WebAuthn) login + superadmin-org 2FA
Add passkeys as an additional authentication method for all users, and require a
second factor (email + passkey) in the superadmin org when the user has a passkey.
- @simplewebauthn/{server,browser} v13; all verification in a server-only
src/lib/server/webauthn module (per-org-domain RP ID + full origin, derived per
request; never hardcoded). Signature counter persisted (clone detection).
- Credentials stored as PASSKEY-type Identifier rows (new Identifier.json holds
publicKey/counter/transports/deviceType/backedUp/label/lastUsedAt; identifier =
credentialId) toward a unified identifier model. PASSKEY rows are excluded from all
contact/identity displays + visibility settings.
- WebAuthn challenge stored on the Session row (single-use, short TTL), closing the
replay hole; new Session columns passkeyChallenge/passkeyChallengeExpiresAt/
emailVerifiedAt (additive migration).
- Register + manage passkeys in /settings (add/list/rename/delete). Usernameless
(discoverable) passkey login. Superadmin org: email code sets emailVerifiedAt and
defers activation until a passkey assertion at /webauthn/2fa/verify; the two factors
are bound to one cookie session and neither can be skipped or swapped. Non-superadmin
orgs and superadmins without a passkey are unchanged (passkey OR email).
Library choice follows the incomplete PR #27; the insecure parts (no server-side
challenge, hardcoded RP, client-side verification, stubbed auth, no counter) were
rebuilt.
ADR: docs/adr/2026-06-13-passkey-webauthn-auth.md
Plan: planning/orca/2026-06-13-passkey-auth/plan.md
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>1 parent bdc55f6 commit dd36b21
34 files changed
Lines changed: 2407 additions & 35 deletions
File tree
- docs/adr
- src
- lib/server/webauthn
- messages
- en-AU
- en-US
- fr
- it
- prisma
- migrations/20260613052610_passkey_webauthn
- routes
- api/[[version]]/webauthn
- 2fa/verify
- authenticate
- options
- verify
- register
- options
- verify
- login
- members/[id]
- settings
- tests/vitest
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 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 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
119 | 119 | | |
120 | 120 | | |
121 | 121 | | |
| 122 | + | |
| 123 | + | |
122 | 124 | | |
123 | 125 | | |
124 | 126 | | |
| |||
0 commit comments