From f0a9d37808fc12d9b950ebd0416abba8cd1db90e Mon Sep 17 00:00:00 2001 From: Ali Fotouhi Date: Thu, 23 Jul 2026 12:28:56 +0330 Subject: [PATCH 1/2] fix: redirect authenticated users away from /login The login page unconditionally cleared the auth token and query cache on every mount, forcing a re-login even when a valid session already existed. It now checks the stored token with getCurrentAdmin() (the same check the dashboard route loader uses) and redirects to the dashboard if it's still valid, only clearing the session when it's missing or invalid. The check is skipped for the Telegram MiniApp flow, which manages its own auth token independently. Co-Authored-By: Claude Sonnet 5 --- dashboard/src/pages/login.tsx | 45 ++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/dashboard/src/pages/login.tsx b/dashboard/src/pages/login.tsx index 658d70cc8..8fcd96254 100644 --- a/dashboard/src/pages/login.tsx +++ b/dashboard/src/pages/login.tsx @@ -8,9 +8,9 @@ import { Input } from '@/components/ui/input' import { LoaderButton } from '@/components/ui/loader-button' import { PasswordInput } from '@/components/ui/password-input' import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs' -import { useAdminMiniAppToken, useAdminToken, useCreateOwner, useDeleteOwner, useResetOwnerPassword, useUpgradeOwner } from '@/service/api' +import { getCurrentAdmin, useAdminMiniAppToken, useAdminToken, useCreateOwner, useDeleteOwner, useResetOwnerPassword, useUpgradeOwner } from '@/service/api' import { $fetch } from '@/service/http' -import { removeAuthToken, setAuthToken } from '@/utils/authStorage' +import { getAuthToken, removeAuthToken, setAuthToken } from '@/utils/authStorage' import { queryClient } from '@/utils/query-client' import { zodResolver } from '@hookform/resolvers/zod' import { retrieveRawInitData } from '@telegram-apps/sdk' @@ -129,18 +129,6 @@ export const Login: FC = () => { resolver: zodResolver(schema), }) - useEffect(() => { - // Cancel all ongoing queries first to stop any in-flight requests - queryClient.cancelQueries() - // Remove the auth token - removeAuthToken() - // Clear all React Query cache to ensure fresh state after logout - queryClient.clear() - if (location.pathname !== '/login') { - navigate('/login', { replace: true }) - } - }, [location.pathname, navigate]) - let isTelegram = false let initDataRaw = '' try { @@ -151,6 +139,35 @@ export const Login: FC = () => { initDataRaw = '' } + useEffect(() => { + if (location.pathname !== '/login') { + navigate('/login', { replace: true }) + } + }, [location.pathname, navigate]) + + useEffect(() => { + // The Telegram MiniApp flow below owns its own auth exchange - let it run + // undisturbed rather than racing it over the stored token. + if (isTelegram) return + // No token: nothing to verify, show the login form + if (!getAuthToken()) return + + // A token exists - check whether it's still valid before deciding + // whether to redirect to the dashboard or drop the stale session + getCurrentAdmin() + .then(() => { + navigate('/', { replace: true }) + }) + .catch(() => { + // Cancel all ongoing queries first to stop any in-flight requests + queryClient.cancelQueries() + // Remove the auth token + removeAuthToken() + // Clear all React Query cache to ensure fresh state after logout + queryClient.clear() + }) + }, [navigate, isTelegram]) + const { mutate: login, isPending: loading, From d91f7a8b1406696f86d0b44a393a204274d0fad6 Mon Sep 17 00:00:00 2001 From: Ali Fotouhi Date: Sun, 26 Jul 2026 10:34:57 +0330 Subject: [PATCH 2/2] fix: only drop session on confirmed auth failure, avoid stale-response races getCurrentAdmin() rejected on any error (network blips, 5xx), which cleared a possibly-valid token on transient failures. Now only 401/403 responses clear the stored session. Also cancel the request on unmount via AbortController and re-check the stored token before acting, so a stale response can't clobber a token set by a concurrent login. Co-Authored-By: Claude Sonnet 5 --- dashboard/src/pages/login.tsx | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/dashboard/src/pages/login.tsx b/dashboard/src/pages/login.tsx index 8fcd96254..6c1e1da98 100644 --- a/dashboard/src/pages/login.tsx +++ b/dashboard/src/pages/login.tsx @@ -149,16 +149,27 @@ export const Login: FC = () => { // The Telegram MiniApp flow below owns its own auth exchange - let it run // undisturbed rather than racing it over the stored token. if (isTelegram) return + + const token = getAuthToken() // No token: nothing to verify, show the login form - if (!getAuthToken()) return + if (!token) return + + const controller = new AbortController() // A token exists - check whether it's still valid before deciding // whether to redirect to the dashboard or drop the stale session - getCurrentAdmin() + getCurrentAdmin(controller.signal) .then(() => { navigate('/', { replace: true }) }) - .catch(() => { + .catch((error: any) => { + if (error?.name === 'AbortError') return + // Another flow (e.g. a manual login) already replaced the token - don't clobber it + if (getAuthToken() !== token) return + // Only drop the session on a confirmed auth failure; transient/network + // errors shouldn't log out an otherwise-valid session + if (error?.status !== 401 && error?.status !== 403) return + // Cancel all ongoing queries first to stop any in-flight requests queryClient.cancelQueries() // Remove the auth token @@ -166,6 +177,8 @@ export const Login: FC = () => { // Clear all React Query cache to ensure fresh state after logout queryClient.clear() }) + + return () => controller.abort() }, [navigate, isTelegram]) const {