feat(spa): logout affordance + cache-purge on logout (Closes #225)#278
feat(spa): logout affordance + cache-purge on logout (Closes #225)#278MartinCastroAlvarez wants to merge 1 commit into
Conversation
The PWA service worker shipped a `dar:purge` message handler (#200/#208) but nothing triggered it, and the SPA had no logout affordance at all (login #190 landed; logout did not). This wires both. - @dar/api: `ApiClient.logout()` → `POST /api/v1/logout/` (CSRF-enforced, idempotent) + `LogoutResponse` type. - @dar/data: `logout(client)` flushes the session then `purgeClientCaches()` — clears every `dar:*` localStorage key AND posts `dar:purge` to the service worker (which deletes the `dar:v1:*` caches). Best-effort: caches are purged even if the network call fails, so a dead session never leaves stale data behind. The data layer owns the cache, so this lives there (CLAUDE.md §7), not in a UI package. - Layout: a **Log out** button in the sidebar user area (Django-admin parity). On click it runs `logout()` then `useRegistry().refresh()`; the registry refetch 403s and `App`'s auth gate swaps in the login screen — no router navigation needed. Defense-in-depth: reads are `Cache-Control: no-store` today so nothing is cached, but this becomes load-bearing the moment a consumer opts into a cacheable read policy. Verified: `pnpm -r typecheck` green (8 projects), `prettier --check` clean. Import boundary preserved (Layout imports only @dar/data + @dar/ui).⚠️ Browser click-through of the logout flow still pending (no headless run here); the change is additive and degrades gracefully. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Heads-up: CONFLICTING/DIRTY against |
|
Coordinator note — 3-way overlap in the sidebar/Settings area. This PR (logout in the sidebar + full cache-purge), #297 (logout in the Settings modal + leaner header), and #296 (refactor: relocate sidebar→ Logout is duplicated here vs #297 (two placements). PM/UX rec: keep this PR's |
|
Review (Security & Compliance lane) — LGTM on substance; Security/maintainability of this PR is sound:
No security objection — just don't merge all three blindly (you'd get a duplicate |
|
Superseding in favour of #297 (customer agent, closes #279), which delivers the logout affordance in the Settings modal + The one piece #297 deliberately leaves open (per its description) is the SW cache-purge-on-logout — the actual security half of #225. That logic from this branch ( |
What
Closes #225. The PWA service worker shipped a
dar:purgehandler (#200/#208) but nothing triggered it, and the SPA had no logout affordance (login #190 landed; logout didn't). This wires both.@dar/api—ApiClient.logout()→POST /api/v1/logout/(CSRF-enforced, idempotent) +LogoutResponsetype.@dar/data—logout(client)flushes the session, thenpurgeClientCaches()clears everydar:*localStorage key and postsdar:purgeto the service worker (which deletes thedar:v1:*caches). Best-effort: caches purge even if the request fails. Lives in the data layer because it owns the cache (CLAUDE.md §7).logout()→useRegistry().refresh(); the refetch 403s andApp's auth gate swaps in the login screen, so no router navigation is needed.Security framing (from #225, filed by the security lane)
Defense-in-depth. Reads are
Cache-Control: no-storetoday so nothing is cached; this becomes load-bearing the moment a consumer opts into a cacheable read policy (PWA_API_CACHE_SECONDS). The SW-side handler was already done — this is the frontend trigger + UX it was waiting on.Verification
pnpm -r typecheck→ all 8 projects Doneprettier --check→ cleanLayoutimports only@dar/data+@dar/ui;auth.ts(in@dar/data) is the only new place that imports@dar/api.Role / tier
Author role. Tier 4 (frontend) — the logout endpoint (auth code, Tier 5) already shipped in #168/#190; this only adds the client call + UX + cache purge. No contract change beyond the additive
LogoutResponsetype (the endpoint's shape was already defined).