Improve the frontend 4

This commit is contained in:
Urtzi Alfaro
2025-11-01 21:35:03 +01:00
parent f44d235c6d
commit 0220da1725
59 changed files with 5785 additions and 1870 deletions

View 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
);
}
}