From 2af3c481bb11a93a19514870a92246bd5aa58b42 Mon Sep 17 00:00:00 2001 From: Masahiko AMANO Date: Wed, 10 Jun 2026 14:55:12 +0300 Subject: [PATCH] fix(frontend): redirect to /login when the session can't be refreshed On a failed token refresh the client cleared the auth store and threw, but nothing navigated away, so an expired session left the user on a page that only showed errors. Redirect to /login when the refresh token is missing or rejected. Co-Authored-By: Claude Opus 4.8 --- frontend/src/lib/api/client.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/frontend/src/lib/api/client.ts b/frontend/src/lib/api/client.ts index 3fef352..a8d635a 100644 --- a/frontend/src/lib/api/client.ts +++ b/frontend/src/lib/api/client.ts @@ -1,8 +1,18 @@ import { get } from 'svelte/store'; +import { goto } from '$app/navigation'; +import { browser } from '$app/environment'; import { authStore } from '$lib/stores/auth'; const BASE = '/api/v1'; +/** Clear the session and bounce to the login screen. Called when the refresh + * token is missing or rejected, so an expired session doesn't strand the user + * on a page that only shows errors. */ +function endSession(): void { + authStore.set({ accessToken: null, refreshToken: null, user: null }); + if (browser) void goto('/login'); +} + export class ApiError extends Error { constructor( public readonly status: number, @@ -21,7 +31,7 @@ let refreshPromise: Promise | null = null; async function refreshTokens(): Promise { const { refreshToken } = get(authStore); if (!refreshToken) { - authStore.set({ accessToken: null, refreshToken: null, user: null }); + endSession(); throw new ApiError(401, 'unauthorized', 'Session expired'); } @@ -32,7 +42,7 @@ async function refreshTokens(): Promise { }); if (!res.ok) { - authStore.set({ accessToken: null, refreshToken: null, user: null }); + endSession(); throw new ApiError(401, 'unauthorized', 'Session expired'); }