mirror of
https://github.com/PR0M3TH3AN/bitlogin.git
synced 2026-10-27 08:31:21 +00:00
Recovery phrase is mandatory for passkey accounts
Owner decision: keep the architecture (static, independently hostable, no BitLogin-operated OAuth service, shared signer, or central login domain) and strengthen recovery instead. Passkey registration now runs the phrase ceremony BEFORE granting a session -- phrase shown, three words verified, then dashboard -- exactly as password signup does. The deferred 'Secure your account' card is gone, along with the securePhrasePending state behind it: that design was inherited from the OAuth on-ramps, where a service still held a copy of the credential. Nothing holds a copy for a passkey account, so deferring the only recovery path was strictly worse than the design it came from. Note the recovery EXPORT is not an alternative here: by design it carries no phrase-derived key and cannot recover an account alone (§19.5), so the phrase ceremony is the only thing that closes the trap. Also documents the loss mode that surprises people: a passkey is bound to the site's WebAuthn RP id, so a deployment changing domain leaves its users' passkeys unable to unlock anything there -- account untouched on relays, phrase still works. The e2e now drives the real ceremony (reads the 12 words from the DOM, answers the three-word quiz) and asserts activeMethod() is null until it completes, so 'no session before recovery' is enforced against a real browser. New policy pin: runPasskeyRegistration must hand off to the phrase screens and must not dispatch a session itself. 269 tests green. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
1 parent
0f066750b6
commit
30b4f34300
6 files changed
+93
-45
No files matched your search
+20
-8
@@ -58,12 +58,20 @@ leave:
|
||||
remembered password, which dies with the memory, and better than the
|
||||
removed Drive rail, which died with a client ID.
|
||||
- **Lost platform account (Google/Apple ban or lockout), or a device-bound
|
||||
key that's gone: the passkey is gone.** Recourse, in order: the **12-word
|
||||
recovery phrase** (the dashboard's "Secure your account" card exists
|
||||
precisely for this — Tier B2 of the on-ramps design, retained); or a
|
||||
previously done password graduation (PK4). A user who claimed neither
|
||||
and lost the platform account has lost access — the same honest bottom
|
||||
line as every rail, which is why the phrase card is load-bearing.
|
||||
key that's gone: the passkey is gone.** Recourse: the **12-word recovery
|
||||
phrase**, which every passkey account has, because the ceremony is
|
||||
**mandatory at registration** (owner decision 2026-08-04) — the session
|
||||
is not granted until the user has seen the phrase and answered the
|
||||
three-word quiz, exactly as password signup works. The deferred "secure
|
||||
your account later" card this replaced was inherited from the OAuth
|
||||
on-ramp design, where a service still held a copy of the credential;
|
||||
nothing holds a copy here, so deferring the only recovery path was
|
||||
strictly worse than the design it was borrowed from.
|
||||
- **Lost site domain.** Worth stating because it surprises people: the
|
||||
passkey is bound to the site's WebAuthn RP id, so if a deployment changes
|
||||
domain, its users' passkeys no longer unlock anything there. The account
|
||||
is untouched on relays and the phrase (or password) still works — another
|
||||
reason the ceremony is not optional.
|
||||
- **One passkey = one account.** PRF output is per-credential, so a second
|
||||
passkey derives a *different* account — there is no "add a backup
|
||||
passkey" for the same account. The backup for the account is the phrase
|
||||
@@ -91,8 +99,12 @@ account at setup.
|
||||
passkey." Sign-in with a passkey that has no account on this site routes
|
||||
into first-time setup *with the credential it already derived* — fresh
|
||||
identity or import — rather than erroring.
|
||||
- Setup skips the upfront phrase ceremony (Tier B2): the dashboard card
|
||||
holds the phrase for the first session and nudges until claimed.
|
||||
- Setup runs the recovery ceremony before granting a session: phrase shown,
|
||||
three words verified, then the dashboard. The setup screen says so up
|
||||
front ("Next you'll save a 12-word recovery phrase… it isn't optional"),
|
||||
so it reads as part of the flow rather than an interruption. Net secret
|
||||
count is still lower than password signup — one phrase, versus a password
|
||||
*and* a phrase — and daily sign-in is a biometric tap.
|
||||
- Re-creating setup against a passkey whose account already exists simply
|
||||
signs in (`AccountAlreadyExistsError` → login with the same derived
|
||||
credential); a passkey sign-in against no account offers setup
|
||||
|
||||
Reference in new issue
Block a user