import { redirect, isRedirect } from '@sveltejs/kit'; import type { PageServerLoad } from './$types'; import { clearAuthTokens } from '$lib/server/api'; import { setAccessTokenCookies } from '$lib/server/access-token-cookie'; import { getWorkspaceLoginUrl, readWorkspaceReturnPath, clearWorkspaceReturnPath, storeReturnPath, redirectToKeycloakAuthorization, redirectToKeycloakLogin, getHubBackendUrl, isSecureContext, } from '$lib/server/workspace-auth'; export const load: PageServerLoad = async ({ cookies, url }) => { console.error('[LOGIN] url.pathname:', url.pathname, '| params:', Object.fromEntries(url.searchParams)); // Relay SSO: el Hub App Launcher redirige aquí con ?relay=UUID4. // Se canjea server-to-server (endpoint público, sin Bearer) por access_token + refresh_token. const relayToken = url.searchParams.get('relay'); if (relayToken) { try { const hubBackendUrl = getHubBackendUrl(); const exchangeRes = await fetch(`${hubBackendUrl}/api/v1/auth/sso-exchange`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ relay_token: relayToken }) }); if (exchangeRes.ok) { const data = await exchangeRes.json(); const isProduction = isSecureContext(); setAccessTokenCookies(cookies, data.access_token, { secure: isProduction, maxAge: 60 * 60 * 24 * 7 }); if (data.refresh_token) { cookies.set('refresh_token', data.refresh_token, { path: '/', httpOnly: true, secure: isProduction, sameSite: 'lax', maxAge: 60 * 60 * 24 * 30 }); } const redirectTo = url.searchParams.get('redirect') || readWorkspaceReturnPath(cookies, '/dashboard'); clearWorkspaceReturnPath(cookies); throw redirect(303, redirectTo); } // relay inválido/expirado (410) o Hub no disponible → continúa al flujo normal } catch (err) { if (isRedirect(err)) throw err; // silencio: cae al flujo normal de login } } clearAuthTokens(cookies); // Workspace redirige de vuelta aquí con ?sso_verified=1 después de que el usuario // se autenticó en Workspace (que usa el mismo Keycloak central). // En ese momento la sesión KC ya existe en el browser → prompt=none funciona sin // mostrar ninguna pantalla de login. if (url.searchParams.get('sso_verified') === '1') { const existingReturnPath = readWorkspaceReturnPath(cookies, ''); const intendedPath = existingReturnPath && existingReturnPath !== '/login' ? existingReturnPath : (url.searchParams.get('redirect') || '/dashboard'); storeReturnPath(cookies, intendedPath); redirectToKeycloakAuthorization(url.origin, intendedPath); } // El Hub devolvió ?redirect= sin sso_verified=1. // Ir directo a Keycloak SIN prompt=none para romper el loop Hub↔login: // - KC tiene sesión activa → devuelve código al callback → éxito. // - KC no tiene sesión → muestra el form de login → callback → éxito. const redirectParam = url.searchParams.get('redirect'); if (redirectParam) { const existingReturnPath = readWorkspaceReturnPath(cookies, ''); const intendedPath = existingReturnPath && existingReturnPath !== '/login' ? existingReturnPath : redirectParam; storeReturnPath(cookies, intendedPath); redirectToKeycloakLogin(url.origin, intendedPath); } // Primera visita sin ningún parámetro → guardar destino y mandar al workspace. storeReturnPath(cookies, '/dashboard'); throw redirect(303, getWorkspaceLoginUrl(url.origin)); };