/** * Authentication Error Composable for Frontend * * Provides utilities to classify authentication errors and determine * if a logout is necessary. This matches the backend authErrorHelper logic. */ export interface AuthErrorClassification { shouldLogout: boolean errorType: 'token_expired' | 'token_invalid' | 'authorization' | 'other' | 'none' reason?: string } export const useAuthError = () => { /** * Classifies an error response to determine if it's a true authentication failure * * @param error - The error response object * @returns Classification indicating if logout is required */ const classifyAuthError = (error: any): AuthErrorClassification => { const status = Number(error?.status ?? error?.statusCode ?? 0) const message = (error?.message ?? error?.detail ?? error?.statusMessage ?? '').toLowerCase() // Not an auth-related error at all if (status !== 401 && status !== 403 && status !== 407) { return { shouldLogout: false, errorType: 'none' } } // Check for specific authentication failure indicators const tokenExpiredIndicators = [ 'token expired', 'token has expired', 'jwt expired', 'session expired', 'authentication expired' ] const tokenInvalidIndicators = [ 'invalid token', 'token invalid', 'invalid credentials', 'authentication failed', 'invalid authentication', 'token not found', 'no token provided', 'missing token', 'malformed token', 'token verification failed' ] const authorizationIndicators = [ 'forbidden', 'access denied', 'insufficient permissions', 'not authorized', 'permission denied', 'unauthorized access', 'requires permission' ] // Check message content for specific error types if (tokenExpiredIndicators.some(indicator => message.includes(indicator))) { return { shouldLogout: true, errorType: 'token_expired', reason: 'Token has expired and cannot be refreshed' } } if (tokenInvalidIndicators.some(indicator => message.includes(indicator))) { return { shouldLogout: true, errorType: 'token_invalid', reason: 'Token is invalid or authentication failed' } } if (authorizationIndicators.some(indicator => message.includes(indicator))) { return { shouldLogout: false, errorType: 'authorization', reason: 'User is authenticated but lacks permission for this resource' } } // Default behavior based on status code // 401: Usually means authentication failed - should logout // 403: Usually means authorization failed - should NOT logout // 407: Proxy authentication required - should logout if (status === 401 || status === 407) { return { shouldLogout: true, errorType: 'token_invalid', reason: `Received ${status} status without specific error message` } } if (status === 403) { return { shouldLogout: false, errorType: 'authorization', reason: 'Access forbidden - likely a permissions issue, not authentication' } } return { shouldLogout: false, errorType: 'other', reason: 'Error does not indicate authentication failure' } } /** * Checks if an error should trigger a logout * * @param error - The error response object * @returns true if the user should be logged out */ const shouldLogoutOnError = (error: any): boolean => { return classifyAuthError(error).shouldLogout } /** * Handles logout by calling the logout API and navigating to signin */ const performLogout = async () => { try { await $fetch('/api/idempiere-auth/logout', { method: 'POST', headers: useRequestHeaders(['cookie']) }) } catch (error) { console.error('Logout API call failed:', error) // Continue with navigation even if API call fails } // Navigate to signin page await navigateTo('/signin') } /** * Main error handler that checks if logout is needed and performs it * * @param error - The error response object * @returns true if logout was performed, false otherwise */ const handleAuthError = async (error: any): Promise => { const classification = classifyAuthError(error) // Log in development mode if (process.dev) { console.log('[useAuthError] Error classification:', { status: error?.status ?? error?.statusCode, errorType: classification.errorType, shouldLogout: classification.shouldLogout, reason: classification.reason }) } if (classification.shouldLogout) { await performLogout() return true } return false } return { classifyAuthError, shouldLogoutOnError, handleAuthError, performLogout } }