100 lines
3.5 KiB
TypeScript
100 lines
3.5 KiB
TypeScript
import { redirect } from '@sveltejs/kit';
|
|
import type { PageServerLoad } from './$types';
|
|
|
|
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] Error en autenticación:', errorParam, errorDescription);
|
|
throw redirect(303, `/login?error=${encodeURIComponent(errorDescription || errorParam)}`);
|
|
}
|
|
|
|
if (!code) {
|
|
console.error('❌ [Callback Server] No se recibió código de autorización');
|
|
throw redirect(303, '/login?error=No se recibió código de autorización');
|
|
}
|
|
|
|
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 (esto es lo importante)
|
|
// Las cookies deben ser HttpOnly y Secure en producción
|
|
const isProduction = process.env.NODE_ENV === 'production';
|
|
|
|
cookies.set('access_token', tokens.access_token, {
|
|
path: '/',
|
|
httpOnly: true,
|
|
secure: isProduction,
|
|
sameSite: 'lax',
|
|
maxAge: 60 * 60 * 24 * 7 // 7 días
|
|
});
|
|
|
|
if (tokens.refresh_token) {
|
|
cookies.set('refresh_token', tokens.refresh_token, {
|
|
path: '/',
|
|
httpOnly: true,
|
|
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 = '/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');
|
|
}
|
|
}
|
|
|
|
// Redirigir a la página de destino
|
|
throw redirect(303, redirectTo);
|
|
|
|
} catch (err: any) {
|
|
console.error('❌ [Callback Server] Error procesando autenticación:', err);
|
|
throw redirect(303, `/login?error=${encodeURIComponent(err.message || 'Error procesando autenticación')}`);
|
|
}
|
|
};
|