Skip to content

fix: redirect to login on session expiry instead of generic error#4593

Open
ravindu0823 wants to merge 2 commits into
Dokploy:canaryfrom
ravindu0823:fix/session-expiry-redirect-to-login
Open

fix: redirect to login on session expiry instead of generic error#4593
ravindu0823 wants to merge 2 commits into
Dokploy:canaryfrom
ravindu0823:fix/session-expiry-redirect-to-login

Conversation

@ravindu0823

Copy link
Copy Markdown

What

When a user's session expires while they stay on a page (e.g. saving environment variables on a service), tRPC mutations/queries fail with a generic per-component "error" toast. There is no indication the session expired and no recovery path until the user manually refreshes, which then redirects to login.

Root cause

The frontend tRPC client (apps/dokploy/utils/api.ts) has no global handler for UNAUTHORIZED responses. The backend correctly throws UNAUTHORIZED tRPC errors on expired sessions, but those surface only through per-component .catch blocks as generic toasts.

Fix

  • New apps/dokploy/utils/auth-error.ts with pure, testable predicates:
    • isUnauthorizedError(error) — true when the error is a TRPCClientError whose data.code === "UNAUTHORIZED".
    • shouldRedirectOnAuthError(pathname, error) — false for public paths (/, /register*, /invitation*) or non-unauthorized errors; true otherwise.
    • handleAuthError(error) — thin side-effecting wrapper that redirects to / exactly once (module-level once-flag) when on a protected page.
  • apps/dokploy/utils/api.ts now passes a queryClientConfig with a global QueryCache and MutationCache (@tanstack/react-query) whose onError calls handleAuthError, so both queries and mutations trigger the redirect regardless of per-component .catch blocks.

Test

apps/dokploy/__test__/utils/auth-error.test.ts covers isUnauthorizedError (UNAUTHORIZED vs other codes / plain Error / null / undefined) and shouldRedirectOnAuthError (public paths return false even with an auth error; protected path + auth error returns true; protected path + non-auth error returns false). Confirmed RED first (module absent), then GREEN:

 ✓ __test__/utils/auth-error.test.ts (8 tests) 8ms
 Test Files  1 passed (1)
      Tests  8 passed (8)

Typecheck: pnpm --filter=dokploy run typecheck passes clean.

The handleAuthError window redirect is not unit-tested (no jsdom in this suite).

Fixes #4310

🤖 Generated with Claude Code

Expired sessions surfaced as generic per-component toasts because the
frontend tRPC client had no global handler for UNAUTHORIZED responses.

Add a pure, testable auth-error helper and wire a global QueryCache /
MutationCache onError handler into the tRPC client so any UNAUTHORIZED
response on a protected page redirects to login, regardless of
per-component .catch blocks.

Fixes Dokploy#4310

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ravindu0823 ravindu0823 requested a review from Siumauricio as a code owner June 9, 2026 09:08
@dosubot dosubot Bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Jun 9, 2026
Address review (blocker): the API reuses the UNAUTHORIZED code for two
different situations — a missing/expired session AND an authenticated user who
lacks a role or resource permission (146 such throws across the routers).
Redirecting on every UNAUTHORIZED would yank a logged-in user off their page
whenever they hit any permission-denied path.

Tag only the genuine no-session throws (protectedProcedure, and the no-session
branch of the cli/admin/enterprise procedures) with a SESSION_EXPIRED sentinel
message, and gate the client redirect on that sentinel instead of the bare
UNAUTHORIZED code. Permission/role denials now fall through to their normal
per-component toast with no navigation.

Also extend the public-path allowlist (/accept-invitation, /reset-password,
/send-reset-password) so an auth error on those pages never redirects.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:M This PR changes 30-99 lines, ignoring generated files. labels Jun 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Session Expiry Not Detected – Environment Variable Save Fails with Generic Error Instead of Redirecting to Login

1 participant