managarten/apps/mana/apps
Till JS 24001e9545 feat(vault): rotate recovery code while zero-knowledge is active
Closes backlog #2 from the Phase 9 audit. Lets a user replace their
recovery code without going through the disable→generate→re-enable
dance. Works in BOTH standard and zero-knowledge modes.

vault-client
------------
New rotateRecoveryCode() method on the VaultClient interface.
Returns RecoveryCodeSetupResult, identical shape to setupRecoveryCode.

Branches on the current vault state via getStatus():

  Standard mode:
    Re-fetches the plaintext MK from the server (same path as the
    initial setupRecoveryCode), generates a fresh 32-byte recovery
    secret, derives the new wrap key via HKDF, seals the MK, posts
    the wrap to /recovery-wrap (idempotent server-side, replaces
    the existing row in place).

  Zero-knowledge mode:
    Server can't hand out the plaintext MK any more, so we use the
    cachedUnwrappedMkBytes that unlockWithRecoveryCode stashed when
    the user typed in their old recovery code earlier this session.
    Throws with a clear message if the cache is empty (e.g. user
    landed on the page via init rather than recovery-unlock):
    "sign out and back in with your current recovery code first"
    so the cache gets repopulated.

Both branches:
  - Wipe the raw MK reference after sealing
  - Wipe the recovery secret after format
  - Return the formatted code for the UI to display

The OLD recovery code is now permanently invalid. Using it on a
future unlock attempt will fail with the standard generic
"wrong recovery code" error.

Settings UI
-----------
New rotateStep state machine ('idle' / 'rotated') runs alongside
the existing zkSetupStep so the user can rotate without leaving the
active-state UI.

In the active-mode card (zkSetupStep === 'enabled'):
  - Two side-by-side buttons:
    "🔁 Recovery-Code rotieren" + "Zero-Knowledge-Modus wieder deaktivieren …"
  - When the user clicks rotate, handleRotateRecoveryCode() runs the
    flow and renders an inline "Neuer Recovery-Code" subsection
    (same .recovery-code monospace block + Copy button as the
    initial setup) with explicit warning that the old code is now
    invalid.
  - "Ich habe den neuen Code gesichert" button wipes the displayed
    code and drops back to idle.
  - The disable flow stays available (the rotate UI hides itself
    when the user has clicked into the disable confirmation path).

The 28 vault integration tests still pass (39 total in
encryption-vault/, including the existing 11 KEK tests). The new
rotateRecoveryCode method reuses the already-tested
setRecoveryWrap server endpoint, so no new server-side tests are
needed for this commit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:43:10 +02:00
..
landing chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
mobile chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
web feat(vault): rotate recovery code while zero-knowledge is active 2026-04-07 23:43:10 +02:00