Improve the frontend 4
This commit is contained in:
380
frontend/src/utils/permissions.ts
Normal file
380
frontend/src/utils/permissions.ts
Normal file
@@ -0,0 +1,380 @@
|
||||
/**
|
||||
* Unified Permission Checking Utility
|
||||
*
|
||||
* This module provides a centralized system for checking permissions across
|
||||
* both global user roles and tenant-specific roles.
|
||||
*
|
||||
* WHEN TO USE WHICH PERMISSION CHECK:
|
||||
*
|
||||
* 1. Use checkGlobalPermission() for:
|
||||
* - Platform-wide features (user management, system settings)
|
||||
* - Cross-tenant operations
|
||||
* - Administrative tools
|
||||
* - Features that aren't tenant-specific
|
||||
*
|
||||
* 2. Use checkTenantPermission() for:
|
||||
* - Tenant-scoped operations (team management, tenant settings)
|
||||
* - Resource access within a tenant (orders, inventory, recipes)
|
||||
* - Organization-specific features
|
||||
* - Most application features
|
||||
*
|
||||
* 3. Use checkCombinedPermission() for:
|
||||
* - Features that require EITHER global OR tenant permissions
|
||||
* - Mixed access scenarios (e.g., super admin OR tenant owner)
|
||||
* - Fallback permission checks
|
||||
*
|
||||
* EXAMPLES:
|
||||
*
|
||||
* // Check if user can manage platform users (global only)
|
||||
* checkGlobalPermission(user, { requiredRole: 'admin' })
|
||||
*
|
||||
* // Check if user can manage tenant team (tenant only)
|
||||
* checkTenantPermission(tenantAccess, { requiredRole: 'owner' })
|
||||
*
|
||||
* // Check if user can access a feature (either global admin or tenant owner)
|
||||
* checkCombinedPermission(user, tenantAccess, {
|
||||
* globalRoles: ['admin', 'super_admin'],
|
||||
* tenantRoles: ['owner']
|
||||
* })
|
||||
*/
|
||||
|
||||
import {
|
||||
GLOBAL_USER_ROLES,
|
||||
TENANT_ROLES,
|
||||
ROLE_HIERARCHY,
|
||||
hasGlobalRole,
|
||||
hasTenantRole,
|
||||
type GlobalUserRole,
|
||||
type TenantRole,
|
||||
type Role
|
||||
} from '../types/roles';
|
||||
|
||||
/**
|
||||
* User object structure (from Auth service)
|
||||
*/
|
||||
export interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
role: GlobalUserRole;
|
||||
full_name?: string;
|
||||
is_active: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tenant access object structure (from Tenant service)
|
||||
*/
|
||||
export interface TenantAccess {
|
||||
has_access: boolean;
|
||||
role: TenantRole;
|
||||
permissions: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission check options for global permissions
|
||||
*/
|
||||
export interface GlobalPermissionOptions {
|
||||
requiredRole: GlobalUserRole;
|
||||
allowHigherRoles?: boolean; // Default: true
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission check options for tenant permissions
|
||||
*/
|
||||
export interface TenantPermissionOptions {
|
||||
requiredRole?: TenantRole;
|
||||
requiredPermission?: string;
|
||||
allowHigherRoles?: boolean; // Default: true
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission check options for combined (global + tenant) permissions
|
||||
*/
|
||||
export interface CombinedPermissionOptions {
|
||||
globalRoles?: GlobalUserRole[];
|
||||
tenantRoles?: TenantRole[];
|
||||
tenantPermissions?: string[];
|
||||
requireBoth?: boolean; // Default: false (OR logic), true for AND logic
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user has a specific global permission
|
||||
*
|
||||
* @param user - User object from auth store
|
||||
* @param options - Permission requirements
|
||||
* @returns true if user has the required global permission
|
||||
*
|
||||
* @example
|
||||
* // Check if user is an admin
|
||||
* checkGlobalPermission(user, { requiredRole: 'admin' })
|
||||
*
|
||||
* // Check if user is exactly a manager (no higher roles)
|
||||
* checkGlobalPermission(user, { requiredRole: 'manager', allowHigherRoles: false })
|
||||
*/
|
||||
export function checkGlobalPermission(
|
||||
user: User | null | undefined,
|
||||
options: GlobalPermissionOptions
|
||||
): boolean {
|
||||
if (!user || !user.is_active) return false;
|
||||
|
||||
const { requiredRole, allowHigherRoles = true } = options;
|
||||
|
||||
if (allowHigherRoles) {
|
||||
// Check if user has the required role or higher
|
||||
return hasGlobalRole(user.role, requiredRole);
|
||||
} else {
|
||||
// Check for exact role match
|
||||
return user.role === requiredRole;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user has a specific tenant permission
|
||||
*
|
||||
* @param tenantAccess - Tenant access object from tenant store
|
||||
* @param options - Permission requirements
|
||||
* @returns true if user has the required tenant permission
|
||||
*
|
||||
* @example
|
||||
* // Check if user is a tenant owner
|
||||
* checkTenantPermission(tenantAccess, { requiredRole: 'owner' })
|
||||
*
|
||||
* // Check if user has a specific permission
|
||||
* checkTenantPermission(tenantAccess, { requiredPermission: 'manage_team' })
|
||||
*
|
||||
* // Check if user is exactly an admin (no higher roles)
|
||||
* checkTenantPermission(tenantAccess, { requiredRole: 'admin', allowHigherRoles: false })
|
||||
*/
|
||||
export function checkTenantPermission(
|
||||
tenantAccess: TenantAccess | null | undefined,
|
||||
options: TenantPermissionOptions
|
||||
): boolean {
|
||||
if (!tenantAccess || !tenantAccess.has_access) return false;
|
||||
|
||||
const { requiredRole, requiredPermission, allowHigherRoles = true } = options;
|
||||
|
||||
// Check role-based permission
|
||||
if (requiredRole) {
|
||||
if (allowHigherRoles) {
|
||||
if (!hasTenantRole(tenantAccess.role, requiredRole)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (tenantAccess.role !== requiredRole) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check specific permission
|
||||
if (requiredPermission) {
|
||||
if (!tenantAccess.permissions?.includes(requiredPermission)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check combined global and tenant permissions
|
||||
*
|
||||
* @param user - User object from auth store
|
||||
* @param tenantAccess - Tenant access object from tenant store
|
||||
* @param options - Permission requirements
|
||||
* @returns true if user meets the permission criteria
|
||||
*
|
||||
* @example
|
||||
* // Check if user is either global admin OR tenant owner (OR logic)
|
||||
* checkCombinedPermission(user, tenantAccess, {
|
||||
* globalRoles: ['admin', 'super_admin'],
|
||||
* tenantRoles: ['owner']
|
||||
* })
|
||||
*
|
||||
* // Check if user is global admin AND tenant member (AND logic)
|
||||
* checkCombinedPermission(user, tenantAccess, {
|
||||
* globalRoles: ['admin'],
|
||||
* tenantRoles: ['member', 'admin', 'owner'],
|
||||
* requireBoth: true
|
||||
* })
|
||||
*/
|
||||
export function checkCombinedPermission(
|
||||
user: User | null | undefined,
|
||||
tenantAccess: TenantAccess | null | undefined,
|
||||
options: CombinedPermissionOptions
|
||||
): boolean {
|
||||
const {
|
||||
globalRoles = [],
|
||||
tenantRoles = [],
|
||||
tenantPermissions = [],
|
||||
requireBoth = false
|
||||
} = options;
|
||||
|
||||
// Check global roles
|
||||
const hasGlobalAccess = globalRoles.length === 0 || (
|
||||
user?.is_active &&
|
||||
globalRoles.some(role => hasGlobalRole(user.role, role))
|
||||
);
|
||||
|
||||
// Check tenant roles
|
||||
const hasTenantRoleAccess = tenantRoles.length === 0 || (
|
||||
tenantAccess?.has_access &&
|
||||
tenantRoles.some(role => hasTenantRole(tenantAccess.role, role))
|
||||
);
|
||||
|
||||
// Check tenant permissions
|
||||
const hasTenantPermissionAccess = tenantPermissions.length === 0 || (
|
||||
tenantAccess?.has_access &&
|
||||
tenantPermissions.some(perm => tenantAccess.permissions?.includes(perm))
|
||||
);
|
||||
|
||||
// Combine tenant role and permission checks (must pass at least one)
|
||||
const hasTenantAccess = hasTenantRoleAccess || hasTenantPermissionAccess;
|
||||
|
||||
if (requireBoth) {
|
||||
// AND logic: must have both global and tenant access
|
||||
return hasGlobalAccess && hasTenantAccess;
|
||||
} else {
|
||||
// OR logic: must have either global or tenant access
|
||||
return hasGlobalAccess || hasTenantAccess;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user can manage team members
|
||||
*
|
||||
* @param user - User object
|
||||
* @param tenantAccess - Tenant access object
|
||||
* @returns true if user can manage team
|
||||
*
|
||||
* @example
|
||||
* canManageTeam(user, tenantAccess)
|
||||
*/
|
||||
export function canManageTeam(
|
||||
user: User | null | undefined,
|
||||
tenantAccess: TenantAccess | null | undefined
|
||||
): boolean {
|
||||
return checkCombinedPermission(user, tenantAccess, {
|
||||
globalRoles: [GLOBAL_USER_ROLES.ADMIN, GLOBAL_USER_ROLES.SUPER_ADMIN],
|
||||
tenantRoles: [TENANT_ROLES.OWNER, TENANT_ROLES.ADMIN]
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user is tenant owner
|
||||
*
|
||||
* @param user - User object
|
||||
* @param tenantAccess - Tenant access object
|
||||
* @returns true if user is owner
|
||||
*
|
||||
* @example
|
||||
* isTenantOwner(user, tenantAccess)
|
||||
*/
|
||||
export function isTenantOwner(
|
||||
user: User | null | undefined,
|
||||
tenantAccess: TenantAccess | null | undefined
|
||||
): boolean {
|
||||
return checkCombinedPermission(user, tenantAccess, {
|
||||
globalRoles: [GLOBAL_USER_ROLES.SUPER_ADMIN], // Super admin can act as owner
|
||||
tenantRoles: [TENANT_ROLES.OWNER]
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user can perform administrative actions
|
||||
*
|
||||
* @param user - User object
|
||||
* @returns true if user has admin access
|
||||
*
|
||||
* @example
|
||||
* canPerformAdminActions(user)
|
||||
*/
|
||||
export function canPerformAdminActions(
|
||||
user: User | null | undefined
|
||||
): boolean {
|
||||
return checkGlobalPermission(user, {
|
||||
requiredRole: GLOBAL_USER_ROLES.ADMIN
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's effective permissions for a tenant
|
||||
*
|
||||
* @param user - User object
|
||||
* @param tenantAccess - Tenant access object
|
||||
* @returns Object with permission flags
|
||||
*
|
||||
* @example
|
||||
* const perms = getEffectivePermissions(user, tenantAccess)
|
||||
* if (perms.canManageTeam) { ... }
|
||||
*/
|
||||
export function getEffectivePermissions(
|
||||
user: User | null | undefined,
|
||||
tenantAccess: TenantAccess | null | undefined
|
||||
) {
|
||||
return {
|
||||
// Global permissions
|
||||
isGlobalAdmin: checkGlobalPermission(user, { requiredRole: GLOBAL_USER_ROLES.ADMIN }),
|
||||
isSuperAdmin: checkGlobalPermission(user, { requiredRole: GLOBAL_USER_ROLES.SUPER_ADMIN }),
|
||||
isManager: checkGlobalPermission(user, { requiredRole: GLOBAL_USER_ROLES.MANAGER }),
|
||||
|
||||
// Tenant permissions
|
||||
isTenantOwner: checkTenantPermission(tenantAccess, { requiredRole: TENANT_ROLES.OWNER }),
|
||||
isTenantAdmin: checkTenantPermission(tenantAccess, { requiredRole: TENANT_ROLES.ADMIN }),
|
||||
isTenantMember: checkTenantPermission(tenantAccess, { requiredRole: TENANT_ROLES.MEMBER }),
|
||||
isTenantViewer: checkTenantPermission(tenantAccess, { requiredRole: TENANT_ROLES.VIEWER }),
|
||||
|
||||
// Combined permissions
|
||||
canManageTeam: canManageTeam(user, tenantAccess),
|
||||
canTransferOwnership: isTenantOwner(user, tenantAccess),
|
||||
canPerformAdminActions: canPerformAdminActions(user),
|
||||
|
||||
// Access flags
|
||||
hasGlobalAccess: !!user?.is_active,
|
||||
hasTenantAccess: !!tenantAccess?.has_access,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission validation error types
|
||||
*/
|
||||
export class PermissionError extends Error {
|
||||
constructor(message: string, public readonly requiredPermissions: string[]) {
|
||||
super(message);
|
||||
this.name = 'PermissionError';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that user has required permissions, throw error if not
|
||||
*
|
||||
* @param user - User object
|
||||
* @param tenantAccess - Tenant access object
|
||||
* @param options - Permission requirements
|
||||
* @throws PermissionError if user lacks required permissions
|
||||
*
|
||||
* @example
|
||||
* assertPermission(user, tenantAccess, {
|
||||
* tenantRoles: ['owner'],
|
||||
* errorMessage: 'Only tenant owners can perform this action'
|
||||
* })
|
||||
*/
|
||||
export function assertPermission(
|
||||
user: User | null | undefined,
|
||||
tenantAccess: TenantAccess | null | undefined,
|
||||
options: CombinedPermissionOptions & { errorMessage?: string }
|
||||
): void {
|
||||
const hasPermission = checkCombinedPermission(user, tenantAccess, options);
|
||||
|
||||
if (!hasPermission) {
|
||||
const requiredPerms = [
|
||||
...(options.globalRoles || []),
|
||||
...(options.tenantRoles || []),
|
||||
...(options.tenantPermissions || [])
|
||||
];
|
||||
|
||||
throw new PermissionError(
|
||||
options.errorMessage || 'You do not have permission to perform this action',
|
||||
requiredPerms
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user