Files
bitlogin/docs/future-recovery-options.md
Claude 0801371566 Fix live-deployment bugs, add manual passwords, and improve integration support
Fixes reported from the live Vercel deployment:
- vercel.json: remove cleanUrls, which was breaking every internal .html
  link (ERR_FAILED) since links already use explicit extensions
- Recovery export was silently broken after login or password rotation:
  the crypto worker's session state only picked up the recovery capsule
  event on register/recover, never on login/changePassword, even though
  the decrypted payload always carries it. Surfaced recoveryCapsuleEvent
  (and recoveryPublicKey for changePassword) through core's LoginResult /
  ChangePasswordResult and wired them into the worker session; covered by
  a new account.e2e.test.ts case
- New accounts never got a Nostr profile: publishProfileAndRelayLists is
  now called with name: loginName after registration, so the npub's
  profile defaults to the chosen login name
- Password manager never recognized the fields (shadow DOM + JS-driven
  submission defeat the passive save-prompt heuristics, and the
  generated-password screen never put the password in a real input at
  all). Fixed with an explicit navigator.credentials.store() call after
  every register/login/recover/changePassword; documented as
  Chromium-only in docs.html

New: optional manual/custom password (packages/core/src/account/manualPassword.ts),
opt-in per spec §9.3's "future release may permit manual passwords behind
an advanced setting" allowance. Enforces a length floor, a common-password
blocklist, a login-name-substring check, a low-variety-pattern check, and
an entropy floor; the widget shows live non-blocking feedback as the user
types. The generated high-entropy passphrase remains the default,
recommended option.

Integration/interop support:
- window.nostr provider now carries a readonly _bitlogin marker
- window.bitlogin ({ version, isActiveSigner() }) is installed
  unconditionally as soon as the script loads, independent of login
  state or window.nostr's current occupant. Found and fixed a real bug
  along the way: an unconditional assignment is required because browsers
  auto-expose any element with a matching id/name as a same-named global,
  so <bitlogin-auth id="bitlogin"> silently shadowed window.bitlogin
  under a naive `if (!window.bitlogin)` guard. Renamed the demo's own
  element id to avoid the same trap and documented the gotcha in docs.html
- docs.html: new section on supporting multiple signing methods
  (extensions, NIP-46, BitLogin) alongside each other
- Added packages/demo/public/llms.txt for AI-assisted integration

Deployment: GitHub Pages requires a paid plan for private repos, so its
workflow and the .github directory are removed; README documents
Vercel-only deployment and the direct-from-CDN integration path.

Docs: added docs/future-recovery-options.md, laying out passkey-based
2FA/recovery options (pure 2FA, full independent recovery, and a
constrained middle ground) with pros/cons for future consideration —
not a development plan, nothing here is decided.

59/59 tests pass; core and widget typecheck clean; all fixes verified
live in Chromium via Playwright against an in-memory mock relay.

Co-Authored-By: Claude <[email protected]>
Claude-Session: https://claude.ai/code/session_01GWJbXXhvTx9wo59jSz57J6
2026-07-21 15:35:10 +00:00

8.6 KiB
Raw Permalink Blame History

BitLogin: Future Recovery-Path Options

Document version: 0.1 Status: Options for future consideration only. Nothing here is decided, scheduled, or implemented, and this is not a development plan — it's a record of the tradeoffs so the decision can be revisited with the reasoning intact, rather than re-litigated from scratch. Depends on: docs/spec.md and docs/second-factor.md (§SF5 describes the 2-of-2 split-key mechanism referenced throughout).


The core distinction: a second factor vs. a recovery path

These are different shapes of feature, and conflating them is the easiest way to accidentally weaken the account:

  • A second factor (AND-gate). Something additional required alongside the password. It can only make login harder to complete without it — it can never, by itself, grant access. If you lose the second factor, you fall back to whatever recovery paths already existed (today: the recovery phrase).
  • A recovery path (OR-gate). An independent way to reach the account without the password (or without anything else). BitLogin already has exactly one of these: the 12-word recovery phrase. Every recovery path is a full-power master key — anyone who has it can take over the account completely, with nothing else needed.

The question that prompted this document: could a WebAuthn passkey (Face ID / Touch ID / Windows Hello, via the PRF extension) serve as both — a 2FA hardening layer on the password path, and a way to sign back in and reset the password without the phrase, the way "reset via authenticator" flows work on many consumer services today?

Mechanically, yes. Whether to build it is a real tradeoff, laid out below.


Option A — Passkey as a pure second factor (AND-gate only)

How it works: the credential-capsule payload key becomes a 2-of-2 split between the password and a device-held secret (see second-factor.md §SF5 for the exact derivation). The passkey's WebAuthn PRF output unlocks the device's share of that split. Without the password, the capsule can't even be located — the locator itself is password-derived — so the passkey alone grants nothing. The recovery phrase remains the only way to recover without the password.

