Files
bakery-ia/frontend/src/utils/permissions.ts
Claude 7741dd8067 Fix frontend build TypeScript errors
Fixed multiple TypeScript type errors that were preventing the build from working properly:

1. Fixed infinite query type issue in forecasting.ts by excluding 'select' from options
2. Fixed Card variant type errors by changing contentPadding="default" to contentPadding="md"
3. Fixed router export issues by removing non-existent exports (ROUTE_CONFIGS, getRoutesForRole, etc.)
4. Fixed router readonly array type issues by updating RouteConfig interface
5. Fixed ProtectedRoute requiredRoles prop issue by removing invalid prop usage
6. Fixed auth store User type compatibility by allowing null for tenant_id
7. Fixed missing useToasts export from ui.store by removing from exports
8. Fixed permissions utility boolean type issues by wrapping expressions in Boolean()

The frontend build now completes successfully.
2025-11-06 18:39:20 +00:00

381 lines
11 KiB
TypeScript

/**
* 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 || Boolean(
user?.is_active &&
globalRoles.some(role => hasGlobalRole(user.role, role))
);
// Check tenant roles
const hasTenantRoleAccess = tenantRoles.length === 0 || Boolean(
tenantAccess?.has_access &&
tenantRoles.some(role => hasTenantRole(tenantAccess.role, role))
);
// Check tenant permissions
const hasTenantPermissionAccess = tenantPermissions.length === 0 || Boolean(
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
);
}
}