Race condition: the axios 401 interceptor uses a dynamic import, so handleUnauthorized() fires AFTER doInitialize() sets isInitialized=true. handleUnauthorized() then reset isInitialized to false, leaving the app stuck on a loading spinner with no way to recover. Fix: remove isInitialized=false from handleUnauthorized() in all three apps. When handleUnauthorized() redirects via window.location.href, all JS state resets naturally. When it skips the redirect (already on a public page like /login), the app should render normally in an unauthenticated state. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
111 lines
2.8 KiB
TypeScript
111 lines
2.8 KiB
TypeScript
import { defineStore } from 'pinia'
|
|
import { computed, ref } from 'vue'
|
|
import { apiClient } from '@/lib/axios'
|
|
import type { AuthMeUser } from '@/types/portal'
|
|
|
|
export const useAuthStore = defineStore('auth', () => {
|
|
const user = ref<AuthMeUser | null>(null)
|
|
const isInitialized = ref(false)
|
|
|
|
const isAuthenticated = computed(() => !!user.value)
|
|
|
|
function setUser(data: AuthMeUser | null) {
|
|
user.value = data
|
|
}
|
|
|
|
async function resetPortalStoresSync(): Promise<void> {
|
|
const { usePortalStore } = await import('@/stores/usePortalStore')
|
|
usePortalStore().reset()
|
|
}
|
|
|
|
function clearState() {
|
|
user.value = null
|
|
void resetPortalStoresSync()
|
|
}
|
|
|
|
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') {
|
|
const path = window.location.pathname
|
|
const publicPaths = ['/login', '/wachtwoord-vergeten', '/wachtwoord-resetten', '/verify-email-change']
|
|
if (!publicPaths.some(p => path.startsWith(p)) && !path.startsWith('/register')) {
|
|
window.location.href = '/login'
|
|
}
|
|
}
|
|
}
|
|
|
|
async function login(email: string, password: string): Promise<void> {
|
|
const { data } = await apiClient.post<{
|
|
success: boolean
|
|
data: { user: AuthMeUser }
|
|
}>('/auth/login', { email, password })
|
|
|
|
// Token is set automatically via httpOnly Set-Cookie header
|
|
setUser(data.data.user)
|
|
|
|
// Validate by fetching full user data
|
|
const ok = await fetchUser()
|
|
if (!ok) throw new Error('Sessie kon niet worden gestart.')
|
|
}
|
|
|
|
async function fetchUser(): Promise<boolean> {
|
|
try {
|
|
const { data } = await apiClient.get<{ success: boolean; data: AuthMeUser }>('/auth/me')
|
|
setUser(data.data)
|
|
|
|
return true
|
|
}
|
|
catch {
|
|
clearState()
|
|
|
|
return false
|
|
}
|
|
}
|
|
|
|
async function logout(): Promise<void> {
|
|
try {
|
|
await apiClient.post('/auth/logout')
|
|
}
|
|
catch {
|
|
// Ignore network errors; still clear local session
|
|
}
|
|
clearState()
|
|
}
|
|
|
|
let initializePromise: Promise<void> | null = null
|
|
|
|
function initialize(): Promise<void> {
|
|
if (isInitialized.value) return Promise.resolve()
|
|
if (!initializePromise)
|
|
initializePromise = doInitialize()
|
|
|
|
return initializePromise
|
|
}
|
|
|
|
async function doInitialize(): Promise<void> {
|
|
try {
|
|
await fetchUser()
|
|
}
|
|
finally {
|
|
isInitialized.value = true
|
|
}
|
|
}
|
|
|
|
return {
|
|
user,
|
|
isAuthenticated,
|
|
isInitialized,
|
|
setUser,
|
|
login,
|
|
logout,
|
|
fetchUser,
|
|
initialize,
|
|
handleUnauthorized,
|
|
}
|
|
})
|