Merge pull request 'fix/logout-anexo' (#392) from fix/logout-anexo into development
Reviewed-on: ADUANASOFT/anexo76#392
This commit is contained in:
@@ -365,6 +365,8 @@ services:
|
||||
- KEYCLOAK_REALM=${KEYCLOAK_REALM:-master}
|
||||
- KEYCLOAK_CLIENT_ID=${KEYCLOAK_CLIENT_ID:-anexo76-backend}
|
||||
- KEYCLOAK_CLIENT_SECRET=${KEYCLOAK_CLIENT_SECRET:-dev-secret}
|
||||
# SvelteKit ORIGIN — evita que request.url.origin use la IP/puerto interno del contenedor
|
||||
- ORIGIN=${ORIGIN:-https://anexo76-dev.aduanasoft.com}
|
||||
ports:
|
||||
- "5111:5173"
|
||||
depends_on:
|
||||
|
||||
@@ -142,6 +142,8 @@ services:
|
||||
# CORS / CSRF — trusted origins para svelte.config.js
|
||||
- CORS_ORIGINS=${CORS_ORIGINS:-http://localhost:5173,http://localhost:3001}
|
||||
- TRUSTED_ORIGINS=${TRUSTED_ORIGINS:-}
|
||||
# SvelteKit ORIGIN — evita que request.url.origin use la IP/puerto interno del contenedor
|
||||
- ORIGIN=${ORIGIN:-http://localhost:5173}
|
||||
ports:
|
||||
- "5173:5173"
|
||||
depends_on:
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
getAccessTokenFromCookies,
|
||||
setAccessTokenCookies
|
||||
} from '$lib/server/access-token-cookie';
|
||||
import { isSecureContext } from '$lib/server/workspace-auth';
|
||||
|
||||
/**
|
||||
* Obtiene y normaliza la URL base de la API para llamadas desde el servidor
|
||||
@@ -57,7 +58,7 @@ export function setAuthTokens(
|
||||
refreshToken?: string
|
||||
) {
|
||||
setAccessTokenCookies(cookies, accessToken, {
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
secure: isSecureContext(),
|
||||
maxAge: 60 * 60 * 24 * 7 // 7 días
|
||||
});
|
||||
|
||||
@@ -66,7 +67,7 @@ export function setAuthTokens(
|
||||
path: '/',
|
||||
httpOnly: true, // *** HttpOnly: JS nunca lee el refresh_token ***
|
||||
sameSite: 'lax',
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
secure: isSecureContext(),
|
||||
maxAge: 60 * 60 * 24 * 30 // 30 días
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,6 +4,17 @@ import { redirect, type Cookies } from '@sveltejs/kit';
|
||||
const DEFAULT_WORKSPACE_BASE_URL = 'https://workspace.aduanasoft.com';
|
||||
const RETURN_PATH_COOKIE = 'workspace_return_path';
|
||||
|
||||
/**
|
||||
* Returns true only when the public-facing URL uses HTTPS.
|
||||
* Use this for cookie `secure` flag instead of NODE_ENV so that
|
||||
* cookies work on HTTP LAN dev environments (e.g. 192.168.x.x).
|
||||
*/
|
||||
export function isSecureContext(): boolean {
|
||||
const origin = (env.ORIGIN || process.env.ORIGIN || '').trim();
|
||||
if (origin) return origin.startsWith('https://');
|
||||
return process.env.NODE_ENV === 'production';
|
||||
}
|
||||
|
||||
function stripTrailingSlashes(value: string): string {
|
||||
return value.replace(/\/+$/, '');
|
||||
}
|
||||
@@ -51,9 +62,9 @@ export function getWorkspaceLoginUrl(
|
||||
if (options?.forPostLogout) {
|
||||
return `${workspaceBaseUrl}/login`;
|
||||
}
|
||||
// return_to points to /login so that after Workspace auth the browser lands on
|
||||
// /login, which immediately attempts a prompt=none KC auth.
|
||||
const loginUrl = `${systemBaseUrl}/login`;
|
||||
// return_to includes sso_verified=1 so the workspace preserves it when redirecting
|
||||
// back, regardless of what additional params the workspace appends.
|
||||
const loginUrl = `${systemBaseUrl}/login?sso_verified=1`;
|
||||
return `${workspaceBaseUrl}/login?return_to=${encodeURIComponent(loginUrl)}`;
|
||||
}
|
||||
|
||||
@@ -63,7 +74,7 @@ export function storeReturnPath(cookies: Cookies, path: string): void {
|
||||
path: '/',
|
||||
httpOnly: true,
|
||||
sameSite: 'lax',
|
||||
secure: env.NODE_ENV === 'production',
|
||||
secure: isSecureContext(),
|
||||
maxAge: 60 * 10
|
||||
});
|
||||
}
|
||||
@@ -100,7 +111,7 @@ export function storeWorkspaceReturnPath(cookies: Cookies, url: URL): string {
|
||||
path: '/',
|
||||
httpOnly: true,
|
||||
sameSite: 'lax',
|
||||
secure: env.NODE_ENV === 'production',
|
||||
secure: isSecureContext(),
|
||||
maxAge: 60 * 10
|
||||
});
|
||||
|
||||
@@ -147,10 +158,14 @@ export function redirectToKeycloakAuthorization(systemBaseUrl: string, redirectP
|
||||
|
||||
export function buildKeycloakLogoutUrl(systemBaseUrl: string): string {
|
||||
const keycloakBaseUrl = getPublicKeycloakBaseUrl();
|
||||
const workspaceLoginUrl = getWorkspaceLoginUrl(systemBaseUrl, { forPostLogout: true });
|
||||
// post_logout_redirect_uri must be a URI registered in the KC client.
|
||||
// The workspace login URL (workspace.aduanasoft.com/login) is NOT registered there.
|
||||
// Use a local /auth/post-logout route which IS covered by the app's registered wildcard,
|
||||
// then that route bounces to workspace login.
|
||||
const postLogoutRedirectUri = `${systemBaseUrl}/auth/post-logout`;
|
||||
const params = new URLSearchParams({
|
||||
client_id: getKeycloakClientId(),
|
||||
post_logout_redirect_uri: workspaceLoginUrl
|
||||
post_logout_redirect_uri: postLogoutRedirectUri
|
||||
});
|
||||
|
||||
return `${keycloakBaseUrl}/realms/${getKeycloakRealm()}/protocol/openid-connect/logout?${params.toString()}`;
|
||||
|
||||
@@ -40,7 +40,8 @@ export const POST = async ({ request, cookies, fetch }: RequestEvent) => {
|
||||
if (!hasAccess) {
|
||||
return json({ error: 'Access denied to tenant' }, { status: 403 });
|
||||
}
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
const { isSecureContext } = await import('$lib/server/workspace-auth');
|
||||
const isProduction = isSecureContext();
|
||||
cookies.set('sso_tenant_id', String(tenant_id), {
|
||||
path: '/',
|
||||
httpOnly: true,
|
||||
|
||||
@@ -77,7 +77,8 @@ export const load: PageServerLoad = async ({ url, cookies, fetch }) => {
|
||||
// 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 isProduction = process.env.NODE_ENV === 'production';
|
||||
const { isSecureContext } = await import('$lib/server/workspace-auth');
|
||||
const isProduction = isSecureContext();
|
||||
|
||||
setAccessTokenCookies(cookies, tokens.access_token, {
|
||||
secure: isProduction,
|
||||
|
||||
13
frontend/src/routes/auth/post-logout/+server.ts
Normal file
13
frontend/src/routes/auth/post-logout/+server.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getWorkspaceLoginUrl } from '$lib/server/workspace-auth';
|
||||
|
||||
/**
|
||||
* KC redirects here after completing the logout flow.
|
||||
* This URL is covered by the app's registered wildcard in KC (e.g. anexo76-dev.aduanasoft.com/*).
|
||||
* We then send the user to workspace login so it can apply myApps() launcher logic.
|
||||
*/
|
||||
export const GET: RequestHandler = async ({ request, url }) => {
|
||||
const systemBaseUrl = url.origin;
|
||||
throw redirect(303, getWorkspaceLoginUrl(systemBaseUrl, { forPostLogout: true }));
|
||||
};
|
||||
@@ -115,8 +115,9 @@ export const load: PageServerLoad = async ({ url, cookies }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
console.log('[SSO] NODE_ENV:', process.env.NODE_ENV, '→ isProduction:', isProduction);
|
||||
const { isSecureContext } = await import('$lib/server/workspace-auth');
|
||||
const isProduction = isSecureContext();
|
||||
console.log('[SSO] ORIGIN-based secure context:', isProduction);
|
||||
|
||||
// access_token — NO HttpOnly (Bearer desde JS); fragmentado si el JWT supera ~4KB
|
||||
setAccessTokenCookies(cookies, tokens.access_token, {
|
||||
|
||||
@@ -3,8 +3,8 @@ import type { RequestHandler } from './$types';
|
||||
import { clearAccessTokenCookies } from '$lib/server/access-token-cookie';
|
||||
import { buildKeycloakLogoutUrl, clearWorkspaceReturnPath } from '$lib/server/workspace-auth';
|
||||
|
||||
export const POST: RequestHandler = async ({ cookies, request }) => {
|
||||
const systemBaseUrl = new URL(request.url).origin;
|
||||
export const POST: RequestHandler = async ({ cookies, request, url }) => {
|
||||
const systemBaseUrl = url.origin;
|
||||
|
||||
// Eliminar todas las cookies de autenticación (access_token puede estar fragmentado)
|
||||
clearAccessTokenCookies(cookies);
|
||||
|
||||
Reference in New Issue
Block a user