Pros

  • Matches the "bank 2FA" mental model most users already have: forgot your password still means "use your recovery phrase," full stop. No new master key exists.
  • Smallest attack-surface increase of any option here — it can only ever make an account harder to break into, never easier.
  • Fully backward-compatible and additive to the shipped protocol (see second-factor.md §SF4 for why the envelope-versioning approach doesn't disturb existing accounts).
  • No dependency on a third party's account security (Apple ID, Google Account) for anything account-critical. If a synced passkey is lost or its sync account is compromised, the attacker still needs the password — the phrase alone remains the recovery answer either way.

Cons

  • Doesn't solve "I forgot my password and can't find my 12 words" — the exact moment a lot of real users hit. The convenience win from a Face-ID-driven "reset" flow doesn't materialize under this option.
  • One more thing enrolled per account (a device/credential registry) for a security property that's arguably nice-to-have rather than essential — BitLogin's generated high-entropy passwords already carry most of the defensive load against the threat 2FA typically addresses (credential stuffing / reused passwords), which matters less here since manual passwords are opt-in and discouraged.

Option B — Passkey as an independent recovery path (OR-gate, full parity with the phrase)

How it works: using WebAuthn's discoverable credentials, a browser can present "sign in with a passkey" and hand back an identity without the site supplying a username first. The passkey would derive its own locator and capsule — structurally parallel to how the recovery phrase works today — letting a user tap the passkey, get logged in, and set a brand-new login name and password. No phrase required.

Pros

  • Solves the actual "forgot password" moment with the lowest possible friction: Face ID, done. This is the flow users already expect from "Sign in with passkey" buttons elsewhere on the web.
  • No paper to lose, no drawer to search.

Cons — the significant one first

  • Most real users will have a synced passkey (iCloud Keychain, Google Password Manager) — that's the default, low-friction path on every mainstream device, and there's no realistic way to force people onto non-syncing hardware keys. A synced passkey lives inside the user's Apple ID / Google account. If this option grants full account recovery with no phrase required, the account's real security floor becomes whatever protects that Apple/Google account — not BitLogin's own cryptography. Account takeovers via compromised iCloud/Google credentials (phishing, SIM-swap) are a common, well-documented attack pattern, not a hypothetical.
  • That directly reopens the exact dependency BitLogin's entire design exists to avoid: no operator, no support desk, no third party that can be compelled, breached, or phished into handing over the account. This option quietly relocates that dependency to Apple or Google instead of removing it.
  • It's a second full-power master key, full stop — doubling the number of ways to fully take over an account, independent of how carefully it's implemented.
  • It is not a small add-on. Matching the recovery capsule's existing guarantees (its own address, its own hash chain for gap/replay detection, its own tombstone/vandalism-resistance story per §14.2) means building an entire second, parallel recovery-capsule system — real protocol surface, not a UI feature.
  • Two "I forgot everything" buttons that do fundamentally different things is a support and mental-model problem. A user may stop safeguarding their phrase ("I have Face ID") and only discover it was the one real backup after a new phone or a changed Apple ID quietly drops the synced passkey.

Option C — Constrained passkey recovery (same-credential-only)

How it works: a middle path on Option B — recovery is permitted only through the specific passkey credential ID already enrolled for 2FA (Option A), never a freshly minted one. Enrolling a new passkey requires already being logged in; recovery never accepts an unfamiliar credential.

Pros

  • Closes off the specific case of "attacker takes over my iCloud account, mints a brand-new passkey there, uses it to recover" — a fresh credential can never be used for recovery, only ones already on file.
  • Somewhat lower engineering cost than a fully independent Option B, since it reuses the same enrolled-credential registry as the 2FA design.

Cons

  • Does not close the main hole: if the already-enrolled passkey is itself synced, an attacker who compromises the Apple/Google account gets a working copy of that exact credential too — "same credential only" and "any new credential" become equally reachable once the sync account is owned. This mitigation only fully works for non-syncing, single-device passkeys, which most users won't have.
  • Inherits most of Option B's structural cost (a second recovery-capsule system) for a partial fix.
  • A serverless design has no good way to add the mitigations centralized services normally layer onto "recover without your password" flows — a delay window with a chance to cancel, an out-of-band notification to the real owner, a manual review step. There's no server to hold that state and no reliable channel to notify anyone (BitLogin has no email, and a signed Nostr DM only reaches a device that happens to be online and watching). That gap is a property of the architecture, not something this option can design around — it's worth naming plainly rather than pretending a technical constraint could close it.

Summary comparison

                          Forgot-password    New master-key    Depends on
                          UX improvement?     risk added?      Apple/Google?
Option A (2FA only)              no             minimal            no
Option B (full recovery)        yes              full              yes (synced case)
Option C (constrained)          yes            partial‑reduced     yes (synced case)

None of these are mutually exclusive with each other or with the existing password + phrase design — Option A could ship on its own, or as a foundation that C or B build on top of later. The point of this document is only to make the tradeoff explicit before any of them is chosen.