Refactor backend and frontend code for improved structure and functionality
- Rearranged imports in multiple files for consistency and clarity. - Updated logging middleware to exclude specific paths from logging. - Enhanced security module by cleaning up token handling and improving tenant validation. - Added tenant and company scoped mixins for better database model management. - Implemented generic CRUD routes for tenant-scoped resources. - Improved error handling and response management in API routes. - Cleaned up login and logout processes to ensure proper session management. - Introduced mechanisms to clear local storage and cookies on tenant change. - Enhanced company store to detect tenant changes and clear data accordingly. - Added new DTO mixins for currency and value affect flags.
This commit is contained in:
@@ -48,8 +48,7 @@ async function refreshToken(): Promise<string | null> {
|
||||
};
|
||||
|
||||
refreshTokenValue = getCookie('refresh_token');
|
||||
if (refreshTokenValue) {
|
||||
console.log('📝 [API] Refresh token encontrado en cookies, sincronizando a localStorage');
|
||||
if (refreshTokenValue) {
|
||||
localStorage.setItem('refresh_token', refreshTokenValue);
|
||||
}
|
||||
}
|
||||
@@ -57,9 +56,7 @@ async function refreshToken(): Promise<string | null> {
|
||||
if (!refreshTokenValue) {
|
||||
console.error('❌ [API] No hay refresh token disponible');
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log('🔄 [API] Intentando refrescar token...');
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/v1/auth/refresh`, {
|
||||
@@ -88,8 +85,7 @@ async function refreshToken(): Promise<string | null> {
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('✅ [API] Token refrescado correctamente');
|
||||
const data = await response.json();
|
||||
|
||||
// Guardar los nuevos tokens
|
||||
if (data.access_token) {
|
||||
@@ -136,8 +132,7 @@ async function fetchApi<T = any>(
|
||||
retryCount = 0
|
||||
): Promise<ApiResponse<T>> {
|
||||
// Si ya estamos refrescando el token, esperar
|
||||
if (isRefreshing && retryCount === 0) {
|
||||
console.log('⏳ [API] Esperando refresh del token...');
|
||||
if (isRefreshing && retryCount === 0) {
|
||||
return new Promise((resolve) => {
|
||||
subscribeTokenRefresh((newToken) => {
|
||||
resolve(fetchApi<T>(endpoint, options, 1));
|
||||
@@ -168,16 +163,14 @@ async function fetchApi<T = any>(
|
||||
});
|
||||
|
||||
// Si recibimos 401 o 403 y no es el endpoint de refresh, intentar refrescar el token
|
||||
if ((response.status === 401 || response.status === 403) && !endpoint.includes('/auth/refresh') && retryCount === 0) {
|
||||
console.log('🔄 [API] Recibido 401/403, intentando refrescar token...');
|
||||
if ((response.status === 401 || response.status === 403) && !endpoint.includes('/auth/refresh') && retryCount === 0) {
|
||||
isRefreshing = true;
|
||||
|
||||
try {
|
||||
const newToken = await refreshToken();
|
||||
|
||||
if (newToken) {
|
||||
// Token refrescado exitosamente
|
||||
console.log('✅ [API] Token refrescado exitosamente');
|
||||
// Token refrescado exitosamente
|
||||
onTokenRefreshed(newToken);
|
||||
isRefreshing = false;
|
||||
// Reintentar la petición original con el nuevo token
|
||||
|
||||
@@ -11,9 +11,10 @@
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { page } from '$app/stores';
|
||||
import { page } from '$app/state';
|
||||
import { enhance } from '$app/forms';
|
||||
import { loginWithProvider } from '$lib/sso';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let { class: className, ...restProps }: HTMLAttributes<HTMLDivElement> = $props();
|
||||
|
||||
@@ -25,23 +26,40 @@
|
||||
let loading = $state(false);
|
||||
|
||||
// Obtener el error del servidor si existe
|
||||
const error = $derived($page.form?.error || '');
|
||||
const error = $derived(page.form?.error || '');
|
||||
|
||||
// Limpiar todo el localStorage y cookies al montar el componente de login
|
||||
// Esto asegura que no queden datos del tenant anterior
|
||||
onMount(() => {
|
||||
clearAllData();
|
||||
});
|
||||
|
||||
// Función para limpiar cookies del cliente
|
||||
function clearClientCookies() {
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.removeItem('access_token');
|
||||
localStorage.removeItem('refresh_token');
|
||||
}
|
||||
|
||||
if (typeof document !== 'undefined') {
|
||||
const isSecure = window.location.protocol === 'https:';
|
||||
const secureFlag = isSecure ? '; Secure' : '';
|
||||
document.cookie = `access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC; SameSite=Lax${secureFlag}`;
|
||||
document.cookie = `refresh_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC; SameSite=Lax${secureFlag}`;
|
||||
document.cookie = `active_company_id=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC; SameSite=Lax${secureFlag}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Función para limpiar todo el localStorage y cookies
|
||||
function clearAllData() {
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.removeItem('access_token');
|
||||
localStorage.removeItem('refresh_token');
|
||||
localStorage.removeItem('activeCompanyId');
|
||||
}
|
||||
|
||||
clearClientCookies();
|
||||
}
|
||||
|
||||
function handleMicrosoftLogin() {
|
||||
// Limpiar datos antes de iniciar SSO
|
||||
clearAllData();
|
||||
|
||||
// Guardar el tenant_slug en localStorage para recuperarlo después del callback
|
||||
if (tenantSlug) {
|
||||
localStorage.setItem('pending_tenant_slug', tenantSlug);
|
||||
@@ -50,6 +68,9 @@
|
||||
}
|
||||
|
||||
function handleGoogleLogin() {
|
||||
// Limpiar datos antes de iniciar SSO
|
||||
clearAllData();
|
||||
|
||||
// Guardar el tenant_slug en localStorage para recuperarlo después del callback
|
||||
if (tenantSlug) {
|
||||
localStorage.setItem('pending_tenant_slug', tenantSlug);
|
||||
|
||||
@@ -15,6 +15,7 @@ class CompanyStore {
|
||||
private _activeCompany = $state<Company | null>(null);
|
||||
private _companies = $state<Company[]>([]);
|
||||
private _loading = $state(false);
|
||||
private _currentTenantId = $state<number | null>(null);
|
||||
|
||||
get activeCompany() {
|
||||
return this._activeCompany;
|
||||
@@ -36,7 +37,22 @@ class CompanyStore {
|
||||
try {
|
||||
const response = await fetch('/api/company/my-companies');
|
||||
if (response.ok) {
|
||||
this._companies = await response.json();
|
||||
const newCompanies = await response.json();
|
||||
|
||||
// Detectar si el tenant ha cambiado
|
||||
if (newCompanies.length > 0) {
|
||||
const newTenantId = newCompanies[0].tenant_id;
|
||||
|
||||
// Si el tenant cambió, limpiar el store primero
|
||||
if (this._currentTenantId !== null && this._currentTenantId !== newTenantId) {
|
||||
|
||||
this.clear();
|
||||
}
|
||||
|
||||
this._currentTenantId = newTenantId;
|
||||
}
|
||||
|
||||
this._companies = newCompanies;
|
||||
|
||||
// Si hay compañías y no hay una activa, seleccionar la primera
|
||||
if (this._companies.length > 0 && !this._activeCompany) {
|
||||
@@ -44,6 +60,10 @@ class CompanyStore {
|
||||
}
|
||||
} else {
|
||||
console.error('Error loading companies:', response.statusText);
|
||||
// Si falla la carga (ej: 401), limpiar el store
|
||||
if (response.status === 401) {
|
||||
this.clear();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading companies:', error);
|
||||
@@ -98,6 +118,7 @@ class CompanyStore {
|
||||
this._activeCompany = null;
|
||||
this._companies = [];
|
||||
this._loading = false;
|
||||
this._currentTenantId = null;
|
||||
|
||||
// Limpiar localStorage
|
||||
if (typeof window !== 'undefined') {
|
||||
|
||||
@@ -8,6 +8,8 @@ export const GET: RequestHandler = async ({ cookies, fetch }) => {
|
||||
const token = cookies.get('access_token');
|
||||
|
||||
if (!token) {
|
||||
// Limpiar cualquier cookie de compañía si no hay autenticación
|
||||
cookies.delete('active_company_id', { path: '/' });
|
||||
return json({ error: 'No authenticated' }, { status: 401 });
|
||||
}
|
||||
|
||||
@@ -31,9 +33,13 @@ export const GET: RequestHandler = async ({ cookies, fetch }) => {
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
// Si la autenticación falló, limpiar la cookie de compañía
|
||||
if (response.status === 401) {
|
||||
cookies.delete('active_company_id', { path: '/' });
|
||||
}
|
||||
return json({ error: 'Failed to fetch companies' }, { status: response.status });
|
||||
}
|
||||
|
||||
console.log('✅ Token', token);
|
||||
const companies = await response.json();
|
||||
return json(companies);
|
||||
} catch (error) {
|
||||
|
||||
@@ -6,9 +6,16 @@ export const load: PageServerLoad = async ({ cookies, url }) => {
|
||||
if (url.searchParams.has('logout')) {
|
||||
cookies.delete('access_token', { path: '/' });
|
||||
cookies.delete('refresh_token', { path: '/' });
|
||||
cookies.delete('active_company_id', { path: '/' });
|
||||
return {};
|
||||
}
|
||||
|
||||
// Limpiar siempre las cookies de sesión anterior al cargar login
|
||||
// Esto evita que se queden datos del tenant anterior
|
||||
cookies.delete('access_token', { path: '/' });
|
||||
cookies.delete('refresh_token', { path: '/' });
|
||||
cookies.delete('active_company_id', { path: '/' });
|
||||
|
||||
// Permitir acceso al login sin redirigir automáticamente
|
||||
// Esto evita bucles de redirección cuando el token existe pero puede estar expirado
|
||||
return {};
|
||||
|
||||
@@ -6,6 +6,9 @@ export const POST: RequestHandler = async ({ cookies }) => {
|
||||
cookies.delete('access_token', { path: '/' });
|
||||
cookies.delete('refresh_token', { path: '/' });
|
||||
|
||||
// Eliminar la cookie de la compañía activa
|
||||
cookies.delete('active_company_id', { path: '/' });
|
||||
|
||||
// Redirigir al login
|
||||
throw redirect(303, '/login');
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user