Skip to content

feat(frontend): auto-enter guest mode with inline admin login - #7

Merged
mattDev0 merged 2 commits into
mainfrom
feat/auto-guest-login
Aug 31, 2026
Merged

feat(frontend): auto-enter guest mode with inline admin login#7
mattDev0 merged 2 commits into
mainfrom
feat/auto-guest-login

Conversation

@mattDev0

Copy link
Copy Markdown
Owner

Rebased onto current main and fixed before shipping. Verified locally against the production API.

The feature

Visitors land on the dashboard directly as a guest instead of hitting a login wall. Admin sign-in moves into a modal behind a "Login as Admin" button in the header; logging out returns to guest rather than the login screen.

What needed fixing first

The original commit could lock every visitor out of the dashboard — administrators included.

Unrecoverable spinner. A failed api.guestLogin() only did console.error, leaving token empty, so the app rendered a bare spinner indefinitely. The "Login as Admin" button lives in the header, which isn't rendered without a token — so there was no way back in at all.

Logout storm. handleLogout() is called from eight fetch error handlers on 401, and each fired its own api.guestLogin(). The dashboard runs five fetches in parallel on load and again every 30 seconds.

Rate limit collision. /api/auth/guest is capped at 5 requests/minute per IP by RateLimitFilter. The storm trips the limit, which fails acquisition, which produces the permanent spinner. The three bugs compound into a self-inflicted lockout.

The fix

All guest acquisition goes through one guarded entry point:

  • Single-flight — concurrent callers share the in-flight promise instead of racing. Also covers React StrictMode's double-invoke in dev.
  • 5s cooldown — a rejected guest token can't drive an acquire/reject loop against the limiter.
  • Real failure state — an explanatory card with Retry and Sign in as admin, and the login modal renders in that branch, so admin access survives a guest-service outage.

Also in here

  • Deletes components/auth/Login.jsx — superseded by AdminLoginModal, nothing imports it.
  • Vite dev proxy target is configurable via API_PROXY_TARGET, and presents an allowed Origin so the dev server can point at a deployed environment. The orchestrator's CORS allowlist doesn't include the Vite dev origin, which returns 403 → UNAUTHORIZED in the browser but passes for curl (no Origin header).
  • ESLint gets Node globals for config files; vite.config.js was failing no-undef on process.

Verified

eslint      0 errors (1 pre-existing exhaustive-deps warning)
vite build  OK

Exercised in a dev server proxied to production: guest auto-entry, admin login via modal, logout back to guest, and the failure card.

Worth a deliberate decision

The dashboard is now readable with no login at all — health, deployments, Docker container names and images. That's the intent of the feature, but it is a real change in exposure. Guests still can't act (C-1) and logs remain admin-only, so I think it's defensible for a portfolio piece — flagging it as a choice rather than a side effect.

Separately: every first-time visitor now consumes one of the 5/min guest tokens for their IP. Fine for normal traffic, but that limit is what will bite if the dashboard is ever linked somewhere busy.

Remove the full-screen login page gate so the dashboard loads
immediately in guest (read-only) mode. Add a 'Login as Admin'
button in the header that opens a compact login modal to upgrade
to ROLE_ADMIN. Logout now returns to guest mode instead of a
blank login screen.

No backend changes required — the existing /api/auth/guest
endpoint is already permitAll and stateless.
The auto-guest feature could lock every visitor out of the dashboard,
including administrators.

- A failed api.guestLogin() only logged to the console, leaving token
  empty, so the app rendered a bare spinner forever. The "Login as Admin"
  button lives in the header, which is not rendered without a token, so
  there was no way back in.
- handleLogout() is called from eight fetch error handlers on 401, and
  each fired its own api.guestLogin(). The dashboard runs five fetches in
  parallel on load and again every 30s.
- /api/auth/guest is rate limited to 5 requests per minute per IP, so the
  above trips the limit, which fails acquisition, which produces the
  permanent spinner.

Routes every guest acquisition through one guarded entry point:
concurrent callers share the in-flight request (which also covers React
StrictMode's double invoke), and a five second cooldown stops a rejected
token driving an acquire/reject loop. Failure now renders an explanatory
card with Retry and Sign in as admin, and the login modal renders in that
branch so admin access survives a guest-service outage.

Also removes components/auth/Login.jsx, which AdminLoginModal replaced and
nothing imports, makes the Vite dev proxy target configurable via
API_PROXY_TARGET, and gives ESLint Node globals for config files.
@mattDev0
mattDev0 merged commit 593a79b into main Aug 31, 2026
1 check passed
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