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.
37 lines
2.5 KiB
Markdown
37 lines
2.5 KiB
Markdown
# Passkey (WebAuthn) login: assertion, discoverable, clone-detection (ledger backfill)
|
||
|
||
- **Type:** feature — retroactive ledger entry
|
||
- **Date:** 2026-07-01 – 2026-07-05
|
||
- **Area:** `internal/passkey` (assertion crypto), `internal/api` (login/assertion, unbind, UA-guard), `internal/store` (migrations 0013/0014)
|
||
- **Commits:**
|
||
- `e035142` feat(passkey): WebAuthn login/assertion crypto adapter (BeginLogin/FinishLogin over go-webauthn, Oracle-verified against a virtual authenticator; surfaces the signature counter as a ceremony fact)
|
||
- `ec468ba` feat(auth): discoverable (usernameless) passkey login — the from-zero door the username-first assertion couldn't key on
|
||
- `0dbd557` fix(store): renumber the discoverable-login migration 0013 → 0014
|
||
- `9e1df12` feat(passkey): advance `sign_count`, reject clone-warned assertions
|
||
- `4f59d51` feat(auth): owner-tier passkey-unbind remediation endpoint
|
||
- `a63f49d` feat(panel): steer WeChat/QQ in-app browsers to the system browser for passkey — a backend-only UA interstitial (the SPA is untouched); asset/API/health requests pass through, an `ua_ack` cookie lets a determined user continue
|
||
- **Tasks:** #40 (from-zero discoverable login), #67 (WeChat/QQ UA-guard in `internal/panel`)
|
||
|
||
## What it did
|
||
|
||
Built the assertion (login) half of the ceremony: the crypto adapter, then discoverable
|
||
credentials so a user with no typed identifier can still log in (the enrollment
|
||
identifier problem the earlier deferral doc named), clone detection via the advancing
|
||
signature counter, and the owner-tier unbind remediation. `a63f49d` guards the flow at the
|
||
transport edge — WebAuthn is unusable inside the WeChat/QQ WebViews, so those UAs get a
|
||
bilingual "open in your system browser" page instead of the passkey SPA.
|
||
|
||
## Why
|
||
|
||
Enrollment without a login path is half a feature. Discoverable credentials resolve the
|
||
blocker recorded in the earlier deferral (`users.email` is nullable/non-unique and a
|
||
player's username is their Minecraft UUID, so username-first assertion had nothing to key
|
||
on). The UA-guard stops the most common real-world dead end: a passkey prompt that can
|
||
never succeed inside an in-app browser.
|
||
|
||
> **Backfill note.** Reconstructed 2026-07-07 from the commit history. The assertion crypto
|
||
> was verified against a virtual authenticator (enrollment→assertion chain, origin-mismatch
|
||
> and unbound-credential rejection); `9e1df12`'s clone policy and the UA-guard pass-through
|
||
> were unit-tested at their commits. Not independently re-verified for this doc; current
|
||
> tree green at `9911b8c`.
|