fix(security): stop cross-user offline data leak on shared devices (#1176)

Closes BLOCKER B4 — three reinforcing paths could serve one account's
cached data to the next user on a shared device:

- The Workbox 'api-data' cache keyed trip/user-scoped GETs by URL only
  (cookie-blind). Changed to NetworkOnly; offline reads come from the
  per-user IndexedDB cache via the repo layer instead.
- IndexedDB had no per-user scoping. The Dexie connection is now scoped
  per user (trek-offline-u<id>) behind a Proxy so the ~19 importers keep a
  stable binding; login opens the user DB, logout deletes it and returns
  to the anonymous DB.
- logout() was fire-and-forget and racy: background flush/syncAll could
  re-seed the DB after the wipe. It is now async and ordered — close an
  auth gate, unregister sync triggers, disconnect, clear caches, delete
  the user DB — and flush()/syncAll() bail when the gate is closed.
This commit is contained in:
jubnl
2026-06-15 07:58:20 +02:00
committed by GitHub
parent 0a794583d7
commit 5500405f2f
10 changed files with 223 additions and 28 deletions
+4 -4
View File
@@ -105,10 +105,10 @@ describe('authStore', () => {
});
describe('FE-AUTH-006: logout', () => {
it('calls disconnect() and clears user state', () => {
it('calls disconnect() and clears user state', async () => {
useAuthStore.setState({ user: buildUser(), isAuthenticated: true });
useAuthStore.getState().logout();
await useAuthStore.getState().logout();
const state = useAuthStore.getState();
expect(disconnect).toHaveBeenCalledOnce();
@@ -441,10 +441,10 @@ describe('authStore', () => {
});
describe('FE-STORE-AUTH-PERSIST-001: logout resets persisted snapshot', () => {
it('snapshot has isAuthenticated:false after logout (PWA offline will redirect to login)', () => {
it('snapshot has isAuthenticated:false after logout (PWA offline will redirect to login)', async () => {
useAuthStore.setState({ user: buildUser(), isAuthenticated: true });
useAuthStore.getState().logout();
await useAuthStore.getState().logout();
const snapshot = JSON.parse(localStorage.getItem('trek_auth_snapshot') ?? '{}');
expect(snapshot?.state?.isAuthenticated).toBe(false);