feat: add flexible capability-based RBAC with per-tenant customization
Introduces a capability layer on top of existing roles that controls feature visibility and access. Capabilities follow an area.feature.action taxonomy (~35 capabilities) with sensible defaults per role. Tenant admins can customize via grant/revoke overrides stored in org settings JSONB. Key changes: - Add vice_president role to DB schema - Backend: capability constants, resolution logic, CapabilityGuard (global), @RequireCapability decorator on all 16 tenant controllers - Frontend: permission hooks (useCanEdit, useHasCapability), CapabilityGate component, sidebar filtering by capability, all 17 pages migrated from useIsReadOnly to capability-based checks - New admin UI: /settings/permissions matrix page for per-tenant role customization with grant/revoke delta model - GET /organizations/my-capabilities endpoint for capability refresh - Validation of permissionOverrides in settings updates Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
44
frontend/src/permissions/useCapability.ts
Normal file
44
frontend/src/permissions/useCapability.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { useAuthStore } from '../stores/authStore';
|
||||
|
||||
/**
|
||||
* Check if the current user has a specific capability.
|
||||
* Superadmins always return true.
|
||||
*/
|
||||
export function useHasCapability(capability: string): boolean {
|
||||
const user = useAuthStore((s) => s.user);
|
||||
const capabilities = useAuthStore((s) => s.currentOrg?.capabilities);
|
||||
if (user?.isSuperadmin) return true;
|
||||
return capabilities?.includes(capability) ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current user has ANY of the given capabilities.
|
||||
* Superadmins always return true.
|
||||
*/
|
||||
export function useHasAnyCapability(...caps: string[]): boolean {
|
||||
const user = useAuthStore((s) => s.user);
|
||||
const capabilities = useAuthStore((s) => s.currentOrg?.capabilities);
|
||||
if (user?.isSuperadmin) return true;
|
||||
if (!capabilities) return false;
|
||||
return caps.some((c) => capabilities.includes(c));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current user has ALL of the given capabilities.
|
||||
* Superadmins always return true.
|
||||
*/
|
||||
export function useHasAllCapabilities(...caps: string[]): boolean {
|
||||
const user = useAuthStore((s) => s.user);
|
||||
const capabilities = useAuthStore((s) => s.currentOrg?.capabilities);
|
||||
if (user?.isSuperadmin) return true;
|
||||
if (!capabilities) return false;
|
||||
return caps.every((c) => capabilities.includes(c));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a specific capability string matches the user's capability for edit actions.
|
||||
* This replaces the old useIsReadOnly() for more granular checks.
|
||||
*/
|
||||
export function useCanEdit(editCapability: string): boolean {
|
||||
return useHasCapability(editCapability);
|
||||
}
|
||||
Reference in New Issue
Block a user