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:
@@ -1623,6 +1623,14 @@ export const api = {
|
||||
messageMyKey: (): Promise<MyKeyBundle> => req("/api/messages/keys/me"),
|
||||
setupMessageKey: (k: KeySetup): Promise<{ configured: boolean }> =>
|
||||
req("/api/messages/keys", { method: "POST", body: JSON.stringify(k) }),
|
||||
// Forgotten-passphrase reset: drops the server key (and the now-undecryptable history) so a new
|
||||
// passphrase can be set. Password accounts must pass their current password; errors shown inline.
|
||||
resetMessageKey: (currentPassword?: string): Promise<{ configured: boolean }> =>
|
||||
req(
|
||||
"/api/messages/keys/reset",
|
||||
{ method: "POST", body: JSON.stringify({ current_password: currentPassword ?? null }) },
|
||||
{ quiet: true },
|
||||
),
|
||||
messagePublicKey: (userId: number): Promise<{ user_id: number; public_key: string }> =>
|
||||
req(`/api/messages/keys/${userId}`),
|
||||
messageUsers: (): Promise<MessageUser[]> => req("/api/messages/users"),
|
||||
|
||||
@@ -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