feat: Implement dynamic backend URL configuration and streamline logout process by centralizing audit logging.

This commit is contained in:
Galindo97
2026-02-11 16:22:03 -06:00
parent dfd9c06b06
commit 85ca22e574
9 changed files with 130 additions and 162 deletions

View File

@@ -367,10 +367,9 @@ export const logout = async () => {
if (!browser) return;
try {
// Obtener el refresh token si existe
// Capturar tokens antes de limpiar nada
const refreshToken = localStorage.getItem('refresh_token');
const accessToken = localStorage.getItem('access_token');
// Limpiar store de compañías
try {
@@ -389,6 +388,15 @@ export const logout = async () => {
// Si hay instancia de Keycloak, hacer logout de Keycloak
if (keycloakInstance?.authenticated) {
// Primero notificamos al servidor para limpieza de cookies (SvelteKit)
try {
await fetch('/logout', {
method: 'POST'
});
} catch (e) {
console.error("Error calling server logout:", e);
}
await keycloakInstance.logout({
redirectUri: window.location.origin + '/login'
});
@@ -400,6 +408,9 @@ export const logout = async () => {
const form = document.createElement('form');
form.method = 'POST';
form.action = '/logout';
document.body.appendChild(form);
form.submit();

View File

@@ -1,59 +1,48 @@
<script lang="ts">
import { onMount } from 'svelte';
import { companyStore } from '$lib/stores/company.svelte';
import { getExchangeRates } from '$lib/api/dashboard/a76/general_catalogs/exchange-rate';
import CreateEditDialog from './create-edit-dialog.svelte';
import { onMount } from 'svelte';
import { companyStore } from '$lib/stores/company.svelte';
import { getExchangeRates } from '$lib/api/dashboard/a76/general_catalogs/exchange-rate';
import CreateEditDialog from './create-edit-dialog.svelte';
let open = $state(false);
let checked = $state(false);
let open = $state(false);
let checked = $state(false);
async function checkExchangeRate() {
console.log('[ExchangeRateGuard] Checking...', companyStore.activeCompany);
if (!companyStore.activeCompany?.id) {
console.log('[ExchangeRateGuard] No active company');
return;
}
async function checkExchangeRate() {
if (!companyStore.activeCompany?.id) {
return;
}
// Use local date instead of UTC
const today = new Date().toLocaleDateString('fr-CA'); // YYYY-MM-DD
console.log('[ExchangeRateGuard] Date:', today);
try {
const response = await getExchangeRates(companyStore.activeCompany.id, {
date: today,
page_size: 1
});
console.log('[ExchangeRateGuard] Response (stringified):', JSON.stringify(response, null, 2));
// Use local date instead of UTC
const today = new Date().toLocaleDateString('fr-CA'); // YYYY-MM-DD
// api.get returns { data: ..., status: ... } and types now reflect that
const items = response.data?.items || [];
if (items.length === 0) {
console.log('[ExchangeRateGuard] No rate found, opening modal');
open = true;
}
} catch (error) {
console.error('[ExchangeRateGuard] Error checking exchange rate:', error);
} finally {
checked = true;
}
}
try {
const response = await getExchangeRates(companyStore.activeCompany.id, {
date: today,
page_size: 1
});
$effect(() => {
if (companyStore.activeCompany?.id && !checked) {
checkExchangeRate();
}
});
// api.get returns { data: ..., status: ... } and types now reflect that
const items = response.data?.items || [];
function handleSuccess() {
console.log('Exchange rate created successfully via guard');
checked = true;
}
if (items.length === 0) {
open = true;
}
} catch (error) {
console.error('[ExchangeRateGuard] Error checking exchange rate:', error);
} finally {
checked = true;
}
}
$effect(() => {
if (companyStore.activeCompany?.id && !checked) {
checkExchangeRate();
}
});
function handleSuccess() {
checked = true;
}
</script>
<CreateEditDialog
bind:open
onSuccess={handleSuccess}
overlayClass="bg-black/20"
/>
<CreateEditDialog bind:open onSuccess={handleSuccess} overlayClass="bg-black/20" />

View File

@@ -0,0 +1,51 @@
/**
* Configuración de conexión al backend
* Detecta automáticamente el entorno y usa la URL correcta
*/
export function getBackendUrl(): string {
// 1. Si existe variable de entorno, úsala (override manual)
if (import.meta.env.VITE_BACKEND_URL) {
return import.meta.env.VITE_BACKEND_URL;
}
// 2. Detección automática basada en dónde corre el código
if (typeof window !== 'undefined') {
// CLIENTE (Browser): usar la URL pública del backend
// En local: http://localhost:8000
// En prod: mismo dominio o dominio específico
const hostname = window.location.hostname;
if (hostname === 'localhost' || hostname === '127.0.0.1') {
return 'http://localhost:8000/api';
}
// En producción, asumir que el backend está en el mismo dominio /api
// o usar un subdominio específico
return `${window.location.protocol}//${hostname}/api`;
} else {
// SERVIDOR (SvelteKit SSR/Endpoints): usar URL interna
// En Docker: http://backend:8000
// En local: http://127.0.0.1:8000 (IPv4 explícito)
// Detectar si estamos en Docker por hostname
const isDocker = process.env.HOSTNAME?.includes('docker');
if (isDocker) {
return 'http://backend:8000/api';
}
// En desarrollo local, usar IPv4 explícito para evitar problemas con IPv6
return 'http://127.0.0.1:8000/api';
}
}
export const BACKEND_URL = getBackendUrl();
// Helper para logs
export function logBackendConfig() {
console.log('[Backend Config]', {
url: BACKEND_URL,
isServer: typeof window === 'undefined',
});
}

View File

@@ -8,11 +8,11 @@ export const load: PageServerLoad = async ({ cookies, url }) => {
clearAuthTokens(cookies);
return {};
}
// Limpiar siempre las cookies de sesión anterior al cargar login
// Esto evita que se queden datos del tenant anterior
clearAuthTokens(cookies);
// Permitir acceso al login sin redirigir automáticamente
// Esto evita bucles de redirección cuando el token existe pero puede estar expirado
return {};
@@ -32,7 +32,7 @@ export const actions = {
try {
const baseUrl = getServerApiUrl();
const loginUrl = `${baseUrl}v1/auth/login`;
const requestBody = {
username,
password,
@@ -46,11 +46,11 @@ export const actions = {
},
body: JSON.stringify(requestBody)
});
const result = await response.json();
if (!response.ok) {
return fail(response.status, {
return fail(response.status, {
error: result.detail || 'Error de autenticación',
username,
tenant_slug
@@ -72,8 +72,8 @@ export const actions = {
if (error && typeof error === 'object' && 'status' in error && 'location' in error) {
throw error;
}
return fail(500, {
return fail(500, {
error: 'Error de conexión con el servidor: ' + (error instanceof Error ? error.message : String(error)),
username,
tenant_slug

View File

@@ -1,79 +1,7 @@
import { redirect } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { getServerApiUrl, getAuthTokens } from '$lib/server/api';
export const POST: RequestHandler = async ({ cookies, fetch }) => {
// Obtener tokens antes de borrarlos
const { accessToken, refreshToken } = getAuthTokens(cookies);
if (refreshToken) {
try {
// Intentar obtener el username del access token
let username = "Unknown";
if (accessToken) {
try {
const parts = accessToken.split('.');
if (parts.length === 3) {
const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
username = payload.preferred_username || payload.username || payload.sub || "Unknown";
}
} catch (e) {
console.error("Error decoding token for logout audit:", e);
}
}
// Llamar al backend para logout y auditoría
const baseUrl = getServerApiUrl();
let finalUrl = `${baseUrl}v1/auth/logout`;
// Logic to handle potential connectivity issues (Docker vs Localhost)
// If we are on the host but API_URL targets 'backend' container, it might fail.
const makeRequest = async (url: string) => {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
refresh_token: refreshToken,
username: username
})
});
console.log(`[LOGOUT DEBUG] Backend response status: ${response.status} for url: ${url}`);
return response;
};
try {
await makeRequest(finalUrl);
console.log(`Logout processed for user ${username} at ${finalUrl}`);
} catch (err) {
console.warn(`Failed to contact backend at ${finalUrl}, retrying with localhost...`);
// Fallback: try localhost if the 'backend' hostname failed
if (finalUrl.includes('backend')) {
finalUrl = finalUrl.replace('backend', 'localhost');
try {
await makeRequest(finalUrl);
console.log(`Logout processed for user ${username} at ${finalUrl} (fallback: localhost)`);
} catch (fallbackErr) {
console.warn(`Localhost failed, trying 127.0.0.1...`);
// Second Fallback: try 127.0.0.1 explicitly to avoid IPv6 issues
finalUrl = finalUrl.replace('localhost', '127.0.0.1');
try {
await makeRequest(finalUrl);
console.log(`Logout processed for user ${username} at ${finalUrl} (fallback: 127.0.0.1)`);
} catch (secondFallbackErr) {
console.error("Logout audit failed even with 127.0.0.1 fallback:", secondFallbackErr);
}
}
} else {
console.error("Logout audit failed:", err);
}
}
} catch (error) {
console.error("Error reporting logout to backend:", error);
}
}
export const POST: RequestHandler = async ({ cookies }) => {
// Eliminar todas las cookies de autenticación
cookies.delete('access_token', { path: '/' });
cookies.delete('refresh_token', { path: '/' });