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 current user state from Nuxt const nuxtApp = useNuxtApp(); // Create reactive auth state const authState = ref({ user: null, authenticated: false, groups: [] }); // Create a loading state const isLoading = ref(true); // Function to sync auth state from nuxtApp payload const syncAuthState = () => { try { // Safely check if payload data exists if (nuxtApp.payload && nuxtApp.payload.data && nuxtApp.payload.data.authState) { const payloadAuthState = nuxtApp.payload.data.authState as AuthState; authState.value = payloadAuthState; isLoading.value = false; console.log('[useAuthorization] Auth state synced from payload:', { authenticated: payloadAuthState.authenticated, groups: payloadAuthState.groups, user: payloadAuthState.user?.email }); return true; } } catch (error) { console.error('[useAuthorization] Error syncing auth state:', error); } return false; }; // Try to get auth state from API if not in payload const loadAuthState = async () => { try { const sessionData = await $fetch('/api/auth/session') as AuthState; authState.value = sessionData; isLoading.value = false; console.log('[useAuthorization] Auth state loaded from API:', { authenticated: sessionData.authenticated, groups: sessionData.groups, user: sessionData.user?.email }); // Update nuxtApp payload for future use updateAuthState(sessionData); } catch (error) { console.error('[useAuthorization] Failed to load auth state:', error); isLoading.value = false; } }; // Initialize auth state immediately (not just onMounted) if (process.client) { // Try to sync from payload first const synced = syncAuthState(); // If not synced from payload, load from API if (!synced) { loadAuthState(); } } else { // On server, try to get from payload syncAuthState(); } /** * Get current user groups from session */ const getUserGroups = (): string[] => { try { return authState.value?.groups || []; } catch (error) { console.error('[useAuthorization] Error getting user groups:', error); return []; } }; /** * Get current authenticated user */ const getCurrentUser = (): UserWithGroups | null => { try { return authState.value?.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 = (authState: AuthState) => { if (!nuxtApp.payload.data) { nuxtApp.payload.data = {}; } nuxtApp.payload.data.authState = authState; }; 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 }; };