export interface UserWithGroups { id: string; email: string; username: string; name: string; authMethod: string; groups: string[]; } export interface AuthState { user: UserWithGroups | null; authenticated: boolean; groups: string[]; } /** * Authorization composable for role-based access control */ export const useAuthorization = () => { // Get the cached auth state from middleware instead of making API calls const nuxtApp = useNuxtApp(); // Simple reactive state that starts with safe defaults const authState = reactive({ user: null, authenticated: false, groups: [] }); // Loading state const isLoading = ref(true); const hasInitialized = ref(false); // Initialize auth state from middleware cache (no API calls!) const initializeAuth = () => { if (hasInitialized.value) return; try { console.log('[useAuthorization] Initializing from middleware cache...'); // Try to get auth from middleware's cached state first const cachedAuthState = nuxtApp.payload?.data?.authState; if (cachedAuthState && typeof cachedAuthState === 'object') { // Update reactive state from cache authState.user = cachedAuthState.user || null; authState.authenticated = cachedAuthState.authenticated || false; authState.groups = Array.isArray(cachedAuthState.groups) ? cachedAuthState.groups : []; console.log('[useAuthorization] Auth state loaded from cache:', { authenticated: authState.authenticated, groups: authState.groups, user: authState.user?.email }); hasInitialized.value = true; isLoading.value = false; return true; } // Fallback: try session cache const sessionCache = nuxtApp.payload?.data?.['auth:session:cache']; if (sessionCache && typeof sessionCache === 'object') { authState.user = sessionCache.user || null; authState.authenticated = sessionCache.authenticated || false; authState.groups = Array.isArray(sessionCache.groups) ? sessionCache.groups : []; console.log('[useAuthorization] Auth state loaded from session cache:', { authenticated: authState.authenticated, groups: authState.groups, user: authState.user?.email }); hasInitialized.value = true; isLoading.value = false; return true; } } catch (error) { console.error('[useAuthorization] Failed to load from cache:', error); } // Set safe defaults if no cache available console.log('[useAuthorization] No cache available, using defaults'); authState.user = null; authState.authenticated = false; authState.groups = []; hasInitialized.value = true; isLoading.value = false; return false; }; // Initialize immediately (both client and server) initializeAuth(); // Watch for changes in payload data (optimized to prevent loops) if (process.client) { let watcherTimeout: NodeJS.Timeout | null = null; watch( () => nuxtApp.payload?.data?.authState, (newAuthState, oldAuthState) => { // Only update if the auth state actually changed if (newAuthState && typeof newAuthState === 'object' && newAuthState !== oldAuthState) { // Debounce to prevent rapid re-initialization if (watcherTimeout) clearTimeout(watcherTimeout); watcherTimeout = setTimeout(() => { console.log('[useAuthorization] Auth state updated, syncing...'); // Direct sync instead of full re-initialization authState.user = newAuthState.user || null; authState.authenticated = newAuthState.authenticated || false; authState.groups = Array.isArray(newAuthState.groups) ? newAuthState.groups : []; if (!hasInitialized.value) { hasInitialized.value = true; isLoading.value = false; } }, 100); // 100ms debounce } }, { immediate: false, deep: false } ); } /** * Get current user groups from session */ const getUserGroups = (): string[] => { try { return Array.isArray(authState.groups) ? authState.groups : []; } catch (error) { console.error('[useAuthorization] Error getting user groups:', error); return []; } }; /** * Get current authenticated user */ const getCurrentUser = (): UserWithGroups | null => { try { return authState.user || null; } catch (error) { console.error('[useAuthorization] Error getting current user:', error); return null; } }; /** * Check if user has specific role/group */ const hasRole = (role: string): boolean => { const groups = getUserGroups(); return groups.includes(role); }; /** * Check if user has any of the specified roles */ const hasAnyRole = (roles: string[]): boolean => { const groups = getUserGroups(); return roles.some(role => groups.includes(role)); }; /** * Check if user has all of the specified roles */ const hasAllRoles = (roles: string[]): boolean => { const groups = getUserGroups(); return roles.every(role => groups.includes(role)); }; /** * Check if user can access a specific resource/feature */ const canAccess = (resource: string): boolean => { const groups = getUserGroups(); // Define access rules for different resources const accessRules: Record = { 'expenses': ['sales', 'admin'], 'admin-console': ['admin'], 'audit-logs': ['admin'], 'system-logs': ['admin'], 'duplicate-management': ['admin'], 'user-management': ['admin'], 'interests': ['user', 'sales', 'admin'], 'berths': ['user', 'sales', 'admin'], 'dashboard': ['user', 'sales', 'admin'], }; const requiredRoles = accessRules[resource]; if (!requiredRoles) { // If no specific rules defined, allow for authenticated users return groups.length > 0; } return hasAnyRole(requiredRoles); }; /** * Convenience methods for common role checks */ const isAdmin = (): boolean => hasRole('admin'); const isSales = (): boolean => hasRole('sales'); const isUser = (): boolean => hasRole('user'); const isSalesOrAdmin = (): boolean => hasAnyRole(['sales', 'admin']); const isUserOrAbove = (): boolean => hasAnyRole(['user', 'sales', 'admin']); /** * Get user's highest role (for display purposes) */ const getHighestRole = (): string => { const groups = getUserGroups(); if (groups.includes('admin')) return 'admin'; if (groups.includes('sales')) return 'sales'; if (groups.includes('user')) return 'user'; return 'none'; }; /** * Get role display name */ const getRoleDisplayName = (role: string): string => { const roleNames: Record = { 'admin': 'Administrator', 'sales': 'Sales Team', 'user': 'User', 'none': 'No Access' }; return roleNames[role] || role; }; /** * Get role color for UI display */ const getRoleColor = (role: string): string => { const roleColors: Record = { 'admin': 'red', 'sales': 'purple', 'user': 'blue', 'none': 'grey' }; return roleColors[role] || 'grey'; }; /** * Check if current route requires specific roles */ const checkRouteAccess = (route: any): boolean => { if (!route.meta?.roles) { // No role requirements, allow access return true; } const requiredRoles = Array.isArray(route.meta.roles) ? route.meta.roles : [route.meta.roles]; return hasAnyRole(requiredRoles); }; /** * Get navigation items filtered by user permissions */ const getFilteredNavigation = (navigationItems: any[]): any[] => { return navigationItems.filter(item => { if (!item.roles) return true; // No role restrictions return hasAnyRole(item.roles); }); }; /** * Update auth state (called by middleware) */ const updateAuthState = (newAuthState: AuthState) => { try { const nuxtApp = useNuxtApp(); if (!nuxtApp.payload.data) { nuxtApp.payload.data = {}; } nuxtApp.payload.data.authState = newAuthState; } catch (error) { console.error('[useAuthorization] Error updating auth state:', error); } }; return { // State getters getUserGroups, getCurrentUser, // Role checking hasRole, hasAnyRole, hasAllRoles, canAccess, // Convenience methods isAdmin, isSales, isUser, isSalesOrAdmin, isUserOrAbove, // Utility methods getHighestRole, getRoleDisplayName, getRoleColor, checkRouteAccess, getFilteredNavigation, // State management updateAuthState }; };