Retroactively author 15 grouped detail docs covering the backend functional (feat/fix) commits made before the change ledger was established (fad48ff), closing the ledger's detail-doc axis for the pre-convention history. Each doc groups a feature's constituent commits, lists their SHAs with subjects, and carries a backfill note stating it was reconstructed from git history on 2026-07-07 and not independently re-verified (current tree green at9911b8c). Add a Detail docs section to INDEX.md linking every detail doc (the 6 existing + 15 backfill) to the commit(s) it covers, so a doc is findable from the index without a column on the auto-generated ledger table. Catch the table up with the missing9911b8crow. Scope: backend (Go/Java/K8s) only, per the ledger's stated convention that frontend/panel commits are the collaborator's UI work; non-functional commits (docs/style/chore/refactor) keep their table row without a dedicated detail doc.
38 lines
2.6 KiB
Markdown
38 lines
2.6 KiB
Markdown
# Passkey (WebAuthn) enrollment subsystem + hardening (ledger backfill)
|
||
|
||
- **Type:** feature + fix — retroactive ledger entry
|
||
- **Date:** 2026-07-01 – 2026-07-02
|
||
- **Area:** `internal/passkey` (go-webauthn adapter), `internal/api` (enrollment handlers/audit), `internal/store` (migrations 0007–0009)
|
||
- **Commits:**
|
||
- `f2c916d` feat(api): passkey enrollment persistence layer
|
||
- `742f15f` feat(api): passkey enrollment endpoints
|
||
- `0261204` feat(passkey): go-webauthn enrollment verifier adapter (Oracle-verified against a virtual authenticator)
|
||
- `fce0fce` feat(passkey): wire the enrollment verifier into felis-api
|
||
- `7278cd7` feat(passkey): require + record user verification at enrollment (`UserVerification=required`; capture `user_verified`/`backup_eligible`/`backup_state` — migration 0009) — *fix (d)*
|
||
- `cdbb5ab` fix(api): record credential id in the passkey-register audit event so bind/unbind are symmetric — *fix (a)*
|
||
- `9953275` fix(api): bound `webauthn_challenges` growth by superseding *all* prior rows per (user, purpose) — *fix (b)*
|
||
- `20e31fb` fix(store): cascade-delete passkeys + challenges on user removal (recreate both FKs `ON DELETE CASCADE`, scoped to the passkey tables only) — *fix (c)*
|
||
- `54bc6ef` fix(api): clear bound passkeys on password change to close a takeover foothold — *fix (e)*
|
||
- **Tasks:** #36 (passkey bind with email-OTP fallback), #48–#52 (fixes a–e)
|
||
|
||
## What it did
|
||
|
||
Built the WebAuthn *enrollment* half — persistence, the go-webauthn crypto adapter, and
|
||
the register-begin/finish endpoints — then hardened it through the five-fix batch (a–e):
|
||
symmetric audit, a bounded challenge table, cascade cleanup, enforced+recorded user
|
||
verification, and unbinding every passkey on a password reset so a passkey planted through
|
||
a transiently-hijacked session cannot survive as a standing login foothold.
|
||
|
||
## Why
|
||
|
||
Passkeys are the phishing-resistant factor with email-OTP as the fallback. The hardening
|
||
batch closes the seams that make enrollment safe to *rely on*: without UV enforcement a
|
||
passkey proves possession but not user; without the password-reset clear, a planted
|
||
passkey outlives the very remediation meant to evict an attacker.
|
||
|
||
> **Backfill note.** Reconstructed 2026-07-07 from the commit history. The adapter crypto
|
||
> was verified against a virtual authenticator (virtualwebauthn), and each fix shipped
|
||
> with a targeted test (UV-negative rejection, challenge-growth bound, cascade, symmetric
|
||
> audit) at its commit. Not independently re-verified for this doc; current tree green at
|
||
> `9911b8c`. The assertion/login half is a separate doc ([passkey-login](2026-07-01-passkey-login.md)).
|