Sesiones correctas

This commit is contained in:
2026-03-04 13:31:38 -07:00
parent 3b46f48655
commit 16b3dc5d47
10 changed files with 127 additions and 164 deletions

View File

@@ -1,7 +1,5 @@
import type { Writable } from 'svelte/store';
import { writable } from 'svelte/store';
// Types
export interface User {
id: string;
email: string;
@@ -13,131 +11,94 @@ export interface User {
is_two_factor_enabled: boolean;
created_at: string;
}
export interface AuthState {
user: User | null;
token: string | null;
isAuthenticated: boolean;
isLoading: boolean;
}
export interface LoginRequest {
email: string;
password: string;
tenant_slug: string;
totp_code?: string;
}
export interface LoginResponse {
access_token: string;
token_type: string;
expires_in: number;
user: User;
}
// Initial state
const initialState: AuthState = {
user: null,
token: null,
isAuthenticated: false,
isLoading: false
};
// Create auth store
function createAuthStore() {
const { subscribe, set, update }: Writable<AuthState> = writable(initialState);
let _state = initialState;
subscribe(s => { _state = s; });
return {
subscribe,
// Rehidrata sesión desde cookie HttpOnly (no toca localStorage)
init: async () => {
if (typeof window !== 'undefined') {
try {
const response = await fetch('/api/v1/auth/me', {
credentials: 'include',
headers: { 'X-App': 'client' }
headers: { 'X-App': 'client', 'X-Tenant-Slug': 'aduanasoft' }
});
if (response.ok) {
const user = await response.json();
set({
user,
token: null,
isAuthenticated: true,
isLoading: false
});
set({ user, token: null, isAuthenticated: true, isLoading: false });
}
// 401/400 es esperado cuando no hay sesión activa — no es un error
} catch (error) {
// Ignorar errores de red en init
}
} catch (error) {}
}
},
// Login
login: async (credentials: LoginRequest): Promise<void> => {
update(state => ({ ...state, isLoading: true }));
try {
const response = await fetch('/api/v1/auth/login', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'X-Tenant-Slug': credentials.tenant_slug,
},
body: JSON.stringify(credentials)
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Login failed');
}
const data: LoginResponse = await response.json();
set({
user: data.user,
token: data.access_token,
isAuthenticated: true,
isLoading: false
});
set({ user: data.user, token: data.access_token, isAuthenticated: true, isLoading: false });
} catch (error) {
update(state => ({ ...state, isLoading: false }));
throw error;
}
},
// Logout
logout: async () => {
// Llamar al backend para que borre la cookie HttpOnly
try {
const token = _state.token;
await fetch('/api/v1/auth/logout', {
method: 'POST',
credentials: 'include',
headers: { 'X-App': 'client' }
headers: {
'X-App': 'client',
'X-Tenant-Slug': 'aduanasoft',
...(token ? { 'Authorization': `Bearer ${token}` } : {})
}
});
} catch { /* ignorar errores de red */ }
} catch {}
set(initialState);
if (typeof window !== 'undefined') {
window.location.href = '/login';
}
},
// Update user data
updateUser: (user: User) => {
update(state => ({ ...state, user }));
},
// Set user from SSR pre-load (no fetch required)
setUser: (user: User) => {
set({ user, token: null, isAuthenticated: true, isLoading: false });
},
// Set loading state
setLoading: (isLoading: boolean) => {
update(state => ({ ...state, isLoading }));
}
updateUser: (user: User) => { update(state => ({ ...state, user })); },
setUser: (user: User) => { set({ user, token: null, isAuthenticated: true, isLoading: false }); },
setLoading: (isLoading: boolean) => { update(state => ({ ...state, isLoading })); }
};
}
export const auth = createAuthStore();