import { fail, redirect, type Actions } from '@sveltejs/kit'; import { env } from '$env/dynamic/private'; import { extractFaSessionId } from '$lib/shared/cookies'; import { getErrorMessage, type ErrorCode } from '$lib/shared/errors'; import type { PageServerLoad } from './$types'; export const load: PageServerLoad = ({ url }) => { return { registered: url.searchParams.get('registered') === '1', reason: url.searchParams.get('reason') }; }; export const actions = { login: async ({ request, cookies, fetch, url }) => { const data = await request.formData(); const email = data.get('email') as string; const password = data.get('password') as string; if (!email || !password) { return fail(400, { error: getErrorMessage('MISSING_CREDENTIALS') }); } const baseUrl = env.API_INTERNAL_URL || 'http://localhost:8080'; let response: Response; try { response = await fetch(`${baseUrl}/api/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }) }); } catch (e) { console.error('Login request failed', e); return fail(500, { error: getErrorMessage('INTERNAL_ERROR') }); } if (response.status === 401) { let code: ErrorCode = 'INVALID_CREDENTIALS'; try { const body = (await response.json()) as { code?: string }; if (body?.code) code = body.code as ErrorCode; } catch { // Body not JSON — fall through to INVALID_CREDENTIALS } return fail(401, { error: getErrorMessage(code) }); } if (response.status === 429) { return fail(429, { error: getErrorMessage('TOO_MANY_LOGIN_ATTEMPTS'), rateLimited: true }); } if (!response.ok) { return fail(response.status, { error: getErrorMessage('INTERNAL_ERROR') }); } // Extract fa_session id from the Set-Cookie header and re-emit to the browser. // Modern Node/Undici exposes getSetCookie(); fall back to a single header for older runtimes. const setCookieHeaders = typeof response.headers.getSetCookie === 'function' ? response.headers.getSetCookie() : response.headers.get('set-cookie') ? [response.headers.get('set-cookie')!] : []; const sessionId = extractFaSessionId(setCookieHeaders); if (!sessionId) { console.error('Backend returned 200 OK on login but no fa_session cookie'); return fail(500, { error: getErrorMessage('INTERNAL_ERROR') }); } const isHttps = url.protocol === 'https:'; cookies.set('fa_session', sessionId, { path: '/', httpOnly: true, sameSite: 'strict', secure: isHttps, maxAge: 60 * 60 * 8 // 8h — must match backend spring.session.timeout }); // Best-effort cleanup of the legacy Basic-auth cookie from older deployments. cookies.delete('auth_token', { path: '/' }); return redirect(303, '/'); } } satisfies Actions;