Skip to content

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 RecoveryPhraseDisplay component. 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.
  • 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 onComplete is 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.

  • useBackupNudge hook: tracks signupAt, sessionCount, lastDismissedAt, and "tomorrow" deferral count in localStorage.
  • BackupNudgeBanner component (amber, dismissible, used on profile + dashboard).
  • BackupNudgeModal component (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 TOTPSetupModal in 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.json and confirm via lint:openapi-sync.

Phase 5 โ€” PIN re-entry gating + recovery phrase view UX โ€‹

  • useReauthPIN hook: 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.md covering 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 criterionPhase
Signup is 4 mandatory + 1 optional biometric step1
Biometric works on iOS/Android, omits on unsupported(already shipped)
Biometric enable/disable from profile settings1 (entry point), 5 (re-auth gate)
TOTP setup flow with QR works end-to-end3
PIN-encrypted ciphertext retrievable after TOTP3 + 4
PIN change re-wraps stored ciphertext4
Escalating banner system works across sessions2
Recovery phrase view requires PIN re-entry5
Existing pilot users see the new banner if no Tier 22
No regression in zero-knowledge guaranteesAll
docs/engineering/ recovery-model docs6

Built with VitePress