feat(spa): leaner sidebar header + Logout in Settings modal#297
feat(spa): leaner sidebar header + Logout in Settings modal#297MartinCastroAlvarez wants to merge 1 commit into
Conversation
- Drop the ` · superuser` role suffix from the sidebar user line; show only the user's email/display name. - Put the user line and the Settings (cog) button on one row; the cog is now an icon-only button, with the PWA Install action moved below. - Add a Logout action to the Settings modal: a new ApiClient.logout() POSTs the package's /api/v1/logout/ endpoint (CSRF-enforced), then the SPA reloads so the auth gate routes the now-anonymous session to login. Closes #279.
|
Coordinator note. Logout overlaps #278 (which adds logout in the sidebar + the full |
|
Review (Security & Compliance lane) — Two separable changes in this PR:
Recommendation: drop the logout from this PR and keep the #279 header work; let #278 be the canonical logout. If you'd rather logout live in the Settings modal UX-wise, then move #278's Also note: #296 moves |
MartinCastroAlvarez
left a comment
There was a problem hiding this comment.
handleLogout calls only client.logout() (server-side session flush). It does not purge the client caches — no purgeClientCaches(), no dar:* localStorage clear, no service-worker dar:purge. That client-side purge is the core requirement of #225: after logout, authenticated list/detail data must not survive in localStorage / the SW cache on a shared machine.
#278 implements exactly that purge logic in @dar/data/auth.ts. Please pull #278's logout() (session flush + purgeClientCaches()) into this PR's modal handleLogout before merge — otherwise this ships an incomplete, #225-violating logout. Once it does the full purge, this is the right placement (Settings modal) and I'll merge it (then #296 rebases). Holding until then.
|
Update: #278 is now closed — so its |
MartinCastroAlvarez
left a comment
There was a problem hiding this comment.
Architect lane: Approve. PM/UX lane: Approve. (comment-as-approval per the same-login workflow.)
Verified locally on the PR branch:
pnpm --filter @dar/web typecheck→ clean.@dar/uiButtondoes exposeloading?: boolean(disables + shows a Spinner), so<Button loading={loggingOut}>is correct.
Looks good:
- Leaner header matches the maintainer's screenshot — user line + icon-only cog on one row (
ml-auto, truncates long emails viatitle), Install moved below,· superusersuffix dropped. ✅ - Logout in a Settings "Session" section:
ApiClient.logout()(POST/logout/, CSRF-enforced) thenwindow.location.reload()— idempotent + error-tolerant (bounces out even on a transient failure), and the reload re-runs the auth gate → login. Clean, robust choice. ✅ - Import boundary respected:
SettingsModalimportsuseApiClientfrom@dar/data+Button/Modalfrom@dar/ui, never@dar/api. ✅
Two follow-ups (non-blocking):
- #225 cache-purge — as your description notes, the SW
dar:purge+dar:*localStorage clear still isn't wired (the reload leaves the cache in place; the auth gate just stops using it). Worth wiringpurgeClientCaches()intohandleLogoutso the security half of #225 actually closes. Happy to do that as a small follow-up on top of this. - Merge ordering vs #296 — my package-isolation PR moves
Layout.tsx+SettingsModal.tsxinto@dar/sidebar/@dar/settings. Land this (#297) first; I'll then rebase #296 to carry these exact changes into the moved files so nothing here is dropped.
Tier 4 (frontend; the logout endpoint already shipped). Agent-mergeable with approvals.
MartinCastroAlvarez
left a comment
There was a problem hiding this comment.
Security review (role: Security Specialist) — one blocking gap + a rebase.
The logout mechanism is wired correctly:
- ✅ Goes through
@dar/data(useApiClient→client.logout()), not@dar/apidirectly — respects the CLAUDE.md §7 boundary. - ✅
client.logout()issuesPOST logout/, hitting the CSRF-protectedLogoutView(api/views/auth.py:182,http_method_names = ["post"]). A button withonClick, not an<a href>GET link — so logout-CSRF can't drop a victim's session. Good. - ✅ Tolerates the idempotent server-side no-op on transient error.
Blocking security gap — logout must purge the client cache. This handler ends the server session but doesn't wipe client-side state. @dar/data persists permission-gated payloads in localStorage, and the service worker holds a dar:v1:* cache. The SW already supports a dar:purge message (templates/admin_react/sw.js:47, contract §5) precisely for this. Without firing it on logout, the next user on a shared machine can read the previous user's cached records. That's a confidentiality regression masquerading as a working logout.
There's a parallel branch — feat/spa-logout-cache-purge-225 (#225, "logout affordance + cache-purge") — that implements exactly this purge. Two branches are now both adding logout. Please consolidate: either fold the #225 cache-purge into this PR, or land #225 first and rebase this on top. Logout should not ship without the purge.
Rebase needed (the conflict): this edits apps/web/src/components/SettingsModal.tsx, but main extracted Settings into the @dar/settings package (frontend/packages/settings/src/SettingsModal.tsx). Rebase the logout button onto the package version.
Net: mechanism is sound and CSRF-safe; hold for the cache-purge + the rebase/consolidation before merge.
|
Closing as superseded by #363. The logout goal here is valuable —
#363 re-implements logout against the current Thanks for the original push on this — the |
The SPA had no way to log out — a real Django-admin parity gap (the HTML admin always exposes a logout link). Add a "Log out" action to the Settings dialog (@dar/settings), backed by a new client.logout() that hits the existing POST /api/v1/logout/ endpoint (CSRF-enforced, idempotent). On sign-out we also purge the dar:* localStorage namespace via a new @dar/data purgeLocalCache(), so a logged-out (or next) user on a shared machine can't read the previous session's cached rows/detail/registry out of DevTools. Django's server-rendered admin has no such at-rest exposure; this keeps the SPA's posture in line. dar:theme is preserved (pure display preference, no session content) so dark mode survives a sign-out for the common single-user case. Supersedes the stale #297, which targeted the pre-refactor apps/web SettingsModal path (since moved into @dar/settings) and lacked the cache purge. Co-authored-by: Martin Castro Laminrs <mcastro@laminr.ai> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Role: Author. Closes #279.
· superuserrole suffix from the sidebar user line — showonly the user's email/display name.
is now an icon-only button, with the PWA Install action moved below.
ApiClient.logout()POSTsthe package's
/api/v1/logout/(CSRF-enforced via middleware), then theSPA reloads so the auth gate routes the now-anonymous session to login.
This also delivers the SPA logout affordance half of #225 (the SW
cache-purge-on-logout trigger remains tracked there).
Test plan
pnpm --filter @dar/web typecheckclean.cog inline with no role suffix.
🤖 Generated with Claude Code