mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 23:01:09 +02:00
Closes the second Phase 9 follow-up. When a user has zero-knowledge
mode active and signs in on a new device (or after a session expiry),
the layout's vault-unlock effect lands in the new
'awaiting-recovery-code' state. Previously this was a dead end —
the layout just logged a warning and the rest of the app sat with a
locked vault.
This commit adds the missing UI piece: a non-dismissable modal that
mounts whenever the unlock effect signals 'awaiting-recovery-code'.
RecoveryCodeUnlockModal component
---------------------------------
- Reads the singleton vault client via getVaultClient()
- Single text input + submit button
- On submit:
1. Calls vaultClient.unlockWithRecoveryCode(input)
2. On success: clears input, calls onUnlocked() prop → parent
hides the modal, app boots normally
3. On RecoveryCodeFormatError: shows a format hint
4. On any other error (wrong code OR corrupted blob — surfaced
uniformly so an attacker can't distinguish): shows
"Recovery-Code falsch, prüfe deine Eingabe"
- Non-dismissable: there's no Cancel button. Without the recovery
code the app cannot read encrypted data and would just sit in a
half-broken state. The user can sign out from the header (the
auth flow runs above the encryption layer) if they need to bail.
- Help text at the bottom is honest about the irreversible nature
of losing the recovery code.
Layout integration
------------------
+layout.svelte:
- Imports the modal
- New `needsRecoveryCode = $state(false)` flag
- The vault-unlock effect now switches on three branches instead
of just success/failure:
'unlocked' → needsRecoveryCode = false
'awaiting-recovery-code' → needsRecoveryCode = true (mount modal)
anything else → console.warn (unchanged)
- Logout path also resets needsRecoveryCode so the modal doesn't
leak across sessions
- {#if needsRecoveryCode} mounts the component at the bottom of
the markup (above the existing global toasts and banners)
The autofocus warning is suppressed via svelte-ignore — the input
needs immediate focus because it's the only thing the user can
interact with on this surface, and screen-reader users will hear
the modal's accessible name from the role="dialog" + aria-labelledby
binding.
End-to-end smoke flow that now works:
1. User goes to /settings/security on Device A, enables ZK
2. User signs out, signs back in on Device B
3. Layout effect calls vaultClient.unlock() → server returns
recovery blob → vaultClient state goes to awaiting-recovery-code
4. Modal mounts, user pastes their recovery code from password
manager
5. unlockWithRecoveryCode runs the inline AES-GCM unwrap, imports
the MK as non-extractable, caches the bytes for a future
disable, transitions to 'unlocked'
6. Modal calls onUnlocked → layout dismisses modal → rest of the
app boots and renders decrypted data
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
||
|---|---|---|
| .. | ||
| api | ||
| calc/packages/shared | ||
| calendar | ||
| cards | ||
| chat | ||
| citycorners | ||
| contacts | ||
| context | ||
| docs | ||
| guides | ||
| inventar | ||
| mana | ||
| manacore/apps/web/src/lib | ||
| manavoxel | ||
| matrix | ||
| memoro | ||
| moodlit | ||
| mukke | ||
| news | ||
| nutriphi | ||
| photos | ||
| picture | ||
| planta | ||
| presi | ||
| questions | ||
| skilltree | ||
| storage | ||
| times | ||
| todo | ||
| traces | ||
| uload | ||
| zitare/packages/content | ||