port-nimara-client-portal/composables/useAuthorization.ts

272 lines
6.9 KiB
TypeScript

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<AuthState>({
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<string, string[]> = {
'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<string, string> = {
'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<string, string> = {
'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
};
};