corrad-af-2024/composables/useRbacPermissions.js
Afiq f05dd42c16 Enhance README and implement RBAC system with Authentik integration
- Updated README.md to reflect the new project name and provide an overview of the Role-Based Access Control (RBAC) system.
- Added new components for RBAC management, including:
  - PermissionExample.vue: Demonstrates permission-based navigation.
  - GroupCard.vue: Displays group information and assigned roles.
  - PermissionMatrix.vue: Visual representation of permissions across roles and resources.
  - RoleTemplates.vue: Quick role templates for applying pre-configured permissions.
  - StatsCards.vue: Displays statistics related to users, groups, and roles.
- Introduced useRbacPermissions.js for managing permission checks.
- Created docker-compose.yml for PostgreSQL and Redis services.
- Developed comprehensive documentation for application management and Authentik integration.
- Added multiple pages for managing applications, groups, roles, and users, including bulk operations and templates.
- Updated navigation structure to include new RBAC management paths.
2025-05-31 15:58:41 +08:00

221 lines
6.7 KiB
JavaScript

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<boolean>} - 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>} - 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<boolean>}
*/
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<boolean>}
*/
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<boolean>}
*/
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 }
}