/** * 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 ); } }