Skip to content

fix(web): confirm session before rendering and recover concurrent 401s - #152

Merged
thedancingdeveloper merged 1 commit into
mainfrom
fix/web-auth-session-bootstrap
Sep 28, 2026
Merged

thedancingdeveloper merged 1 commit into
mainfrom
fix/web-auth-session-bootstrap

Conversation

@thedancingdeveloper

Copy link
Copy Markdown
Collaborator

Summary

Fixes two UI bugs with one root cause: the SPA trusted any access token in localStorage, but the server keeps tokens in memory (15 min TTL, wiped on restart).

1. Slow initial table population. After token expiry every startup request (/queue, /status, /history, /config/servers, /config/categories) 401s together. Only the first refreshed and retried; the rest waited for their next poll (2s/5s) or never retried (servers, categories).

  • All concurrent 401s now share one in-flight refresh, then retry.
  • A request sent with an already-rotated token retries with the current one without spending another single-use refresh token.
  • The interceptor attaches the current token when a request has none.
  • Token expiry is persisted; an expired token is refreshed up front on load.

2. Unauthenticated user briefly sees content before the login bounce. The guard passed on a stale token, so the chrome and Downloads page rendered until the API rejected it; chrome also tracked auth via a 2s localStorage poll.

  • authGuard confirms the session with the server once per page load (refresh if expired, else probe /api/status) and redirects via UrlTree if rejected.
  • Chrome follows a reactive session signal, and /login + /welcome stay full-screen.
  • The login page uses the same check, so a stale token no longer leaves it stuck on "Checking status…".

Trade-off: a cold page load waits one extra round trip before the first protected view renders.

Testing

  • npm test -- --watch=false: 100 passed (new regression tests for shared refresh, concurrent 401 recovery, non-auth retry failure keeping the session, guard redirect, stale-token clearing, bare routes)
  • npm run build -- --configuration=production: OK (existing 128-byte style budget warning on queue-view, untouched)
  • ./ci/run e2e: 90 passed. An earlier run caught a first-boot /welcome → /downloads race, which the bare-route change fixes.

🤖 Generated with Claude Code

Two symptoms, one root cause: the SPA trusted any access token in
localStorage, but the server keeps tokens in memory (15 min TTL, wiped on
restart).

Slow table population: after token expiry every startup request 401s, but
only the first refreshed and retried; the rest waited for their next poll
(2s queue, 5s history) or never retried (servers, categories). All 401s now
share one in-flight refresh and retry; a request sent with an already
rotated token retries without spending another single-use refresh token.
Token expiry is persisted so an expired token is refreshed up front.

Content flash before login bounce: the guard passed on a stale token, so
the chrome and Downloads page rendered until the API rejected it. The guard
now confirms the session with the server once per page load, and chrome
follows a reactive session signal (plus route: login/welcome stay
full-screen) instead of a 2s localStorage poll. The login page uses the same
check so a stale token no longer leaves it stuck on "Checking status...".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@thedancingdeveloper
thedancingdeveloper merged commit 51d82b6 into main Sep 28, 2026
11 checks passed
@thedancingdeveloper
thedancingdeveloper deleted the fix/web-auth-session-bootstrap branch September 28, 2026 23:42
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.

1 participant