From 92c7d8f92e99c10d77e10feb90099435cfe93996 Mon Sep 17 00:00:00 2001 From: Marcel Raddatz Date: Thu, 2 Apr 2026 13:56:49 +0200 Subject: [PATCH] feat(auth): preserve redirect URL when redirecting to /login Appends ?redirect= with the original pathname so the login page can redirect back after successful authentication. Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/hooks.server.test.ts | 11 +++++------ frontend/src/hooks.server.ts | 9 +++++++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/frontend/src/hooks.server.test.ts b/frontend/src/hooks.server.test.ts index a57bcc3..3601f49 100644 --- a/frontend/src/hooks.server.test.ts +++ b/frontend/src/hooks.server.test.ts @@ -57,15 +57,14 @@ describe('auth guard (hooks.server.ts handle)', () => { } ); - it('redirects unauthenticated requests on protected routes', async () => { - const { event, resolve } = createEvent('/planner'); + it('redirects unauthenticated requests to /login with redirect param', async () => { + const { event, resolve } = createEvent('/recipes/abc'); try { await handle({ event, resolve }); - // If using SvelteKit redirect, it throws expect.unreachable(); } catch (e: any) { expect(e.status).toBe(302); - expect(e.location).toBe('/login'); + expect(e.location).toBe('/login?redirect=%2Frecipes%2Fabc'); } }); @@ -99,7 +98,7 @@ describe('auth guard (hooks.server.ts handle)', () => { expect(resolve).toHaveBeenCalledWith(event); }); - it('redirects to /login when session validation fails', async () => { + it('redirects to /login with redirect param when session validation fails', async () => { mockGet.mockResolvedValue({ data: undefined, error: { status: 401 } }); const { event, resolve } = createEvent('/planner', 'bad-session'); @@ -108,7 +107,7 @@ describe('auth guard (hooks.server.ts handle)', () => { expect.unreachable(); } catch (e: any) { expect(e.status).toBe(302); - expect(e.location).toBe('/login'); + expect(e.location).toBe('/login?redirect=%2Fplanner'); } }); }); diff --git a/frontend/src/hooks.server.ts b/frontend/src/hooks.server.ts index 6579fbc..9150df4 100644 --- a/frontend/src/hooks.server.ts +++ b/frontend/src/hooks.server.ts @@ -13,6 +13,11 @@ function isPublicRoute(pathname: string): boolean { return PUBLIC_ROUTES.some((route) => pathname === route || pathname.startsWith(route + '/')); } +function loginRedirect(pathname: string): never { + const target = '/login?redirect=' + encodeURIComponent(pathname); + redirect(302, target); +} + export const handle: Handle = async ({ event, resolve }) => { if (isPublicRoute(event.url.pathname)) { return resolve(event); @@ -20,14 +25,14 @@ export const handle: Handle = async ({ event, resolve }) => { const sessionCookie = event.cookies.get('session'); if (!sessionCookie) { - redirect(302, '/login'); + loginRedirect(event.url.pathname); } const api = apiClient(event.fetch); const { data, error } = await api.GET('/v1/auth/me'); if (error || !data?.data) { - redirect(302, '/login'); + loginRedirect(event.url.pathname); } const user = data.data;