import { createSignal, createContext, useContext, JSX, onMount } from 'solid-js'; // ── Types ────────────────────────────────────────────────────────────────────── export interface RuntimeConfig { role: string; onboarding_required: boolean; onboarding_status?: string; enabled_modules: string[]; feature_flags: Record; permissions: Record; user: { id: string; full_name: string; email: string; roles: string[]; active_role: string; }; } export interface AuthState { access_token: string | null; // Memory only — never persisted runtime_config: RuntimeConfig | null; loading: boolean; error: string | null; } // ── Store (in-memory only) ───────────────────────────────────────────────────── const [authState, setAuthState] = createSignal({ access_token: null, runtime_config: null, loading: false, error: null, }); const API_BASE = import.meta.env.VITE_API_URL ?? 'http://localhost:8000'; // ── Auth API Calls ───────────────────────────────────────────────────────────── export async function login(email: string, password: string): Promise { setAuthState(s => ({ ...s, loading: true, error: null })); const res = await fetch(`${API_BASE}/api/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', // include httpOnly refresh cookie body: JSON.stringify({ email, password }), }); if (!res.ok) { const body = await res.json(); setAuthState(s => ({ ...s, loading: false, error: body.error ?? 'Login failed' })); throw new Error(body.error ?? 'Login failed'); } const data = await res.json(); setAuthState(s => ({ ...s, access_token: data.access_token, loading: false })); // Immediately fetch runtimeConfig await fetchRuntimeConfig(data.access_token); } export async function logout(): Promise { const token = authState().access_token; if (token) { await fetch(`${API_BASE}/api/auth/logout`, { method: 'POST', headers: { Authorization: `Bearer ${token}` }, credentials: 'include', }).catch(() => {}); } setAuthState({ access_token: null, runtime_config: null, loading: false, error: null }); } export async function refreshToken(): Promise { const res = await fetch(`${API_BASE}/api/auth/refresh`, { method: 'POST', credentials: 'include', // reads refresh token from httpOnly cookie }); if (!res.ok) return false; const data = await res.json(); setAuthState(s => ({ ...s, access_token: data.access_token })); return true; } export async function fetchRuntimeConfig(token?: string): Promise { const accessToken = token ?? authState().access_token; if (!accessToken) return; const res = await fetch(`${API_BASE}/api/runtime-config`, { headers: { Authorization: `Bearer ${accessToken}` }, credentials: 'include', }); if (res.ok) { const config: RuntimeConfig = await res.json(); setAuthState(s => ({ ...s, runtime_config: config })); } } export async function switchRole(roleKey: string): Promise { const token = authState().access_token; if (!token) return; const res = await fetch(`${API_BASE}/api/me/roles/switch`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }, body: JSON.stringify({ role_key: roleKey }), credentials: 'include', }); if (res.ok) { const data = await res.json(); setAuthState(s => ({ ...s, access_token: data.access_token })); await fetchRuntimeConfig(data.access_token); } } // ── Helpers ──────────────────────────────────────────────────────────────────── export function isAuthenticated(): boolean { return authState().access_token !== null; } export function hasModule(moduleKey: string): boolean { return authState().runtime_config?.enabled_modules.includes(moduleKey) ?? false; } export function hasPermission(key: string): boolean { return authState().runtime_config?.permissions[key] ?? false; } export function getAuthHeader(): Record { const token = authState().access_token; return token ? { Authorization: `Bearer ${token}` } : {}; } // ── Exported helpers ─────────────────────────────────────────────────────────── export { authState };