feat(messages): R4 S2 — forgotten-passphrase reset for E2EE
The set-once key guard made a lost passphrase a permanent wall (409 on re-setup, KeyGate offered only unlock/setup). Add an escape hatch: - POST /api/messages/keys/reset drops the user's MessageKey so a new passphrase can be set. Because every conversation key is derived from the keypair, replacing it orphans all stored ciphertext in both directions, so the now-dead `user` messages are deleted too (clean slate; system messages untouched). Password accounts must re-verify (a hijacked session must not silently reset messaging and read future messages); Google-only accounts rely on the session + the client danger-confirm. Rate-limited per user. - KeyGate gains a "Forgot passphrase?" path in unlock mode → a danger reset view (spells out the permanent history loss; a current-password field for password accounts) → resets and returns to a fresh setup. - e2ee.resetLocal wipes this device's key + derived conversation keys + the IndexedDB copy; api.resetMessageKey; HU/EN strings.
This commit is contained in:
@@ -85,6 +85,16 @@ export async function clearDevice(userId: number): Promise<void> {
|
||||
});
|
||||
}
|
||||
|
||||
// Wipe this device's E2EE identity: forget the unlocked key + every derived conversation key,
|
||||
// delete the persisted copy, and notify observers so the gate re-appears. Used by the passphrase
|
||||
// reset, which also drops the server key so the next screen is a fresh setup.
|
||||
export async function resetLocal(userId: number): Promise<void> {
|
||||
privKey = null;
|
||||
convKeys.clear();
|
||||
await clearDevice(userId);
|
||||
emitUnlock();
|
||||
}
|
||||
|
||||
// --- key derivation ---
|
||||
async function wrapKeyFrom(passphrase: string, salt: Uint8Array): Promise<CryptoKey> {
|
||||
const base = await subtle.importKey("raw", bsrc(enc.encode(passphrase)), "PBKDF2", false, [
|
||||
|
||||
Reference in New Issue
Block a user