fix: harden B1/B2 auth-cache security - #29
Conversation
Context for reviewersThis is the second PR in the three-PR sequence. It carries the email-verification synchronization from PR 27 and adds the B1/B2 auth-cache hardening needed to keep account data isolated across login, logout, reload, errors, and browser contexts. The risks this PR addressesLachesis persists TanStack Query data for up to seven days. Before this change, account-derived queries used global keys and one shared persisted cache. That creates several concrete failure paths:
These are account-isolation and session-lifecycle risks. The goal is to make the current bearer and the current account cache move together throughout the actual Lachesis call graph. What the change does1. Namespaces account data without storing the bearerAccount-derived query keys receive a deterministic scope derived from the bearer. The bearer itself never enters a query key, persisted value, The account query roots include profile, day/session, tasks, goals, and the other user-owned data paths used by Lachesis. Public queries keep their existing shared behavior. For persistence, only public data and data belonging to the current non-anonymous scope are retained. Legacy unscoped account entries are discarded because their owner cannot be proven. Mutation records are not persisted. Writes are serialized and invalidated across account transitions so an older queued write cannot repopulate the cache after logout. 2. Clears active account state at the transition boundaryWhen the token is cleared or an account transition begins, the lifecycle first resets account queries so mounted observers receive empty results, then removes those queries from the QueryClient and persists the cleaned cache. This covers profile, task, goal, and session observers rather than relying on one profile observer. Mutation callbacks capture the account scope at 3. Gives every caller one auth sourceThe plugin creates one shared Login and signup publish the new token before the reactive profile query can run. The first profile request therefore reads the new bearer. Logout clears visible account state while retaining the old bearer long enough for the logout request, then clears the token on settlement even when the server is offline or has already revoked it. Unauthorized responses are tied to the bearer that was actually sent. A late 401 from an old request cannot clear a newer login. Login, signup, and password-reset-request routes are excluded from the session-revocation path. 4. Synchronizes supported browser contexts without broadcasting credentialsA token-set or token-cleared event is sent through This handles cases such as:
Review examplesThe most important end-to-end checks are:
The focused Lachesis test command is: npm test --workspace=@lepse/lachesisThe tests cover scoped keys, persisted-cache filtering, queued-write invalidation, A-to-B transitions, token-aware request headers, mounted observers, unauthorized responses, and cross-context events. The release workflow currently builds Lachesis but does not run the frontend tests, so the focused suite remains a local/manual check. Scope and sequenceThis PR changes the Lachesis frontend auth/cache lifecycle only. Clotho routes, the database, migrations, mail delivery, bearer format, and server authorization remain unchanged. No dependency or lockfile change is required. PR 29 depends on PR 27. PR 28 contains both earlier PRs plus the independent timer correction, so the intended landing order is:
The key review question is whether every account-owned read, write, persistence path, and auth transition follows the same scope and bearer lifecycle, including the failure paths. |
Summary
This is PR 2 of a three-PR sequence. Its head is commit
3922fc6579bb14c6000a4b10d50f6c54490f3838and includes PR 1 by design.Merge the email-verification PR first. After that merge, this PR's remaining change is the B1/B2 auth-cache hardening commit.