import type { Writable } from 'svelte/store'; import { writable } from 'svelte/store'; export interface User { id: string; email: string; first_name: string; last_name: string; tenant_id: string; role: 'CLIENT_ADMIN' | 'CLIENT_USER'; is_active: boolean; 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; } const initialState: AuthState = { user: null, token: null, isAuthenticated: false, isLoading: false }; function createAuthStore() { const { subscribe, set, update }: Writable = writable(initialState); let _state = initialState; subscribe(s => { _state = s; }); return { subscribe, // Verifica sesión activa al cargar la app (cookie HttpOnly) init: async () => { if (typeof window === 'undefined') return; try { const response = await fetch('/api/v1/auth/me', { credentials: 'include', headers: { 'X-App': 'client', // Sin X-Tenant-ID aquí — /auth/me lee el tenant del JWT directamente } }); if (response.ok) { const user = await response.json(); // Token es null — la autenticación viaja por cookie HttpOnly // El store solo necesita el user para la UI set({ user, token: null, isAuthenticated: true, isLoading: false }); } else { // Cookie expirada o inválida — limpiar estado set(initialState); } } catch { set(initialState); } }, login: async (credentials: LoginRequest): Promise => { 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(); // Guardamos el token en memoria para requests inmediatos // Si la página se recarga, init() recupera la sesión desde la cookie set({ user: data.user, token: data.access_token, isAuthenticated: true, isLoading: false }); } catch (error) { update(state => ({ ...state, isLoading: false })); throw error; } }, logout: async () => { try { const token = _state.token; await fetch('/api/v1/auth/logout', { method: 'POST', credentials: 'include', headers: { 'X-App': 'client', ...(token ? { 'Authorization': `Bearer ${token}` } : {}) } }); } catch {} set(initialState); if (typeof window !== 'undefined') { window.location.href = '/login'; } }, 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();