Tiered Auth Recovery โ Implementation Plan โ
Tracking issue: #352Related: #147 (Advanced Encryption Features) โ TOTP work overlaps Closed duplicate: #354 Created: 2026-05-01 Status: Phase 1 complete (merged into local working branch 2026-05-01)
Goal โ
Replace the current 6-step Phone+PIN signup with a 4-step (+1 optional) flow, defer recovery setup to profile settings, add biometric/TOTP/encrypted-email-backup as opt-in tiers, and nudge users escalatingly until they enable at least one cross-device recovery method.
See #352 for the full feature specification, acceptance criteria, and out-of-scope items.
Why phase this โ
The full scope is too large for a single PR โ it touches signup UX, profile settings, auth-api endpoints, encryption library, escalating-nudge state machine, biometric (already partially done), and a new TOTP setup flow with QR codes and server-side secret storage. Each phase below is a shippable PR-sized slice that delivers user value without depending on later phases.
Phase Breakdown โ
Phase 1 โ Defer recovery out of signup (this PR) โ
Goal: Cut signup from 6 to 4 mandatory steps. Move recovery phrase display + email backup option out of the signup flow and into a new "Account Recovery" card in profile settings.
Scope:
PhonePinSignup.jsx: remove step 4 (email capture) and step 5 (recovery phrase display); collapse step 6 (terms) into the new step 4. Recovery phrase is still generated silently and entropy is still cached in IndexedDB exactly as today.ProfileSettings.jsx: add a new "Account Recovery" section with two actions:- View recovery phrase โ derives the phrase from cached entropy and displays it via existing
RecoveryPhraseDisplaycomponent. No PIN re-entry yet โ tracked separately. - Send encrypted email backup โ opens existing
RecoveryBackupModal. Requires a recovery email; if none on file, prompts user to add one first.
- View recovery phrase โ derives the phrase from cached entropy and displays it via existing
- Add a separate "Recovery email" row that lets users add/change/remove the encrypted recovery email post-signup (replaces the in-flow
EmailCaptureStep). - Existing biometric prompt (current step 6 โ onComplete handler) keeps working โ it already runs after
onCompleteis called. - Storybook + Vitest updates for the new signup step count and the new profile card.
Out of scope (Phase 1):
- PIN re-entry gating on "View recovery phrase" โ tracked as a separate sub-issue.
- Escalating banner system โ Phase 2.
- TOTP โ Phase 3.
- Auth-api endpoint changes โ Phase 4 (only needed for TOTP-gated seed retrieval).
Phase 2 โ Escalating backup nudge โ
Build the session-counter-driven nudge that promotes from a profile-page banner โ dashboard banner โ modal over the first week, gated on whether any Tier 2 method is enabled.
useBackupNudgehook: trackssignupAt,sessionCount,lastDismissedAt, and "tomorrow" deferral count inlocalStorage.BackupNudgeBannercomponent (amber, dismissible, used on profile + dashboard).BackupNudgeModalcomponent (non-blocking, shown session 7+ or day 8+).- Tier-2 detection: backed-by-email-backup (existing flag) OR explicitly-viewed-recovery-phrase (new local flag) OR TOTP-enabled (Phase 3 will flip this).
- Pilot users with no Tier 2 method see the same nudge โ desirable per #352.
Phase 3 โ TOTP authenticator setup โ
UI + client crypto for TOTP.
- New
TOTPSetupModalin profile recovery card: generates a TOTP secret, renders QR code, asks user to confirm a code from their authenticator app. - Use a small audited TOTP lib (e.g.
otpauth). - Client encrypts the user's seed entropy with their PIN-derived wrapping key (already done at signup) and uploads the ciphertext + TOTP shared secret to auth-api (Phase 4 endpoint).
- Status indicator: "TOTP enabled" in the recovery card.
Phase 4 โ auth-api endpoints for PIN-encrypted seed โ
Backend support for Phase 3 + the "I got a new phone" recovery story.
POST /auth/recovery/totp/setupโ store TOTP secret + PIN-encrypted seed ciphertext.POST /auth/recovery/totp/verify-and-retrieveโ verify a TOTP code, return ciphertext.POST /auth/recovery/totp/disableโ wipe both.POST /auth/recovery/seed/rewrapโ called after PIN change to re-wrap the stored ciphertext (client uploads new ciphertext + old/new TOTP code as proof of possession).- Spec these in the auth-api
openapi.jsonand confirm vialint:openapi-sync.
Phase 5 โ PIN re-entry gating + recovery phrase view UX โ
useReauthPINhook: prompts for PIN, validates against stored proof hash, gates a callback.- Apply to "View recovery phrase" in the recovery card.
- Apply to "Disable biometric", "Disable TOTP" toggles.
Phase 6 โ Documentation + cleanup โ
- New page
docs/engineering/security/RECOVERY_MODEL.mdcovering the tiered model, threat assumptions, and operator runbook. - Update
docs/engineering/security/ENCRYPTION.md(if exists) to cross-link. - Worklog entry summarising the rollout.
Dependencies โ
Phase 1 โโโถ Phase 2 โโโถ Phase 6
โโโโถ Phase 3 โโโถ Phase 4 โโโถ Phase 6
โโโโถ Phase 5 โโโPhase 2 depends on Phase 1 (recovery card must exist to host the nudge target). Phase 4 depends on Phase 3 (endpoints have no consumer until the client sends ciphertext). Phase 5 can run in parallel with 2/3.
Acceptance criteria mapping โ
The acceptance criteria in #352 map to phases as:
| #352 criterion | Phase |
|---|---|
| Signup is 4 mandatory + 1 optional biometric step | 1 |
| Biometric works on iOS/Android, omits on unsupported | (already shipped) |
| Biometric enable/disable from profile settings | 1 (entry point), 5 (re-auth gate) |
| TOTP setup flow with QR works end-to-end | 3 |
| PIN-encrypted ciphertext retrievable after TOTP | 3 + 4 |
| PIN change re-wraps stored ciphertext | 4 |
| Escalating banner system works across sessions | 2 |
| Recovery phrase view requires PIN re-entry | 5 |
| Existing pilot users see the new banner if no Tier 2 | 2 |
| No regression in zero-knowledge guarantees | All |
docs/engineering/ recovery-model docs | 6 |