export const useRbacPermissions = () => { const permissionCache = ref(new Map()) const batchCache = ref(new Map()) /** * Check if the current user has a specific permission * @param {string} permissionKey - The unique permission key (e.g., 'menu.dashboard', 'component.user.edit_button') * @param {string} action - The action to check (default: 'view') * @returns {Promise} - Whether the user has the permission */ const hasPermission = async (permissionKey, action = 'view') => { // Check local cache first const cacheKey = `${permissionKey}:${action}` if (permissionCache.value.has(cacheKey)) { return permissionCache.value.get(cacheKey) } try { const { data } = await $fetch('/api/rbac/check-permission', { method: 'POST', body: { permissionKey, action } }) // Cache result locally permissionCache.value.set(cacheKey, data.hasPermission) return data.hasPermission } catch (error) { console.error('Permission check failed:', error) return false // Fail secure } } /** * Check multiple permissions at once (more efficient for bulk checks) * @param {string[]} permissionKeys - Array of permission keys to check * @param {string} action - The action to check for all keys (default: 'view') * @returns {Promise} - Object with permission keys as keys and boolean results as values */ const hasPermissions = async (permissionKeys, action = 'view') => { // Check cache for already known permissions const results = {} const uncachedKeys = [] for (const key of permissionKeys) { const cacheKey = `${key}:${action}` if (permissionCache.value.has(cacheKey)) { results[key] = permissionCache.value.get(cacheKey) } else { uncachedKeys.push(key) } } // If all permissions are cached, return immediately if (uncachedKeys.length === 0) { return results } try { const { data } = await $fetch('/api/rbac/check-permissions-batch', { method: 'POST', body: { permissionKeys: uncachedKeys, action } }) // Cache results locally and add to final results Object.entries(data.permissions).forEach(([key, hasAccess]) => { const cacheKey = `${key}:${action}` permissionCache.value.set(cacheKey, hasAccess) results[key] = hasAccess }) return results } catch (error) { console.error('Batch permission check failed:', error) // Return false for uncached keys uncachedKeys.forEach(key => { results[key] = false }) return results } } /** * Check if user can access a specific menu item * @param {string} menuPath - Menu path or key * @returns {Promise} */ const canAccessMenu = async (menuPath) => { const menuKey = menuPath.startsWith('menu.') ? menuPath : `menu.${menuPath.replace(/^\//, '').replace(/\//g, '.')}` return await hasPermission(menuKey, 'view') } /** * Check if user can see a specific component * @param {string} componentKey - Component permission key * @returns {Promise} */ const canSeeComponent = async (componentKey) => { const fullKey = componentKey.startsWith('component.') ? componentKey : `component.${componentKey}` return await hasPermission(fullKey, 'view') } /** * Check if user can perform a specific feature action * @param {string} featureKey - Feature permission key * @param {string} action - Action to check (create, edit, delete, approve, etc.) * @returns {Promise} */ const canPerformAction = async (featureKey, action) => { const fullKey = featureKey.startsWith('feature.') ? featureKey : `feature.${featureKey}` return await hasPermission(fullKey, action) } /** * Clear the permission cache (useful after role changes) */ const clearCache = () => { permissionCache.value.clear() batchCache.value.clear() } /** * Pre-load permissions for better performance * @param {string[]} permissionKeys - Array of permission keys to preload */ const preloadPermissions = async (permissionKeys) => { await hasPermissions(permissionKeys) } /** * Get cached permission state (useful for reactive UI updates) * @param {string} permissionKey * @param {string} action * @returns {boolean|null} - null if not cached, boolean if cached */ const getCachedPermission = (permissionKey, action = 'view') => { const cacheKey = `${permissionKey}:${action}` return permissionCache.value.has(cacheKey) ? permissionCache.value.get(cacheKey) : null } return { hasPermission, hasPermissions, canAccessMenu, canSeeComponent, canPerformAction, clearCache, preloadPermissions, getCachedPermission } } // Permission key constants for type safety and consistency export const PERMISSION_KEYS = { // Menu permissions MENU: { DASHBOARD: 'menu.dashboard', USERS: 'menu.users', USERS_LIST: 'menu.users.list', USERS_CREATE: 'menu.users.create', RBAC: 'menu.rbac', RBAC_ROLES: 'menu.rbac.roles', RBAC_PERMISSIONS: 'menu.rbac.permissions', REPORTS: 'menu.reports', SETTINGS: 'menu.settings' }, // Component permissions COMPONENT: { USER_EDIT_BUTTON: 'component.user.edit_button', USER_DELETE_BUTTON: 'component.user.delete_button', USER_BULK_ACTIONS: 'component.user.bulk_actions', PROFILE_SENSITIVE_INFO: 'component.profile.sensitive_info', FINANCIAL_DATA: 'component.financial.data', APPROVAL_WORKFLOW: 'component.approval.workflow' }, // Feature permissions FEATURE: { EXPORT_DATA: 'feature.export.data', APPROVE_REQUESTS: 'feature.approve.requests', SYSTEM_BACKUP: 'feature.system.backup', USER_IMPERSONATION: 'feature.user.impersonation', BULK_USER_OPERATIONS: 'feature.bulk.user_operations' } } // Common action types export const PERMISSION_ACTIONS = { VIEW: 'view', CREATE: 'create', EDIT: 'edit', DELETE: 'delete', APPROVE: 'approve', EXPORT: 'export', IMPORT: 'import' } // Helper function for reactive permission checking in templates export const useReactivePermission = (permissionKey, action = 'view') => { const { hasPermission } = useRbacPermissions() const isAllowed = ref(false) const isLoading = ref(true) const checkPermission = async () => { try { isLoading.value = true isAllowed.value = await hasPermission(permissionKey, action) } catch (error) { console.error('Permission check failed:', error) isAllowed.value = false } finally { isLoading.value = false } } // Check permission on component mount onMounted(checkPermission) return { isAllowed, isLoading, checkPermission } }