import { defineStore } from 'pinia' import { computed, ref } from 'vue' import { apiClient } from '@/lib/axios' import { useOrganisationStore } from '@/stores/useOrganisationStore' import type { MeResponse, Organisation, User } from '@/types/auth' export const useAuthStore = defineStore('auth', () => { const user = ref(null) const organisations = ref([]) const appRoles = ref([]) const permissions = ref([]) const isInitialized = ref(false) const mfaSetupRequired = ref(false) const isAuthenticated = computed(() => !!user.value) const isSuperAdmin = computed(() => appRoles.value?.includes('super_admin') ?? false) const currentOrganisation = computed(() => { const orgStore = useOrganisationStore() return organisations.value.find(o => o.id === orgStore.activeOrganisationId) ?? organisations.value[0] ?? null }) function setUser(me: MeResponse) { user.value = { id: me.id, first_name: me.first_name, last_name: me.last_name, full_name: me.full_name, email: me.email, timezone: me.timezone, locale: me.locale, avatar: me.avatar, } organisations.value = me.organisations appRoles.value = me.app_roles permissions.value = me.permissions mfaSetupRequired.value = me.mfa?.setup_required ?? false // Auto-select first organisation if none is active const orgStore = useOrganisationStore() if (!orgStore.activeOrganisationId && me.organisations.length > 0) { orgStore.setActiveOrganisation(me.organisations[0].id) } } function setActiveOrganisation(id: string) { const orgStore = useOrganisationStore() orgStore.setActiveOrganisation(id) } function clearState() { user.value = null organisations.value = [] appRoles.value = [] permissions.value = [] mfaSetupRequired.value = false const orgStore = useOrganisationStore() orgStore.clear() } function handleUnauthorized() { clearState() // Do NOT reset isInitialized — the full page reload (below) resets all JS state. // Resetting it here causes a race condition: the async 401 interceptor fires // after doInitialize() sets isInitialized=true, putting the app back into // a loading state that never resolves. if (typeof window !== 'undefined' && window.location.pathname !== '/login') { window.location.href = '/login' } } async function logout() { try { await apiClient.post('/auth/logout') } catch { // Ignore network errors; still clear local state } finally { clearState() } } /** * Called once on app startup. Validates the httpOnly cookie by calling * GET /auth/me. On 401, clears everything. * Safe to call multiple times — subsequent calls return the same promise. */ let initializePromise: Promise | null = null function initialize(): Promise { if (isInitialized.value) return Promise.resolve() if (!initializePromise) { initializePromise = doInitialize() } return initializePromise } async function doInitialize(): Promise { try { const { data } = await apiClient.get<{ success: boolean; data: MeResponse }>('/auth/me') setUser(data.data) } catch { // Cookie invalid/expired or not present — clear everything clearState() } finally { isInitialized.value = true } } return { user, organisations, appRoles, permissions, isAuthenticated, isInitialized, isSuperAdmin, currentOrganisation, mfaSetupRequired, setUser, setActiveOrganisation, logout, handleUnauthorized, initialize, } })