feat: Enhance authentication middleware with reduced cache expiry, improved session validation, and global error handling for auth-related issues

This commit is contained in:
2025-07-11 11:58:38 -04:00
parent 242e33f7b9
commit bf2361050f
6 changed files with 204 additions and 16 deletions

View File

@@ -14,8 +14,8 @@ export default defineNuxtPlugin(() => {
refreshTimer = null
}
// Calculate time until refresh (refresh 10 minutes before expiry for better safety margin)
const refreshBuffer = 10 * 60 * 1000 // 10 minutes in milliseconds (increased from 5)
// Calculate time until refresh (refresh 5 minutes before expiry)
const refreshBuffer = 5 * 60 * 1000 // 5 minutes in milliseconds
const timeUntilRefresh = expiresAt - Date.now() - refreshBuffer
console.log('[AUTH_REFRESH] Scheduling token refresh in:', Math.max(0, timeUntilRefresh), 'ms')
@@ -165,15 +165,43 @@ export default defineNuxtPlugin(() => {
if (typeof document !== 'undefined') {
let lastVisibilityChange = Date.now()
document.addEventListener('visibilitychange', () => {
document.addEventListener('visibilitychange', async () => {
if (!document.hidden) {
const now = Date.now()
const timeSinceLastCheck = now - lastVisibilityChange
// If tab was hidden for more than 1 minute, check auth status
if (timeSinceLastCheck > 60000) {
// If tab was hidden for more than 30 seconds, check auth status
if (timeSinceLastCheck > 30000) {
console.log('[AUTH_REFRESH] Tab became visible after', Math.round(timeSinceLastCheck / 1000), 'seconds, checking auth status')
checkAndScheduleRefresh()
// Force immediate session validation
try {
const response = await fetch('/api/auth/session', {
headers: {
'Cache-Control': 'no-cache',
'Pragma': 'no-cache'
}
})
if (!response.ok || response.status === 401) {
console.log('[AUTH_REFRESH] Session expired while tab was hidden')
await navigateTo('/login')
return
}
const sessionData = await response.json()
if (!sessionData.authenticated) {
console.log('[AUTH_REFRESH] Not authenticated after tab visibility')
await navigateTo('/login')
return
}
// Re-schedule refresh if session is valid
checkAndScheduleRefresh()
} catch (error) {
console.error('[AUTH_REFRESH] Failed to check session on visibility change:', error)
await navigateTo('/login')
}
}
lastVisibilityChange = now
@@ -181,11 +209,41 @@ export default defineNuxtPlugin(() => {
})
}
// Clean up timer on plugin destruction
// Add periodic session validation (every 2 minutes)
let validationInterval: NodeJS.Timeout | null = null
onMounted(() => {
validationInterval = setInterval(async () => {
console.log('[AUTH_REFRESH] Performing periodic session validation')
try {
const response = await fetch('/api/auth/session', {
headers: {
'Cache-Control': 'no-cache',
'Pragma': 'no-cache'
}
})
if (!response.ok || response.status === 401) {
console.log('[AUTH_REFRESH] Session invalid during periodic check')
clearInterval(validationInterval!)
await navigateTo('/login')
}
} catch (error) {
console.error('[AUTH_REFRESH] Periodic validation error:', error)
}
}, 2 * 60 * 1000) // Every 2 minutes
})
// Clean up timers on plugin destruction
onBeforeUnmount(() => {
if (refreshTimer) {
clearTimeout(refreshTimer)
refreshTimer = null
}
if (validationInterval) {
clearInterval(validationInterval)
validationInterval = null
}
})
})

View File

@@ -0,0 +1,119 @@
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
})
// Handle authentication errors (401, 403)
if (response.status === 401 || response.status === 403) {
handleAuthError({
statusCode: response.status,
statusMessage: response.statusText,
data: response._data
})
}
// Handle 404 errors that might be auth-related
if (response.status === 404 && isProtectedRoute()) {
console.warn('[AUTH_ERROR_HANDLER] 404 on protected route, 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)
})