diff --git a/docker-compose.yml b/docker-compose.yml index 98c1f8f2..7eccf7ec 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -113,6 +113,9 @@ services: - NODE_ENV=${NODE_ENV:-development} - VITE_API_URL=${VITE_API_URL:-http://localhost:8000/api/} - INTERNAL_API_URL=${INTERNAL_API_URL:-http://backend:8000/api/} + - VITE_KEYCLOAK_URL=${VITE_KEYCLOAK_URL:-http://localhost:8085/kcauth} + - VITE_KEYCLOAK_REALM=${VITE_KEYCLOAK_REALM:-master} + - VITE_KEYCLOAK_CLIENT_ID=${VITE_KEYCLOAK_CLIENT_ID:-anexo76-frontend} # Hub — URL pública para el browser y URL interna para server-side - VITE_HUB_URL=${VITE_HUB_URL:-http://localhost:8001} - INTERNAL_HUB_URL=${INTERNAL_HUB_URL:-http://host.docker.internal:8001} diff --git a/frontend/src/lib/auth.ts b/frontend/src/lib/auth.ts index f35153e4..9eb6352c 100644 --- a/frontend/src/lib/auth.ts +++ b/frontend/src/lib/auth.ts @@ -370,18 +370,14 @@ export const logout = async () => { deleteCookie('access_token'); // La cookie HttpOnly del refresh_token la limpia el servidor - // Logout de Keycloak JS si estaba autenticado con SSO - if (keycloakInstance?.authenticated) { + // Logout unificado (SSO y password): POST al logout route del servidor. + // Evita redirección visible al endpoint de Keycloak. + if (keycloakInstance) { try { - await fetch('/logout', { method: 'POST' }); + keycloakInstance.clearToken(); } catch {} - await keycloakInstance.logout({ - redirectUri: window.location.origin + '/login' - }); - return; } - // Para login con password: POST al logout route del servidor const form = document.createElement('form'); form.method = 'POST'; form.action = '/logout'; diff --git a/frontend/src/routes/logout/+server.ts b/frontend/src/routes/logout/+server.ts index eed7419a..9c1b10ed 100644 --- a/frontend/src/routes/logout/+server.ts +++ b/frontend/src/routes/logout/+server.ts @@ -4,6 +4,8 @@ import type { RequestHandler } from './$types'; export const POST: RequestHandler = async ({ cookies, request }) => { const refreshToken = cookies.get('refresh_token'); + const appOrigin = new URL(request.url).origin; + const loginUrl = `${appOrigin}/login`; // Eliminar todas las cookies de autenticación cookies.delete('access_token', { path: '/' }); @@ -12,38 +14,29 @@ export const POST: RequestHandler = async ({ cookies, request }) => { cookies.delete('sso_tenant_id', { path: '/' }); cookies.delete('sso_tenant_pub', { path: '/' }); - // Llamar al Hub para revocar el refresh token y obtener la URL de logout KC. - // Si lo logramos, redirigimos al browser a través del endpoint KC logout para - // que Keycloak elimine su cookie de sesión SSO (evita auto-login silencioso). + // Llamar al Hub para revocar el refresh token. + // La navegación final siempre debe volver al login local de anexo76 + // sin redirigir al endpoint de logout de Keycloak. if (refreshToken) { try { const hubUrl = (env.INTERNAL_HUB_URL || env.HUB_URL || 'http://localhost:8001').replace(/\/+$/, ''); - // Detectar el origen del request para usar como post_logout_redirect_uri - const origin = request.headers.get('origin') || request.headers.get('referer')?.replace(/\/$/, '') || ''; - const postLogoutUri = origin ? `${origin}/login` : '/login'; const res = await fetch(`${hubUrl}/api/v1/auth/logout`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refresh_token: refreshToken, - post_logout_redirect_uri: postLogoutUri, + post_logout_redirect_uri: loginUrl, }), }); if (res.ok) { - const data = await res.json().catch(() => ({})); - if (data.kc_logout_url) { - // Redirigir el browser al endpoint KC logout para limpiar la sesión SSO - throw redirect(303, data.kc_logout_url); - } + await res.json().catch(() => ({})); } } catch (err: any) { - // Si es un redirect de SvelteKit, relanzar - if (err?.status && err?.location) throw err; - // Si falla, caer al login local + // Si falla la llamada al Hub, caer al login local } } - throw redirect(303, '/login'); + throw redirect(303, loginUrl); };