feat(desktop): wipe local crypto + caches on sign-out
This commit is contained in:
@@ -20,6 +20,7 @@ import {
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { ensureInstallId } from '../lib/installId';
|
import { ensureInstallId } from '../lib/installId';
|
||||||
|
import { wipeLocalState } from '../lib/memoryWipe';
|
||||||
import { setSecretStoreUser } from '../lib/secretStore';
|
import { setSecretStoreUser } from '../lib/secretStore';
|
||||||
import { supabase } from '../lib/supabase';
|
import { supabase } from '../lib/supabase';
|
||||||
import { cachedUserKey, ensureLegacyMigrated } from '../lib/userIdentity';
|
import { cachedUserKey, ensureLegacyMigrated } from '../lib/userIdentity';
|
||||||
@@ -235,11 +236,13 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||||||
}, [session, profile, refreshProfile]);
|
}, [session, profile, refreshProfile]);
|
||||||
|
|
||||||
const signOut = useCallback(async () => {
|
const signOut = useCallback(async () => {
|
||||||
|
const uid = session?.user.id ?? null;
|
||||||
await updateOwnProfile(supabase, { presenceState: 'offline' }).catch((err: unknown) => {
|
await updateOwnProfile(supabase, { presenceState: 'offline' }).catch((err: unknown) => {
|
||||||
console.warn('offline update before sign-out failed', err);
|
console.warn('offline update before sign-out failed', err);
|
||||||
});
|
});
|
||||||
await supabaseSignOut(supabase);
|
await supabaseSignOut(supabase);
|
||||||
}, []);
|
await wipeLocalState(uid);
|
||||||
|
}, [session]);
|
||||||
|
|
||||||
const value = useMemo<AuthContextValue>(
|
const value = useMemo<AuthContextValue>(
|
||||||
() => ({
|
() => ({
|
||||||
|
|||||||
@@ -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(); }
|
||||||
|
})));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user