@flusys/nestjs-iam
v1.0.0
Published
Identity and Access Management (IAM) module for NestJS applications
Maintainers
Readme
IAM Package Guide
Package:
@flusys/nestjs-iamPurpose: Identity and Access Management with RBAC, ABAC, and permission logic
Table of Contents
- Overview
- Installation
- Module Configuration
- Constants
- Core Concepts
- Entities
- Permission Modes
- Permission Logic
- Services
- Helpers
- REST API Endpoints
- Multi-Tenant Support
- Permission Guard Integration
- Best Practices
- API Reference
- Package Architecture
Overview
@flusys/nestjs-iam provides a comprehensive Identity and Access Management system:
- Hierarchical Actions - Organize permissions in tree structures
- Action Types - BACKEND (cached for guards), FRONTEND (returned to UI), BOTH
- RBAC - Role-Based Access Control with validation
- DIRECT - Direct user-to-action assignments with validation
- Permission Mode Validation - Throws
BadRequestExceptionwhen using wrong mode - Company/Branch Scoping - Multi-tenant permissions with three-level granularity
- Tenant-Aware Caching - Automatic cache invalidation with multi-tenant support
Package Hierarchy
@flusys/nestjs-core <- Foundation
|
@flusys/nestjs-shared <- Shared utilities
|
@flusys/nestjs-auth <- User/Company management
|
@flusys/nestjs-iam <- Permission management (THIS PACKAGE)Installation
npm install @flusys/nestjs-iam @flusys/nestjs-shared @flusys/nestjs-coreModule Configuration
With Auth Module (Recommended)
import { AuthModule } from '@flusys/nestjs-auth';
import { IAMModule } from '@flusys/nestjs-iam';
@Module({
imports: [
AuthModule.forRoot({
includeIAM: true, // Required for single-tenant mode
bootstrapAppConfig: {
databaseMode: 'single',
enableCompanyFeature: true,
},
config: { /* auth config */ },
}),
IAMModule.forRoot({
global: true,
includeController: true,
bootstrapAppConfig: {
databaseMode: 'single',
enableCompanyFeature: true,
permissionMode: 'FULL', // 'FULL' | 'RBAC' | 'DIRECT'
},
}),
],
})
export class AppModule {}Async Configuration
IAMModule.forRootAsync({
global: true,
includeController: true,
bootstrapAppConfig: {
databaseMode: 'single',
enableCompanyFeature: true,
permissionMode: 'RBAC',
},
imports: [ConfigModule],
useFactory: (config: ConfigService) => ({
// IAM-specific options from config
}),
inject: [ConfigService],
})Constants
// Injection Token
export const IAM_MODULE_OPTIONS = 'IAM_MODULE_OPTIONS';Core Concepts
Actions
Actions represent permissions in the system:
| Field | Description |
|-------|-------------|
| name | Human-readable name |
| code | Unique identifier (e.g., user.create) |
| actionType | Category: backend, frontend, or both |
| metadata | Additional data (icon, routerLink for frontend) |
| parentId | Parent action for hierarchy |
| serial | Display order |
Action Types
enum ActionType {
BACKEND = 'backend', // API endpoint permissions (cached for PermissionGuard)
FRONTEND = 'frontend', // UI features (returned in my-permissions API)
BOTH = 'both', // Both backend and frontend
}Roles
Roles are collections of actions:
| Field | Description |
|-------|-------------|
| name | Human-readable name |
| description | Role description |
| companyId | Company scope (when company feature enabled) |
| isActive | Active status |
Permission Types
enum IamPermissionType {
USER_ROLE = 'user_role', // User assigned to role
ROLE_ACTION = 'role_action', // Role has action
USER_ACTION = 'user_action', // User has action directly
COMPANY_ACTION = 'company_action', // Company action whitelist
}Permission Resolution Order
- Company-Action Whitelist - Filter by company (if enabled)
- UserAction (DENY) - Explicit denials take precedence
- UserAction (GRANT) - Explicit grants
- UserRole -> RoleAction - Inherited from roles
- Action Permission Logic - Complex AND/OR rules
Permission Cascade Deletion
When removing company actions via removeCompanyActionsWithCascade(), the PermissionService performs cascade deletion:
Company Action Removed
↓
1. Delete COMPANY_ACTION permissions
↓
2. Find all roles in the company
↓
3. Delete ROLE_ACTION permissions for those actions
↓
4. Find all users in the company
↓
5. Delete USER_ACTION permissions for those actions
↓
6. Invalidate permission cache for affected usersEntities
Entity Groups
// Core entities (no company feature)
export const IAMCoreEntities = [Action, Role, UserIamPermission];
// Company-specific entities
export const IAMCompanyEntities = [RoleWithCompany, UserIamPermissionWithCompany];
// All entities
export const IAMAllEntities = [
Action,
Role,
RoleWithCompany,
UserIamPermission,
UserIamPermissionWithCompany,
];
// Helper function
export function getIAMEntitiesByConfig(
enableCompanyFeature: boolean,
permissionMode: 'FULL' | 'RBAC' | 'DIRECT' = 'FULL',
): any[] {
const entities: any[] = [Action];
// Permission entity - always included
if (enableCompanyFeature) {
entities.push(UserIamPermissionWithCompany);
} else {
entities.push(UserIamPermission);
}
// Role entity - Only for RBAC or FULL mode (not DIRECT)
if (permissionMode === 'RBAC' || permissionMode === 'FULL') {
if (enableCompanyFeature) {
entities.push(RoleWithCompany);
} else {
entities.push(Role);
}
}
return entities;
}ActionBase
Core action fields in action-base.entity.ts:
| Column | Type | Description |
|--------|------|-------------|
| readOnly | boolean | System-protected action |
| name | varchar(255) | Action name |
| code | varchar(255) | Unique code |
| description | varchar(500) | Description |
| actionType | enum | BACKEND, FRONTEND, BOTH |
| permissionLogic | json | AND/OR rules |
| parentId | uuid | Parent action ID |
| serial | int | Display order |
| isActive | boolean | Active status |
| metadata | json | Additional data |
RoleBase
Core role fields in role-base.entity.ts:
| Column | Type | Description |
|--------|------|-------------|
| readOnly | boolean | System-protected role |
| name | varchar(255) | Role name |
| description | varchar(500) | Description |
| isActive | boolean | Active status |
| serial | int | Display order |
| metadata | json | Additional data |
RoleWithCompany
Extends RoleBase with:
companyId(uuid) - Company scope
PermissionBase
Core permission fields in permission-base.entity.ts:
| Column | Type | Description |
|--------|------|-------------|
| permissionType | enum | USER_ROLE, ROLE_ACTION, etc. |
| sourceType | enum | USER, ROLE, COMPANY |
| sourceId | uuid | Source entity ID |
| targetType | enum | ROLE, ACTION |
| targetId | uuid | Target entity ID |
| userId | uuid | User ID (nullable) |
| validFrom | timestamp | Permission valid from |
| validUntil | timestamp | Permission valid until |
| reason | text | Reason for permission |
| metadata | json | Additional data |
UserIamPermissionWithCompany
Extends PermissionBase with:
companyId(uuid) - Company scopebranchId(uuid) - Branch scope
Permission Granularity:
| Level | companyId | branchId | Use Case | |-------|-----------|----------|----------| | Global | null | null | Super admin across all companies | | Company-wide | set | null | Manager for ALL branches in company | | Branch-specific | set | set | Manager for specific branch only |
Permission Modes
Set in bootstrapAppConfig.permissionMode:
Permission Mode Enum
enum IAMPermissionMode {
RBAC = 1, // Role-Based: Action + RoleAction + UserRole
DIRECT = 2, // Direct User Permissions: Action + UserAction
FULL = 3, // Both RBAC + Direct permissions
}RBAC Mode (Role-Based)
User -> Role -> ActionOnly role-based permissions. Users get permissions through assigned roles.
Note: Calling assignUserActions() in RBAC mode throws BadRequestException.
DIRECT Mode (Action-Based)
User -> ActionOnly direct permissions. Users get actions assigned directly.
Note: Calling assignUserRoles() or assignRoleActions() in DIRECT mode throws BadRequestException.
FULL Mode (Combined)
User -> Role -> Action
User -> ActionBoth RBAC and direct permissions. Default mode. All assignment methods available.
Permission Logic
Complex permission rules using AND/OR operators stored in permissionLogic field:
interface LogicNode {
id: string;
type: 'group' | 'action';
operator?: 'AND' | 'OR';
children?: LogicNode[];
actionId?: string;
}Example: User must have "employee" AND ("department-head" OR "hr-clearance")
const logic: LogicNode = {
id: 'root',
type: 'group',
operator: 'AND',
children: [
{ id: '1', type: 'action', actionId: 'employee-action-id' },
{
id: '2',
type: 'group',
operator: 'OR',
children: [
{ id: '2-1', type: 'action', actionId: 'department-head' },
{ id: '2-2', type: 'action', actionId: 'hr-clearance' },
],
},
],
};Services
| Service | Scope | Description |
|---------|-------|-------------|
| ActionService | REQUEST | Action CRUD, tree queries |
| RoleService | REQUEST | Role CRUD (RBAC/FULL mode only) |
| PermissionService | REQUEST | Permission assignments, my-permissions |
| PermissionCacheService | REQUEST | Cache management |
| IAMConfigService | SINGLETON | IAM configuration access |
| IAMDataSourceService | REQUEST | DataSource provider for multi-tenant |
ActionService
import { ActionService } from '@flusys/nestjs-iam';
// Create action
await actionService.insert({
name: 'View Users',
code: 'user.view',
actionType: ActionType.BACKEND,
parentId: parentAction.id,
});
// Get hierarchical tree
const tree = await actionService.getActionTree(user, 'search', true, false);
// Get actions for permission (company-filtered)
const actions = await actionService.getActionsForPermission(user);RoleService
import { RoleService } from '@flusys/nestjs-iam';
// Create role
await roleService.insert({
name: 'Administrator',
description: 'Full system access',
companyId: 'company-id',
});PermissionService
import { PermissionService, PermissionAction } from '@flusys/nestjs-iam';
// Assign roles to user (RBAC/FULL mode only)
await permissionService.assignUserRoles({
userId: 'user-id',
companyId: 'company-id',
branchId: null, // null = company-wide
items: [{ id: 'role-id', action: PermissionAction.ADD }],
});
// Assign actions directly (DIRECT/FULL mode only)
await permissionService.assignUserActions({
userId: 'user-id',
companyId: 'company-id',
branchId: 'branch-id',
items: [{ id: 'action-id', action: PermissionAction.ADD }],
});
// Assign actions to role (RBAC/FULL mode only)
await permissionService.assignRoleActions({
roleId: 'role-id',
items: [{ id: 'action-id', action: PermissionAction.ADD }],
});
// Get user's permissions
const permissions = await permissionService.getMyPermissions(
userId, branchId, companyId, parentCodes
);PermissionCacheService
import { PermissionCacheService } from '@flusys/nestjs-iam';
// Set permissions
await cacheService.setPermissions(
{ userId, companyId, branchId, enableCompanyFeature: true },
['users.read', 'users.write']
);
// Invalidate user cache
await cacheService.invalidateUser('user-id', 'company-id', ['branch-id']);
// Invalidate role members
await cacheService.invalidateRole('role-id', userIds, 'company-id');
// Tenant-aware action code cache
await cacheService.setActionCodeMap(codeToIdMap, tenantId);
await cacheService.getActionIdsByCodes(['user.view'], tenantId);
await cacheService.invalidateActionCodeCache(tenantId);Cache Key Formats:
permissions:user:{userId} // Without company
permissions:company:{companyId}:branch:{branchId}:user:{userId} // With company
action-codes:map // Single tenant
action-codes:tenant:{tenantId}:map // Multi-tenantHelpers
PermissionModeHelper
Centralizes conversion from string to enum to prevent duplication:
import { PermissionModeHelper } from '@flusys/nestjs-iam';
// Convert string to enum
const mode = PermissionModeHelper.fromString('RBAC');
// Returns: IAMPermissionMode.RBAC
const mode = PermissionModeHelper.fromString(undefined);
// Returns: IAMPermissionMode.FULL (default)
// Convert enum to string
const str = PermissionModeHelper.toString(IAMPermissionMode.RBAC);
// Returns: 'RBAC'Used by:
iam.module.ts(forRoot, forRootAsync)iam-config.service.ts(getPermissionMode)main.ts(Swagger setup)
validateCompanyAccess
Validates user access to a company for permission operations:
import { validateCompanyAccess } from '@flusys/nestjs-iam';
// In a controller or service
validateCompanyAccess(
iamConfig,
dto.companyId,
user,
'You do not have access to this company',
);
// Throws ForbiddenException if user.companyId !== dto.companyIdREST API Endpoints
Actions API
| Endpoint | Method | Description |
|----------|--------|-------------|
| /iam/actions/insert | POST | Create action |
| /iam/actions/get-all | POST | List actions (paginated) |
| /iam/actions/get/:id | GET | Get action by ID |
| /iam/actions/tree | POST | Get hierarchical tree (ALL actions) |
| /iam/actions/tree-for-permission | GET | Get company-filtered actions |
| /iam/actions/update | POST | Update action |
| /iam/actions/delete | POST | Delete action |
Roles API (RBAC/FULL mode only)
| Endpoint | Method | Description |
|----------|--------|-------------|
| /iam/roles/insert | POST | Create role |
| /iam/roles/get-all | POST | List roles (paginated) |
| /iam/roles/get/:id | GET | Get role by ID |
| /iam/roles/update | POST | Update role |
| /iam/roles/delete | POST | Delete role |
Permissions API
| Endpoint | Method | Modes | Description |
|----------|--------|-------|-------------|
| /iam/permissions/role-actions/assign | POST | RBAC, FULL | Assign actions to role |
| /iam/permissions/role-actions/get | POST | RBAC, FULL | Get role actions |
| /iam/permissions/user-roles/assign | POST | RBAC, FULL | Assign roles to user |
| /iam/permissions/user-roles/get | POST | RBAC, FULL | Get user roles |
| /iam/permissions/user-actions/assign | POST | DIRECT, FULL | Assign actions to user |
| /iam/permissions/user-actions/get | POST | DIRECT, FULL | Get user actions |
| /iam/permissions/company-actions/assign | POST | All (company enabled) | Company action whitelist |
| /iam/permissions/company-actions/get | POST | All (company enabled) | Get company actions |
| /iam/permissions/my-permissions | POST | All | Get current user's permissions |
Controller Registration by Mode:
| Mode | Controllers | |------|-------------| | DIRECT | ActionController, MyPermissionController, UserActionPermissionController | | RBAC | ActionController, MyPermissionController, RoleController, RolePermissionController | | FULL | All controllers |
Multi-Tenant Support
Company Feature Toggle
// config/app.config.ts
export const bootstrapAppConfig: IBootstrapAppConfig = {
databaseMode: 'single', // or 'multi-tenant'
enableCompanyFeature: true,
permissionMode: 'FULL',
};When enableCompanyFeature: true
companyIdandbranchIdfields available in DTOs- Company-Action Permissions controller registered
- Three-level permission granularity (Global, Company-wide, Branch-specific)
- Uses
UserIamPermissionWithCompanyandRoleWithCompanyentities
When enableCompanyFeature: false
- Company endpoints NOT available (404)
companyId/branchIdfields visible in Swagger but ignored- All permissions are global
- Uses
UserIamPermissionandRoleentities
Multi-Tenant Database Mode
When databaseMode: 'multi-tenant':
- Each tenant has isolated database/schema
- Action code cache is tenant-aware (separate cache per tenant)
- Uses
IAMDataSourceServicefor tenant-specific connections
Permission Merging
When getMyPermissions is called:
With branchId specified:
- Company-wide roles (branchId=null) + Branch-specific roles for that branch
- Actions from all merged roles
- Company-wide user actions + Branch-specific user actions for that branch
Without branchId (null):
- ALL roles for the user in the company (any branch)
- Actions from all roles
- ALL user actions for the company (any branch)
Result: Complete permission set without duplicates, filtered by company whitelist.
Permission Guard Integration
import { PermissionGuard } from '@flusys/nestjs-shared/guards';
import { RequirePermission, RequireAnyPermission } from '@flusys/nestjs-shared/decorators';
@Controller('users')
export class UserController {
@RequirePermission('user.view')
@Get()
getUsers() {}
@RequireAnyPermission('user.create', 'admin')
@Post()
createUser() {}
}Best Practices
1. Action Code Naming
// Hierarchical naming for backend
'user.view', 'user.create', 'user.delete'
// UPPERCASE for frontend actions
'MENU_USERS', 'MENU_REPORTS', 'FEATURE_EXPORT'2. Company-wide vs Branch-specific
// Company-wide for managers across ALL branches
{ companyId: 'company-a', branchId: null }
// Branch-specific for location-bound staff
{ companyId: 'company-a', branchId: 'branch-x' }3. Use Roles for Common Patterns
// Create roles for common permission sets
const roles = [
{ name: 'Viewer', actions: ['*.view'] },
{ name: 'Editor', actions: ['*.view', '*.create', '*.update'] },
{ name: 'Admin', actions: ['*'] },
];4. Use Direct Actions Sparingly
Direct actions should be exceptions and branch-specific overrides, not the primary permission mechanism.
API Reference
Exports
// Config
export { IAM_MODULE_OPTIONS } from './config';
// Modules
export { IAMModule } from './modules';
// Entities
export { Action } from './entities/action.entity';
export { ActionBase } from './entities/action-base.entity';
export { Role } from './entities/role.entity';
export { RoleBase } from './entities/role-base.entity';
export { RoleWithCompany } from './entities/role-with-company.entity';
export { PermissionBase } from './entities/permission-base.entity';
export { UserIamPermission } from './entities/user-iam-permission.entity';
export { UserIamPermissionWithCompany } from './entities/permission-with-company.entity';
export { IAMCoreEntities, IAMCompanyEntities, IAMAllEntities, getIAMEntitiesByConfig } from './entities';
// Services
export { ActionService } from './services/action.service';
export { RoleService } from './services/role.service';
export { PermissionService } from './services/permission.service';
export { PermissionCacheService } from './services/permission-cache.service';
export { IAMConfigService } from './services/iam-config.service';
export { IAMDataSourceService } from './services/iam-datasource.service';
// Helpers
export { PermissionModeHelper } from './helpers/permission-mode.helper';
export { validateCompanyAccess } from './helpers/company-access.helper';
// Enums
export { ActionType } from './enums/action-type.enum';
export { IAMPermissionMode } from './enums/permission-type.enum';
// Types
export { LogicNode } from './types/logic-node.type';
// DTOs
export * from './dtos';
// Interfaces
export * from './interfaces';
// Swagger Config
export { iamSwaggerConfig } from './docs';Module Static Methods
// Configure module
IAMModule.forRoot(options?: IAMModuleOptions): DynamicModule
IAMModule.forRootAsync(options: IAMModuleAsyncOptions): DynamicModule
IAMModule.forFeature(options?: IAMModuleOptions): DynamicModulePackage Architecture
nestjs-iam/src/
├── config/
│ ├── iam.constants.ts # IAM_MODULE_OPTIONS token
│ └── index.ts
├── modules/
│ └── iam.module.ts # Main module with dynamic config
├── entities/
│ ├── action-base.entity.ts # Action base fields
│ ├── action.entity.ts # Action entity
│ ├── role-base.entity.ts # Role base fields
│ ├── role.entity.ts # Role (no company)
│ ├── role-with-company.entity.ts
│ ├── permission-base.entity.ts # Permission base fields
│ ├── user-iam-permission.entity.ts
│ ├── permission-with-company.entity.ts
│ └── index.ts # Entity groups & getIAMEntitiesByConfig
├── services/
│ ├── action.service.ts # Action CRUD
│ ├── role.service.ts # Role CRUD
│ ├── permission.service.ts # Permission management
│ ├── permission-cache.service.ts # Cache management
│ ├── iam-config.service.ts # Configuration
│ └── iam-datasource.service.ts # DataSource provider
├── controllers/
│ ├── action.controller.ts
│ ├── role.controller.ts
│ ├── my-permission.controller.ts
│ ├── role-permission.controller.ts
│ ├── user-action-permission.controller.ts
│ └── company-action-permission.controller.ts
├── helpers/
│ ├── company-access.helper.ts # Company access validation
│ └── permission-mode.helper.ts # String to enum conversion
├── dtos/
│ ├── action.dto.ts
│ ├── role.dto.ts
│ └── permission.dto.ts
├── interfaces/
│ ├── action.interface.ts
│ ├── role.interface.ts
│ └── iam-module-options.interface.ts
├── enums/
│ ├── action-type.enum.ts
│ └── permission-type.enum.ts
├── types/
│ └── logic-node.type.ts
├── docs/
│ └── iam-swagger.config.ts
└── index.tsLast Updated: 2026-02-21
