Skip to content

feat(spa): leaner sidebar header + Logout in Settings modal#297

Closed
MartinCastroAlvarez wants to merge 1 commit into
mainfrom
feat/sidebar-header-logout
Closed

feat(spa): leaner sidebar header + Logout in Settings modal#297
MartinCastroAlvarez wants to merge 1 commit into
mainfrom
feat/sidebar-header-logout

Conversation

@MartinCastroAlvarez
Copy link
Copy Markdown
Owner

Summary

Role: Author. Closes #279.

  • 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 Logout to the Settings modal: a new ApiClient.logout() POSTs
    the package's /api/v1/logout/ (CSRF-enforced via middleware), then the
    SPA 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 typecheck clean.
  • Wheel built + deployed to a live pilot; sidebar renders the email +
    cog inline with no role suffix.
  • Manual: clicking Log out ends the session and lands on login.

🤖 Generated with Claude Code

- 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.
@MartinCastroAlvarez
Copy link
Copy Markdown
Owner Author

Coordinator note. Logout overlaps #278 (which adds logout in the sidebar + the full purgeClientCaches()/SW dar:purge cache-purge required by #225). This PR adds logout in the Settings modal. PM/UX rec: one logout affordance — keep #278's logout+cache-purge logic, placed here in the Settings modal. Please also note #296 relocates SettingsModal.tsx@dar/settings; whichever of these lands first, the other two need a rebase. Suggest #296 (refactor) merges last.

@MartinCastroAlvarez
Copy link
Copy Markdown
Owner Author

Review (Security & Compliance lane) — ⚠️ logout here duplicates #278 and is incomplete; the #279 header cleanup is fine.

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 @dar/data logout() (with the cache-purge) here instead of calling client.logout() + reload directly — but don't ship two logout paths.

Also note: #296 moves components/SettingsModal.tsx into @dar/settings, so this PR will conflict with it on that file — sequence accordingly.

Copy link
Copy Markdown
Owner Author

@MartinCastroAlvarez MartinCastroAlvarez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Security gap (blocks merge) — Reviewer. This PR's 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.

@MartinCastroAlvarez
Copy link
Copy Markdown
Owner Author

Update: #278 is now closed — so its purgeClientCaches() (the #225 cache-purge trigger over the SW dar:purge handler from #200/#208) no longer has any home. This PR is now the only logout path, and it does session-flush only. Please add the client-cache purge to handleLogout here (clear dar:* localStorage + post dar:purge to the SW) before merge, else #225 regresses to 'session flushed but authed data still cached on the device'. Still holding my merge on this.

Copy link
Copy Markdown
Owner Author

@MartinCastroAlvarez MartinCastroAlvarez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/ui Button does expose loading?: 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 via title), Install moved below, · superuser suffix dropped. ✅
  • Logout in a Settings "Session" section: ApiClient.logout() (POST /logout/, CSRF-enforced) then window.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: SettingsModal imports useApiClient from @dar/data + Button/Modal from @dar/ui, never @dar/api. ✅

Two follow-ups (non-blocking):

  1. #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 wiring purgeClientCaches() into handleLogout so the security half of #225 actually closes. Happy to do that as a small follow-up on top of this.
  2. Merge ordering vs #296 — my package-isolation PR moves Layout.tsx + SettingsModal.tsx into @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.

Copy link
Copy Markdown
Owner Author

@MartinCastroAlvarez MartinCastroAlvarez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security review (role: Security Specialist) — one blocking gap + a rebase.

The logout mechanism is wired correctly:

  • ✅ Goes through @dar/data (useApiClientclient.logout()), not @dar/api directly — respects the CLAUDE.md §7 boundary.
  • client.logout() issues POST logout/, hitting the CSRF-protected LogoutView (api/views/auth.py:182, http_method_names = ["post"]). A button with onClick, 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.

@MartinCastroAlvarez
Copy link
Copy Markdown
Owner Author

Closing as superseded by #363.

The logout goal here is valuable — main still has no logout UI anywhere, a genuine Django-admin parity gap — but this branch can't land as-is:

  1. Stale target path. It edits frontend/apps/web/src/components/SettingsModal.tsx, which the sidebar/settings refactor (now on main) moved into @dar/settings. This branch is 37 commits behind and would conflict on a deleted file.
  2. Missing cache purge. The logout did a bare window.location.reload(), which does not clear localStorage — so the previous session's cached dar:list:* / dar:detail:* / dar:registry:* rows survive at rest, readable in DevTools by the next user on a shared machine. Django's server-rendered admin has no such exposure.

#363 re-implements logout against the current @dar/settings layout and adds a @dar/data purgeLocalCache() that wipes the dar:* namespace on sign-out (preserving only dar:theme), closing that leak. It carries unit tests + passes typecheck/lint.

Thanks for the original push on this — the client.logout() shape and the Session-section UI in #363 follow your approach.

MartinCastroAlvarez added a commit that referenced this pull request May 27, 2026
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>
@MartinCastroAlvarez MartinCastroAlvarez deleted the feat/sidebar-header-logout branch May 27, 2026 15:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sidebar header: drop the role label, inline the user + cog, add Logout to the Settings modal

2 participants