272 lines
6.9 KiB
TypeScript
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
|
|
};
|
|
};
|