From 0d360d74dfbdae228239096a63ff0756a56259d0 Mon Sep 17 00:00:00 2001 From: Roland Tannous Date: Sun, 15 Feb 2026 08:27:35 +0000 Subject: [PATCH] fix: auto-redirect to signup/login when auth tokens are stale --- studio/frontend/src/features/auth/api.ts | 26 ++++++++++++++++++- .../hooks/use-training-runtime-lifecycle.ts | 7 +++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/studio/frontend/src/features/auth/api.ts b/studio/frontend/src/features/auth/api.ts index 6705caa5e4..f42a544757 100644 --- a/studio/frontend/src/features/auth/api.ts +++ b/studio/frontend/src/features/auth/api.ts @@ -10,6 +10,26 @@ type RefreshResponse = { refresh_token: string; }; +let isRedirecting = false; + +async function redirectToAuth(): Promise { + if (isRedirecting) return; + isRedirecting = true; + + let target = "/login"; + try { + const res = await fetch("/api/auth/status"); + if (res.ok) { + const data = (await res.json()) as { initialized: boolean }; + if (!data.initialized) target = "/signup"; + } + } catch { + // Fall through to /login on error + } + + window.location.href = target; +} + export async function refreshSession(): Promise { const refreshToken = getRefreshToken(); if (!refreshToken) return false; @@ -48,7 +68,11 @@ export async function authFetch( if (response.status !== 401) return response; const refreshed = await refreshSession(); - if (!refreshed) return response; + if (!refreshed) { + clearAuthTokens(); + void redirectToAuth(); + return response; + } const retryHeaders = new Headers(init?.headers); const newToken = getAuthToken(); diff --git a/studio/frontend/src/features/training/hooks/use-training-runtime-lifecycle.ts b/studio/frontend/src/features/training/hooks/use-training-runtime-lifecycle.ts index 8d0eb9c756..4bf329a33e 100644 --- a/studio/frontend/src/features/training/hooks/use-training-runtime-lifecycle.ts +++ b/studio/frontend/src/features/training/hooks/use-training-runtime-lifecycle.ts @@ -1,3 +1,4 @@ +import { hasAuthToken } from "@/features/auth"; import { useEffect } from "react"; import { getTrainingMetrics, @@ -48,6 +49,7 @@ export function useTrainingRuntimeLifecycle(): void { }; const pollMetrics = async () => { + if (!hasAuthToken()) return; const gen = runtimeStore.getState().resetGeneration; try { const metrics = await getTrainingMetrics(); @@ -56,13 +58,14 @@ export function useTrainingRuntimeLifecycle(): void { } runtimeStore.getState().applyMetrics(metrics); } catch (error) { - if (!isAbortError(error) && !disposed) { + if (!isAbortError(error) && !disposed && hasAuthToken()) { runtimeStore.getState().setSseConnected(false); } } }; const pollStatus = async () => { + if (!hasAuthToken()) return; const gen = runtimeStore.getState().resetGeneration; try { const status = await getTrainingStatus(); @@ -79,7 +82,7 @@ export function useTrainingRuntimeLifecycle(): void { stopStream(); } } catch (error) { - if (!isAbortError(error) && !disposed) { + if (!isAbortError(error) && !disposed && hasAuthToken()) { runtimeStore.getState().setSseConnected(false); } }