120 lines
4.3 KiB
TypeScript
120 lines
4.3 KiB
TypeScript
import { redirect, isRedirect } from '@sveltejs/kit';
|
|
import type { PageServerLoad } from './$types';
|
|
import { setAccessTokenCookies } from '$lib/server/access-token-cookie';
|
|
import {
|
|
clearWorkspaceReturnPath,
|
|
getWorkspaceLoginUrl,
|
|
readWorkspaceReturnPath,
|
|
storeReturnPath,
|
|
} from '$lib/server/workspace-auth';
|
|
|
|
export const load: PageServerLoad = async ({ url, cookies, fetch }) => {
|
|
// Obtener el código y state de los query params
|
|
const code = url.searchParams.get('code');
|
|
const state = url.searchParams.get('state');
|
|
const errorParam = url.searchParams.get('error');
|
|
const errorDescription = url.searchParams.get('error_description');
|
|
|
|
if (errorParam) {
|
|
console.error('❌ [Callback Server] KC auth error:', errorParam, errorDescription);
|
|
// login_required means no KC session exists yet → send to Workspace login.
|
|
// Preserve the intended destination through the detour so /login can pick it up.
|
|
if (state) {
|
|
try {
|
|
const stateObj = JSON.parse(state);
|
|
const returnPath = stateObj.redirect_url;
|
|
if (returnPath && returnPath.startsWith('/') && returnPath !== '/login') {
|
|
storeReturnPath(cookies, returnPath);
|
|
}
|
|
} catch { /* ignore malformed state */ }
|
|
}
|
|
throw redirect(303, getWorkspaceLoginUrl(url.origin));
|
|
}
|
|
|
|
if (!code) {
|
|
console.error('❌ [Callback Server] No se recibió código de autorización');
|
|
throw redirect(303, getWorkspaceLoginUrl(url.origin));
|
|
}
|
|
|
|
try {
|
|
// Intercambiar código por tokens usando el backend de Keycloak
|
|
// En el servidor (SSR), usar KEYCLOAK_URL que apunta a http://keycloak:8080
|
|
// En producción o fuera de Docker, usar VITE_KEYCLOAK_URL como fallback
|
|
const KEYCLOAK_URL = process.env.KEYCLOAK_URL || process.env.VITE_KEYCLOAK_URL || 'http://localhost:8080';
|
|
const KEYCLOAK_REALM = process.env.KEYCLOAK_REALM || process.env.VITE_KEYCLOAK_REALM || 'master';
|
|
const KEYCLOAK_CLIENT_ID = process.env.KEYCLOAK_CLIENT_ID || process.env.VITE_KEYCLOAK_CLIENT_ID || 'anexo76-backend';
|
|
const KEYCLOAK_CLIENT_SECRET = process.env.KEYCLOAK_CLIENT_SECRET || '';
|
|
|
|
// La redirect_uri debe coincidir exactamente con la registrada en Keycloak
|
|
const redirectUri = `${url.origin}/auth/callback`;
|
|
|
|
const tokenEndpoint = `${KEYCLOAK_URL}/realms/${KEYCLOAK_REALM}/protocol/openid-connect/token`;
|
|
|
|
const body = new URLSearchParams({
|
|
grant_type: 'authorization_code',
|
|
code: code,
|
|
redirect_uri: redirectUri,
|
|
client_id: KEYCLOAK_CLIENT_ID,
|
|
...(KEYCLOAK_CLIENT_SECRET && { client_secret: KEYCLOAK_CLIENT_SECRET })
|
|
});
|
|
|
|
const tokenResponse = await fetch(tokenEndpoint, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded'
|
|
},
|
|
body: body.toString()
|
|
});
|
|
|
|
if (!tokenResponse.ok) {
|
|
const errorData = await tokenResponse.text();
|
|
console.error('❌ [Callback Server] Error al intercambiar código:', errorData);
|
|
throw new Error('Error al obtener tokens');
|
|
}
|
|
|
|
const tokens = await tokenResponse.json();
|
|
|
|
// Establecer las cookies en el servidor
|
|
// access_token → NO HttpOnly (el cliente JS lo usa para el header Authorization)
|
|
// refresh_token → HttpOnly (el JS nunca lo lee; el servidor lo gestiona)
|
|
const { isSecureContext } = await import('$lib/server/workspace-auth');
|
|
const isProduction = isSecureContext();
|
|
|
|
setAccessTokenCookies(cookies, tokens.access_token, {
|
|
secure: isProduction,
|
|
maxAge: 60 * 60 * 24 * 7 // 7 días
|
|
});
|
|
|
|
if (tokens.refresh_token) {
|
|
cookies.set('refresh_token', tokens.refresh_token, {
|
|
path: '/',
|
|
httpOnly: true, // *** HttpOnly: nunca expuesto a JS ***
|
|
secure: isProduction,
|
|
sameSite: 'lax',
|
|
maxAge: 60 * 60 * 24 * 30 // 30 días
|
|
});
|
|
}
|
|
|
|
// Obtener la URL de redirección del state o ir al dashboard
|
|
let redirectTo = readWorkspaceReturnPath(cookies, '/dashboard');
|
|
if (state) {
|
|
try {
|
|
const stateObj = JSON.parse(state);
|
|
redirectTo = stateObj.redirect_url || '/dashboard';
|
|
} catch (e) {
|
|
console.warn('⚠️ [Callback Server] No se pudo obtener redirect_url del state');
|
|
}
|
|
}
|
|
|
|
clearWorkspaceReturnPath(cookies);
|
|
|
|
// Redirigir a la página de destino
|
|
throw redirect(303, redirectTo);
|
|
|
|
} catch (err: any) {
|
|
if (isRedirect(err)) throw err;
|
|
console.error('❌ [Callback Server] Error procesando autenticación:', err);
|
|
throw redirect(303, getWorkspaceLoginUrl(url.origin));
|
|
}
|
|
};
|