Skip to content

Harden dashboard session handling and de-duplicate the demo route tree - #745

Merged
Jambox11 merged 1 commit into
mux-labs:stagingfrom
nasarajoe:fix/dashboard-session-guard-and-demo-dedup
Aug 30, 2026
Merged

Harden dashboard session handling and de-duplicate the demo route tree#745
Jambox11 merged 1 commit into
mux-labs:stagingfrom
nasarajoe:fix/dashboard-session-guard-and-demo-dedup

Conversation

@nasarajoe

Copy link
Copy Markdown
Contributor

What & why

Four related fixes to the developer-dashboard auth/session path and the parallel /demo/dashboard tree. Without these, a stale or half-rehydrated session could render the real dashboard, wallet requests could go out unauthenticated on a token refresh, and the duplicate demo route tree could silently drift from production.

#624 — Use useSessionGuard on dashboard pages

The documented client-side stale-session guard (src/hooks/useSessionGuard.ts) was never imported — AuthGuard carried a second, parallel copy of the same redirect logic.

  • AuthGuard now delegates its redirect to useSessionGuard(). DashboardLayout wraps every real /dashboard/* route in AuthGuard (requireAuth defaults to true; /demo/dashboard/* passes requireAuth={false}), so the guard now runs across the whole production dashboard tree, with the demo tree as the explicit opt-out.
  • New src/hooks/__tests__/useSessionGuard.test.ts (loading / authenticated / unauthenticated / custom redirect target).
  • AuthGuard.test.tsx strengthened to assert the /login?callbackUrl=… redirect fires through the hook.

#629 — Align useWallets token storage key with AuthContext

getStoredAccessToken() already reads the shared session store (loadSession()sessionStorage["mux-auth-session"]), which is exactly where AuthContext.signIn persists the bearer block via saveSession. Added an explicit round-trip regression test that drives the same createSession → saveSession call AuthContext makes and asserts useWallets sends that token — and keeps the existing "legacy localStorage key is ignored" guard.

#630 — Retry fetchWithAuth after /api/auth/refresh on 401

fetchWithAuth previously cleared the session and redirected on the first 401, unlike src/lib/api.js which refreshes and retries.

  • On a 401, fetchWithAuth now POSTs to /api/auth/refresh (refresh token pulled from the shared session store), persists any rotated accessToken, and retries the original request once with the new bearer token.
  • Only if the refresh call fails — or the retried request is still a 401 — does it clear the client session (sessionStorage user record + bearer session + mux_auth_session cookie) and window.location.replace to /login?callbackUrl=….
  • A backend that re-authenticates purely via the refreshed HttpOnly cookie (no token in the body) is still treated as a successful refresh.
  • fetchWithAuth.test.ts rewritten: passthrough, refresh + retry with rotated token, cookie-only refresh, refresh-fails → redirect, retry-still-401 → redirect.

#631 — De-duplicate /demo/dashboard vs /dashboard

  • src/app/demo/dashboard/api-keys/page.tsx is now a thin export { default } from "@/app/dashboard/api-keys/page" — it was a near-identical copy that had drifted to a stale "Settings" page header.
  • New src/app/demo/dashboard/__tests__/route-parity.test.ts enumerates both route trees and fails if a route exists on only one side, unless it is in a reason-tagged allowlist (PROD_ONLY: api-keys/[id]/usage, settings/team; DEMO_ONLY: users). Adding a route to only one tree now requires a conscious, reviewed decision.

Production vs demo/mock split

Explicit and unchanged in spirit: requireAuth on DashboardLayout gates the real tree; /demo/* opts out and sources mock data (useWallets({ demo: true }), /api/demo/*). No new secrets, no NEXT_PUBLIC_* token, no cookie-only auth without server verification. /api/auth/refresh still returns 503 in a production build with no backend.

Docs

  • README.mdfetchWithAuth refresh/retry contract, useSessionGuard wiring, demo/prod route split.
  • src/docs/API_Hooks.md — same, plus a new "Stale-session guard (Use useSessionGuard on dashboard pages #624)" section.
  • No environment variables added or changed.

Tests

New/changed test files pass (pnpm test <files>): useSessionGuard, AuthGuard, fetchWithAuth, useWallets, route-parity — 33 tests green. Runnable via the existing pnpm test script.

Note: several unrelated test files in the suite fail identically before and after this branch (pre-existing).

Closes #624
Closes #629
Closes #630
Closes #631

@drips-wave

drips-wave Bot commented Aug 29, 2026

Copy link
Copy Markdown

@nasarajoe Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@Jambox11
Jambox11 merged commit ffa84e7 into mux-labs:staging Aug 30, 2026
Jambox11 pushed a commit that referenced this pull request Aug 31, 2026
Expands the refresh-route test suite from 7 to 15 tests, covering gaps
identified during the #745 audit:

- Cookie secure flag when NODE_ENV=production
- Caller's cookie header forwarded to upstream backend
- Upstream auth headers (x-api-key, x-api-secret) propagated
- Request body forwarded to backend
- extractSessionToken handling all three response keys (token,
  accessToken, sessionToken)
- No cookie set when backend response has no token field
- Descriptive error message present in 503 response

Also adds src/app/api/** to the default vitest coverage include list so
route handler tests count toward reported coverage numbers.

🤖 Generated with Codebuff

Co-authored-by: Codebuff <noreply@codebuff.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants