feat(desktop): wipe local crypto + caches on sign-out

This commit is contained in:
byGalax
2026-05-16 17:09:01 +02:00
parent 41359816f1
commit 50bfb5b137
2 changed files with 58 additions and 1 deletions
+4 -1
View File
@@ -20,6 +20,7 @@ import {
import { useTranslation } from 'react-i18next';
import { ensureInstallId } from '../lib/installId';
import { wipeLocalState } from '../lib/memoryWipe';
import { setSecretStoreUser } from '../lib/secretStore';
import { supabase } from '../lib/supabase';
import { cachedUserKey, ensureLegacyMigrated } from '../lib/userIdentity';
@@ -235,11 +236,13 @@ export function AuthProvider({ children }: { children: ReactNode }) {
}, [session, profile, refreshProfile]);
const signOut = useCallback(async () => {
const uid = session?.user.id ?? null;
await updateOwnProfile(supabase, { presenceState: 'offline' }).catch((err: unknown) => {
console.warn('offline update before sign-out failed', err);
});
await supabaseSignOut(supabase);
}, []);
await wipeLocalState(uid);
}, [session]);
const value = useMemo<AuthContextValue>(
() => ({
+54
View File
@@ -0,0 +1,54 @@
import { clearConvKeyCache } from '@chat-app/shared/chat';
import { devLocalSecretStore } from './secretStore';
// Aggressively scrub local crypto + chat caches on sign-out (and on
// app-close if the user opted in). Preserves things that aren't sensitive
// and would be annoying to lose (theme, locale, install-id).
//
// We can't enumerate IndexedDB names without async + the indexedDB API,
// so we list the ones we know about explicitly. Adding a new local store
// later? Append to LOCAL_DBS.
const LOCAL_DBS = ['soundboard', 'message-cache', 'chatapp-attachments'];
const PRESERVE_LOCAL_STORAGE = new Set([
'chatapp.theme',
'chatapp.locale',
'chatapp.installId',
'i18nextLng',
]);
export async function wipeLocalState(userId: string | null): Promise<void> {
// 1. Per-conversation key cache (in-memory).
try { clearConvKeyCache(); } catch { /* never throws but be defensive */ }
// 2. Stronghold / secret-store: drop the user-priv blob for this user.
if (userId) {
try { await devLocalSecretStore.removeSecret('chatapp.userpriv.' + userId); }
catch (err) { console.warn('[wipe] userpriv remove failed', err); }
}
// 3. localStorage — preserve only the explicit whitelist.
try {
const keysToDrop: string[] = [];
for (let i = 0; i < window.localStorage.length; i++) {
const k = window.localStorage.key(i);
if (k && !PRESERVE_LOCAL_STORAGE.has(k)) keysToDrop.push(k);
}
for (const k of keysToDrop) window.localStorage.removeItem(k);
} catch (err) { console.warn('[wipe] localStorage clear failed', err); }
// 4. sessionStorage — always full.
try { window.sessionStorage.clear(); } catch { /* ignored */ }
// 5. IndexedDB — delete known databases. Resolves even when blocked so
// we don't hang sign-out forever.
await Promise.allSettled(LOCAL_DBS.map((name) => new Promise<void>((resolve) => {
try {
const req = window.indexedDB.deleteDatabase(name);
req.onsuccess = () => resolve();
req.onerror = () => resolve();
req.onblocked = () => resolve();
} catch { resolve(); }
})));
}