Skip to content

2026-05-01 โ€” Tiered Auth Recovery Phase 1 (#352) โ€” Complete โ€‹

Tracking issue: #352 โ€” Tiered Auth Recovery Closed duplicate: #354Plan: docs/plans/2026-05-01_tiered_auth_recovery_plan.mdBranch: feat/352-auth-recovery-phase1 (worktree, merged locally โ€” not pushed)

What shipped โ€‹

Phase 1 of the tiered auth recovery rollout: defer recovery setup out of signup and into a discoverable profile-settings card, without weakening the existing zero-knowledge guarantees.

Signup simplification โ€‹

apps/web/src/screens/PhonePinSignup.jsx cut from 6 steps to 4:

  • Removed in-flow EmailCaptureStep (recovery email) and RecoveryPhraseDisplay step.
  • Removed the standalone RecoveryBackupModal step.
  • Step 4 is now a single "Almost there" confirmation that triggers account creation.
  • Recovery phrase is still generated silently; entropy is still cached in IndexedDB via the existing getLastDerivedEntropy() โ†’ cacheEntropy() pipeline. No crypto change.

New profile recovery surface โ€‹

  • New apps/web/src/components/AccountRecoveryCard.jsx rendered at the top of Profile โ†’ Privacy & Data.
  • Two actions: View recovery phrase (uses existing RecoveryPhraseDisplay) and Send encrypted email backup (uses existing RecoveryBackupModal).
  • Card switches between a high-prominence red "Set up account recovery" state and a calm neutral "Account recovery is set up" state with a green ShieldCheck.
  • Per-action emerald Done pills mark which Tier-2 options are claimed.

Discoverability ("you have something to do" signal) โ€‹

  • Red dot on the bottom-nav Me tab when no Tier-2 method is set up.
  • Red dot on the Privacy & Data profile tab in the same condition.
  • Both dots react in real time to claim/clear via custom event + cross-tab storage event.

Plumbing โ€‹

  • New apps/web/src/lib/recoveryStatus.js โ€” localStorage-backed getRecoveryStatus(), hasRecoverySetup(), markRecoveryPhraseViewed(), markEmailBackupSent(), clearRecoveryStatus(). Phase 4 will move source-of-truth to Firestore so it follows the user across devices.
  • New apps/web/src/hooks/useRecoverySetupStatus.js โ€” keeps badges/banner reactive.
  • packages/shared/encryption now re-exports entropyToMnemonic + wordlist so the card can derive the phrase on demand.

Dev/test ergonomics โ€‹

  • New window.recoverySpoof (dev-only) helpers in apps/web/src/main.jsx:
    • recoverySpoof.status() / hasSetup() / claimPhrase() / claimEmailBackup() / claimAll() / clear()
    • Lets you flip between needs-setup and set-up states in the browser without going through the real flows.
  • 4 new Vitest tests for AccountRecoveryCard (all passing).

Out of scope (deferred to follow-up issues) โ€‹

  • #365 โ€” PIN re-entry gate before "View recovery phrase".
  • #362 โ€” Escalating session/day-based nudge banner & modal.
  • #363 โ€” TOTP authenticator setup flow.
  • #364 โ€” Auth-API endpoints for TOTP + server-side recovery flag.
  • #366 โ€” Security model documentation refresh.

Validation โ€‹

npm run validate -- --workspace apps/web โ†’ 11/11 PASS at the time of merge.

Notes for next agent โ€‹

  • recoveryStatus flags currently live in localStorage only. Cross-device sync is Phase 4 work.
  • The signup flow no longer surfaces the recovery phrase; users reach it exclusively via the new card. The two red dots are the only nudge until Phase 2 (#362) lands.
  • Worktree pathology (eslint plugin duplication, missing .env.local symlink) bit us during this work โ€” see docs/engineering/guides/PR_WORKFLOW.md for the symlink workaround. Lantern Control extension is also not worktree-aware (#360 tracks).

Built with VitePress