port-nimara-client-portal/plugins/02.auth-error-handler.clien...

130 lines
4.1 KiB
TypeScript

export default defineNuxtPlugin(() => {
// Only run on client side
if (import.meta.server) return
const nuxtApp = useNuxtApp()
const toast = useToast()
// Global error handler for API requests
nuxtApp.hook('app:error', (error: any) => {
console.error('[AUTH_ERROR_HANDLER] Application error:', error)
// Handle authentication errors
if (error.statusCode === 401 || error.statusCode === 403) {
handleAuthError(error)
}
})
// Intercept $fetch errors globally
const originalFetch = globalThis.$fetch
globalThis.$fetch = $fetch.create({
onResponseError({ response }) {
console.log('[AUTH_ERROR_HANDLER] Response error:', {
status: response.status,
url: response.url,
statusText: response.statusText
})
// Only handle authentication errors from our own API endpoints
const isAuthEndpoint = response.url && (
response.url.includes('/api/auth/') ||
response.url.includes('/api/') && !response.url.includes('cms.portnimara.dev') && !response.url.includes('database.portnimara.com')
)
// Handle authentication errors (401, 403) only from our API
if ((response.status === 401 || response.status === 403) && isAuthEndpoint) {
console.log('[AUTH_ERROR_HANDLER] Authentication error from app endpoint')
handleAuthError({
statusCode: response.status,
statusMessage: response.statusText,
data: response._data
})
} else if (response.status === 401 && !isAuthEndpoint) {
console.log('[AUTH_ERROR_HANDLER] Ignoring 401 from external service:', response.url)
// Don't clear auth for external service 401s
}
// Handle 404 errors that might be auth-related
if (response.status === 404 && isProtectedRoute() && isAuthEndpoint) {
console.warn('[AUTH_ERROR_HANDLER] 404 on protected route from app endpoint, may be auth-related')
// Check if session is still valid
checkAndHandleSession()
}
}
})
const handleAuthError = async (error: any) => {
console.error('[AUTH_ERROR_HANDLER] Authentication error detected:', error)
// Clear all auth-related caches
clearAuthCaches()
// Only show toast and redirect if we're not already on the login page
const route = useRoute()
if (route.path !== '/login' && !route.path.startsWith('/auth')) {
toast.error('Your session has expired. Please log in again.')
// Delay navigation slightly to ensure toast is visible
setTimeout(() => {
navigateTo('/login')
}, 500)
}
}
const clearAuthCaches = () => {
console.log('[AUTH_ERROR_HANDLER] Clearing authentication caches')
// Clear Nuxt app payload caches
if (nuxtApp.payload.data) {
delete nuxtApp.payload.data['auth:session:cache']
delete nuxtApp.payload.data.authState
}
// Clear session cookie
const sessionCookie = useCookie('nuxt-oidc-auth')
sessionCookie.value = null
}
const isProtectedRoute = () => {
const route = useRoute()
// Check if current route requires authentication
return route.meta.auth !== false &&
!route.path.startsWith('/login') &&
!route.path.startsWith('/auth')
}
const checkAndHandleSession = async () => {
try {
// Force a fresh session check without cache
const response = await fetch('/api/auth/session', {
headers: {
'Cache-Control': 'no-cache',
'Pragma': 'no-cache'
}
})
if (!response.ok) {
throw new Error(`Session check failed: ${response.status}`)
}
const sessionData = await response.json()
if (!sessionData.authenticated) {
handleAuthError({
statusCode: 401,
statusMessage: 'Session expired'
})
}
} catch (error) {
console.error('[AUTH_ERROR_HANDLER] Failed to check session:', error)
handleAuthError({
statusCode: 401,
statusMessage: 'Session check failed'
})
}
}
// Expose clearAuthCaches for manual use
nuxtApp.provide('clearAuthCaches', clearAuthCaches)
})