fix(e2ee): clear the device's private key on logout (user-approved: every logout)

Resolves the deferred clearDevice security gap: logout never removed the E2EE
private key from IndexedDB, so on a shared machine the conversations stayed
decryptable after sign-out (ChatDock auto-unlocks from the persisted key next
visit). Per the user's decision (clear on EVERY logout), NavSidebar.logout() now
awaits e2ee.clearDevice(me.id) before reload — guarded so an IndexedDB failure
(e.g. private mode) can never block the logout. Re-entering the message
passphrase is required on the next visit.

Resolves the Phase-1 NEEDS-DECISION #3 (clearDevice) + retires the last e2ee
unused export (knip now only flags loadDefaultViewFilters). tsc green, re-review
clean (found + fixed an idb-open throw path blocking logout).
This commit is contained in:
npeter83 2026-07-11 20:57:30 +02:00
parent 5441ad203d
commit 7e562c6cb5

View file

@ -23,6 +23,7 @@ import {
UserPlus,
} from "lucide-react";
import { api, clearActiveAccount, setActiveAccount, type Me } from "../lib/api";
import * as e2ee from "../lib/e2ee";
import { useLiveQuery } from "../lib/useLiveQuery";
import { getUnreadCount, subscribe } from "../lib/notifications";
import type { Page } from "../lib/urlState";
@ -103,6 +104,14 @@ export default function NavSidebar({
} catch {
/* clear locally and reload regardless */
}
// Forget this device's E2EE private key on sign-out: it's otherwise persisted in IndexedDB
// and auto-unlocks on the next visit, so on a shared machine the conversations would stay
// decryptable after logout. Re-entering the message passphrase is required next time.
try {
await e2ee.clearDevice(me.id);
} catch {
/* IndexedDB unavailable (e.g. private mode) — never let it block the logout + reload */
}
clearActiveAccount();
location.reload();
}