@flusys/nestjs-iam 4.0.1 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,55 +1,84 @@
1
- # IAM Package Guide
1
+ # @flusys/nestjs-iam
2
2
 
3
- > **Package:** `@flusys/nestjs-iam`
4
- > **Version:** 4.0.1
5
- > **Purpose:** Identity and Access Management with RBAC, ABAC, and permission logic
3
+ > Identity and Access Management for NestJS — RBAC, DIRECT, and FULL permission modes with hierarchical actions, company scoping, intelligent 1-hour caching, and multi-tenant support.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@flusys/nestjs-iam.svg)](https://www.npmjs.com/package/@flusys/nestjs-iam)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+ [![NestJS](https://img.shields.io/badge/NestJS-11.x-red.svg)](https://nestjs.com/)
8
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue.svg)](https://www.typescriptlang.org/)
9
+ [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D18.x-green.svg)](https://nodejs.org/)
10
+
11
+ ---
6
12
 
7
13
  ## Table of Contents
8
14
 
9
15
  - [Overview](#overview)
16
+ - [Features](#features)
17
+ - [Permission Modes](#permission-modes)
18
+ - [Compatibility](#compatibility)
10
19
  - [Installation](#installation)
11
- - [Module Configuration](#module-configuration)
12
- - [Constants](#constants)
13
- - [Core Concepts](#core-concepts)
14
- - [Interfaces](#interfaces)
20
+ - [Quick Start](#quick-start)
21
+ - [Module Registration](#module-registration)
22
+ - [forRoot (Sync)](#forroot-sync)
23
+ - [forRootAsync (Factory)](#forrootasync-factory)
24
+ - [Configuration Reference](#configuration-reference)
25
+ - [Feature Toggles](#feature-toggles)
26
+ - [API Endpoints](#api-endpoints)
15
27
  - [Entities](#entities)
16
- - [Permission Modes](#permission-modes)
17
- - [Permission Logic](#permission-logic)
18
- - [Services](#services)
19
- - [Helpers](#helpers)
20
- - [REST API Endpoints](#rest-api-endpoints)
21
- - [Multi-Tenant Support](#multi-tenant-support)
22
- - [Permission Guard Integration](#permission-guard-integration)
23
- - [Best Practices](#best-practices)
24
- - [DTOs Reference](#dtos-reference)
25
- - [API Reference](#api-reference)
26
- - [Package Architecture](#package-architecture)
28
+ - [Permission Cache](#permission-cache)
29
+ - [Exported Services](#exported-services)
30
+ - [Using Permissions in Controllers](#using-permissions-in-controllers)
31
+ - [Action Types](#action-types)
32
+ - [Hierarchical Actions](#hierarchical-actions)
33
+ - [Troubleshooting](#troubleshooting)
34
+ - [License](#license)
27
35
 
28
36
  ---
29
37
 
30
38
  ## Overview
31
39
 
32
- `@flusys/nestjs-iam` provides a comprehensive Identity and Access Management system:
40
+ `@flusys/nestjs-iam` is a complete Identity and Access Management system. It supports three permission modes — RBAC (role-based), DIRECT (user-to-action), and FULL (both simultaneously). Permissions are cached per user+company+branch for 1 hour and automatically invalidated on changes.
33
41
 
34
- - **Hierarchical Actions** - Organize permissions in tree structures
35
- - **Action Types** - BACKEND (cached for guards), FRONTEND (returned to UI), BOTH
36
- - **RBAC** - Role-Based Access Control with validation
37
- - **DIRECT** - Direct user-to-action assignments with validation
38
- - **Permission Mode Validation** - Throws `BadRequestException` when using wrong mode
39
- - **Company/Branch Scoping** - Multi-tenant permissions with three-level granularity
40
- - **Tenant-Aware Caching** - Automatic cache invalidation with multi-tenant support
42
+ The module is fully independent from `nestjs-auth` it only depends on `nestjs-core` and `nestjs-shared`. Integration with auth is done via the `USER_ENRICHER` provider interface in `nestjs-auth`.
41
43
 
42
- ### Package Hierarchy
44
+ ---
43
45
 
44
- ```
45
- @flusys/nestjs-core <- Foundation
46
- |
47
- @flusys/nestjs-shared <- Shared utilities
48
- |
49
- @flusys/nestjs-auth <- User/Company management
50
- |
51
- @flusys/nestjs-iam <- Permission management (THIS PACKAGE)
52
- ```
46
+ ## Features
47
+
48
+ - **RBAC Mode** — Users inherit permissions through Roles → Actions assignments
49
+ - **DIRECT Mode** — Users are assigned directly to Actions without roles
50
+ - **FULL Mode** — Both RBAC and DIRECT active simultaneously (union of permissions)
51
+ - **Hierarchical Actions** — Actions form parent-child trees with `parentId`; tree queries supported
52
+ - **Action Types** — `BACKEND` (API guard only), `FRONTEND` (returned to client), `BOTH`
53
+ - **Company Whitelist** `COMPANY_ACTION` records control which actions are available per company
54
+ - **Permission Logic** — Complex AND/OR nested logic on `@RequirePermission()` decorator
55
+ - **1-Hour Cache** — Permissions cached per `userId + companyId + branchId`, auto-invalidated
56
+ - **Menu Management** — Navigation menu items with per-item action requirements
57
+ - **Multi-tenant** — Isolated DataSource Provider per request
58
+
59
+ ---
60
+
61
+ ## Permission Modes
62
+
63
+ | Mode | How it works | Controllers loaded |
64
+ |------|--------------|--------------------|
65
+ | `RBAC` | User → Role → Action | ActionController, RoleController, RolePermissionController, MyPermissionController |
66
+ | `DIRECT` | User → Action (direct) | ActionController, UserActionPermissionController, MyPermissionController |
67
+ | `FULL` | RBAC + DIRECT (union) | All above controllers |
68
+
69
+ `FULL` is the default when `permissionMode` is not specified.
70
+
71
+ ---
72
+
73
+ ## Compatibility
74
+
75
+ | Package | Version |
76
+ |---------|---------|
77
+ | `@flusys/nestjs-core` | `^4.0.0` |
78
+ | `@flusys/nestjs-shared` | `^4.0.0` |
79
+ | `@nestjs/core` | `^11.0.0` |
80
+ | `typeorm` | `^0.3.0` |
81
+ | Node.js | `>= 18.x` |
53
82
 
54
83
  ---
55
84
 
@@ -61,32 +90,33 @@ npm install @flusys/nestjs-iam @flusys/nestjs-shared @flusys/nestjs-core
61
90
 
62
91
  ---
63
92
 
64
- ## Module Configuration
93
+ ## Quick Start
65
94
 
66
- ### With Auth Module (Recommended)
95
+ ### RBAC Mode
67
96
 
68
97
  ```typescript
69
- import { AuthModule } from '@flusys/nestjs-auth';
98
+ import { Module } from '@nestjs/common';
70
99
  import { IAMModule } from '@flusys/nestjs-iam';
71
100
 
72
101
  @Module({
73
102
  imports: [
74
- AuthModule.forRoot({
75
- includeIAM: true, // Required for single-tenant mode
76
- bootstrapAppConfig: {
77
- databaseMode: 'single',
78
- enableCompanyFeature: true,
79
- },
80
- config: { /* auth config */ },
81
- }),
82
-
83
103
  IAMModule.forRoot({
84
104
  global: true,
85
105
  includeController: true,
86
106
  bootstrapAppConfig: {
87
107
  databaseMode: 'single',
88
- enableCompanyFeature: true,
89
- permissionMode: 'FULL', // 'FULL' | 'RBAC' | 'DIRECT'
108
+ enableCompanyFeature: false,
109
+ permissionMode: 'RBAC',
110
+ },
111
+ config: {
112
+ defaultDatabaseConfig: {
113
+ type: 'postgres',
114
+ host: process.env.DB_HOST,
115
+ port: Number(process.env.DB_PORT ?? 5432),
116
+ username: process.env.DB_USER,
117
+ password: process.env.DB_PASSWORD,
118
+ database: process.env.DB_NAME,
119
+ },
90
120
  },
91
121
  }),
92
122
  ],
@@ -94,1083 +124,367 @@ import { IAMModule } from '@flusys/nestjs-iam';
94
124
  export class AppModule {}
95
125
  ```
96
126
 
97
- ### Async Configuration
127
+ ### FULL Mode with Company Feature
98
128
 
99
129
  ```typescript
100
- IAMModule.forRootAsync({
130
+ IAMModule.forRoot({
101
131
  global: true,
102
132
  includeController: true,
103
133
  bootstrapAppConfig: {
104
134
  databaseMode: 'single',
105
- enableCompanyFeature: true,
106
- permissionMode: 'RBAC',
135
+ enableCompanyFeature: true, // Company/branch scoping
136
+ permissionMode: 'FULL', // RBAC + DIRECT
107
137
  },
108
- imports: [ConfigModule],
109
- useFactory: (config: ConfigService) => ({
110
- // IAM-specific options from config
111
- }),
112
- inject: [ConfigService],
138
+ config: { defaultDatabaseConfig: { /* ... */ } },
113
139
  })
114
140
  ```
115
141
 
116
142
  ---
117
143
 
118
- ## Constants
119
-
120
- ```typescript
121
- // Injection Token
122
- export const IAM_MODULE_OPTIONS = 'IAM_MODULE_OPTIONS';
123
- ```
124
-
125
- ---
126
-
127
- ## Core Concepts
128
-
129
- ### Actions
130
-
131
- Actions represent permissions in the system:
132
-
133
- | Field | Description |
134
- |-------|-------------|
135
- | `name` | Human-readable name |
136
- | `code` | Unique identifier (e.g., `user.create`) |
137
- | `actionType` | Category: `backend`, `frontend`, or `both` |
138
- | `metadata` | Additional data (icon, routerLink for frontend) |
139
- | `parentId` | Parent action for hierarchy |
140
- | `serial` | Display order |
144
+ ## Module Registration
141
145
 
142
- ### Action Types
146
+ ### forRoot (Sync)
143
147
 
144
148
  ```typescript
145
- enum ActionType {
146
- BACKEND = 'backend', // API endpoint permissions (cached for PermissionGuard)
147
- FRONTEND = 'frontend', // UI features (returned in my-permissions API)
148
- BOTH = 'both', // Both backend and frontend
149
- }
150
- ```
151
-
152
- ### Roles
153
-
154
- Roles are collections of actions:
155
-
156
- | Field | Description |
157
- |-------|-------------|
158
- | `name` | Human-readable name |
159
- | `description` | Role description |
160
- | `companyId` | Company scope (when company feature enabled) |
161
- | `isActive` | Active status |
162
-
163
- ### Permission Types
164
-
165
- ```typescript
166
- enum IamPermissionType {
167
- USER_ROLE = 'user_role', // User assigned to role
168
- ROLE_ACTION = 'role_action', // Role has action
169
- USER_ACTION = 'user_action', // User has action directly
170
- COMPANY_ACTION = 'company_action', // Company action whitelist
171
- }
149
+ IAMModule.forRoot({
150
+ global?: boolean; // Default: false
151
+ includeController?: boolean; // Default: false
152
+ bootstrapAppConfig?: {
153
+ databaseMode: 'single' | 'multi-tenant';
154
+ enableCompanyFeature: boolean;
155
+ permissionMode?: 'FULL' | 'RBAC' | 'DIRECT'; // Default: 'FULL'
156
+ };
157
+ config?: IIAMModuleConfig;
158
+ })
172
159
  ```
173
160
 
174
- ### Entity Types
175
-
176
- Used for source/target type fields in permissions:
161
+ ### forRootAsync (Factory)
177
162
 
178
163
  ```typescript
179
- enum IamEntityType {
180
- USER = 'user',
181
- ROLE = 'role',
182
- ACTION = 'action',
183
- COMPANY = 'company',
184
- }
185
- ```
186
-
187
- ### Permission Resolution Order
188
-
189
- 1. **Company-Action Whitelist** - Filter by company (if enabled)
190
- 2. **UserAction (DENY)** - Explicit denials take precedence
191
- 3. **UserAction (GRANT)** - Explicit grants
192
- 4. **UserRole -> RoleAction** - Inherited from roles
193
- 5. **Action Permission Logic** - Complex AND/OR rules
194
-
195
- ### Permission Cascade Deletion
164
+ import { ConfigService } from '@nestjs/config';
196
165
 
197
- When removing company actions via `removeCompanyActionsWithCascade()`, the `PermissionService` performs cascade deletion:
198
-
199
- ```
200
- Company Action Removed
201
-
202
- 1. Delete COMPANY_ACTION permissions
203
-
204
- 2. Find all roles in the company
205
-
206
- 3. Delete ROLE_ACTION permissions for those actions
207
-
208
- 4. Find all users in the company
209
-
210
- 5. Delete USER_ACTION permissions for those actions
211
-
212
- 6. Invalidate permission cache for affected users
166
+ IAMModule.forRootAsync({
167
+ global: true,
168
+ includeController: true,
169
+ bootstrapAppConfig: {
170
+ databaseMode: 'single',
171
+ enableCompanyFeature: true,
172
+ permissionMode: 'FULL',
173
+ },
174
+ imports: [ConfigModule],
175
+ useFactory: async (configService: ConfigService) => ({
176
+ defaultDatabaseConfig: {
177
+ type: 'postgres',
178
+ host: configService.get('DB_HOST'),
179
+ port: configService.get<number>('DB_PORT'),
180
+ username: configService.get('DB_USER'),
181
+ password: configService.get('DB_PASSWORD'),
182
+ database: configService.get('DB_NAME'),
183
+ },
184
+ }),
185
+ inject: [ConfigService],
186
+ })
213
187
  ```
214
188
 
215
189
  ---
216
190
 
217
- ## Interfaces
218
-
219
- ### IAction
191
+ ## Configuration Reference
220
192
 
221
193
  ```typescript
222
- interface IAction {
223
- id: string;
224
- readOnly: boolean;
225
- name: string;
226
- description: string | null;
227
- code: string | null;
228
- actionType: ActionType;
229
- permissionLogic: LogicNode | null;
230
- serial: number | null;
231
- isActive: boolean;
232
- parentId: string | null;
233
- metadata: Record<string, any> | null;
234
- createdAt: Date;
235
- updatedAt: Date;
236
- deletedAt: Date | null;
237
- createdById: string | null;
238
- updatedById: string | null;
239
- deletedById: string | null;
240
- }
241
- ```
242
-
243
- ### IActionTree
244
-
245
- ```typescript
246
- interface IActionTree extends IAction {
247
- children?: IActionTree[];
248
- }
249
- ```
250
-
251
- ### IRole
252
-
253
- ```typescript
254
- interface IRole {
255
- id: string;
256
- readOnly: boolean;
257
- name: string;
258
- description: string | null;
259
- isActive: boolean;
260
- serial: number | null;
261
- companyId: string | null;
262
- metadata: Record<string, any> | null;
263
- createdAt: Date;
264
- updatedAt: Date;
265
- deletedAt: Date | null;
266
- createdById: string | null;
267
- updatedById: string | null;
268
- deletedById: string | null;
194
+ interface IIAMModuleConfig extends IDataSourceServiceOptions {
195
+ // All IAM behaviour is controlled via bootstrapAppConfig.
196
+ // IDataSourceServiceOptions provides:
197
+ // defaultDatabaseConfig?: IDatabaseConfig
198
+ // tenantDefaultDatabaseConfig?: IDatabaseConfig
199
+ // tenants?: ITenantDatabaseConfig[]
269
200
  }
270
201
  ```
271
202
 
272
- ### Module Options
203
+ Bootstrap configuration is fixed at startup and cannot change at runtime:
273
204
 
274
205
  ```typescript
275
- // Runtime configuration (currently empty - reserved for future use)
276
- interface IIAMModuleConfig extends IDataSourceServiceOptions {
277
- // Future: cache TTL, etc.
278
- }
279
-
280
- // Full module options
281
- interface IAMModuleOptions extends IDynamicModuleConfig {
282
- bootstrapAppConfig?: IBootstrapAppConfig;
283
- config?: IIAMModuleConfig;
284
- }
285
-
286
- // Async options
287
- interface IAMModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'>, IDynamicModuleConfig {
288
- bootstrapAppConfig?: IBootstrapAppConfig;
289
- config?: IIAMModuleConfig;
290
- useFactory?: (...args: any[]) => Promise<IIAMModuleConfig> | IIAMModuleConfig;
291
- inject?: any[];
292
- useClass?: Type<IAMOptionsFactory>;
293
- useExisting?: Type<IAMOptionsFactory>;
206
+ interface IBootstrapAppConfig {
207
+ databaseMode: 'single' | 'multi-tenant';
208
+ enableCompanyFeature: boolean;
209
+ permissionMode?: 'FULL' | 'RBAC' | 'DIRECT'; // Default: 'FULL'
294
210
  }
295
211
  ```
296
212
 
297
213
  ---
298
214
 
299
- ## Entities
215
+ ## Feature Toggles
300
216
 
301
- ### Entity Groups
217
+ | Feature | Config | Default | Effect |
218
+ |---------|--------|---------|--------|
219
+ | Company scoping | `enableCompanyFeature: true` | `false` | Uses `*WithCompany` entity variants; adds `companyId`/`branchId` to permission assignments; registers `CompanyActionPermissionController` |
220
+ | RBAC | `permissionMode: 'RBAC'` | — | Loads Role, RoleAction, UserRole entities; registers Role controllers |
221
+ | DIRECT | `permissionMode: 'DIRECT'` | — | Loads UserAction entities; registers UserActionPermissionController |
222
+ | FULL | `permissionMode: 'FULL'` | default | All entities and controllers |
302
223
 
303
- ```typescript
304
- // Core entities (no company feature)
305
- export const IAMCoreEntities = [Action, Role, UserIamPermission];
306
-
307
- // Company-specific entities
308
- export const IAMCompanyEntities = [RoleWithCompany, UserIamPermissionWithCompany];
309
-
310
- // All entities
311
- export const IAMAllEntities = [
312
- Action,
313
- Role,
314
- RoleWithCompany,
315
- UserIamPermission,
316
- UserIamPermissionWithCompany,
317
- ];
318
-
319
- // Helper function
320
- export function getIAMEntitiesByConfig(
321
- enableCompanyFeature: boolean,
322
- permissionMode: 'FULL' | 'RBAC' | 'DIRECT' = 'FULL',
323
- ): any[] {
324
- const entities: any[] = [Action];
325
-
326
- // Permission entity - always included
327
- if (enableCompanyFeature) {
328
- entities.push(UserIamPermissionWithCompany);
329
- } else {
330
- entities.push(UserIamPermission);
331
- }
224
+ **Controller loading by mode:**
332
225
 
333
- // Role entity - Only for RBAC or FULL mode (not DIRECT)
334
- if (permissionMode === 'RBAC' || permissionMode === 'FULL') {
335
- if (enableCompanyFeature) {
336
- entities.push(RoleWithCompany);
337
- } else {
338
- entities.push(Role);
339
- }
340
- }
226
+ | Mode | + `enableCompanyFeature` | Additional controller |
227
+ |------|--------------------------|-----------------------|
228
+ | `RBAC` | true | `CompanyActionPermissionController` |
229
+ | `DIRECT` | true | `CompanyActionPermissionController` |
230
+ | `FULL` | true | `CompanyActionPermissionController` |
341
231
 
342
- return entities;
343
- }
344
- ```
345
-
346
- ### ActionBase
347
-
348
- Core action fields in `action-base.entity.ts`:
349
-
350
- | Column | Type | Description |
351
- |--------|------|-------------|
352
- | `readOnly` | boolean | System-protected action |
353
- | `name` | varchar(255) | Action name |
354
- | `code` | varchar(255) | Unique code |
355
- | `description` | varchar(500) | Description |
356
- | `actionType` | enum | BACKEND, FRONTEND, BOTH |
357
- | `permissionLogic` | json | AND/OR rules |
358
- | `parentId` | uuid | Parent action ID |
359
- | `serial` | int | Display order |
360
- | `isActive` | boolean | Active status |
361
- | `metadata` | json | Additional data |
232
+ ---
362
233
 
363
- ### RoleBase
234
+ ## API Endpoints
364
235
 
365
- Core role fields in `role-base.entity.ts`:
236
+ All endpoints use **POST** and require JWT authentication.
366
237
 
367
- | Column | Type | Description |
368
- |--------|------|-------------|
369
- | `readOnly` | boolean | System-protected role |
370
- | `name` | varchar(255) | Role name |
371
- | `description` | varchar(500) | Description |
372
- | `isActive` | boolean | Active status |
373
- | `serial` | int | Display order |
374
- | `metadata` | json | Additional data |
238
+ ### Actions `POST /iam/action/*`
375
239
 
376
- ### RoleWithCompany
240
+ | Endpoint | Permission | Description |
241
+ |----------|-----------|-------------|
242
+ | `POST /iam/action/insert` | `action.create` | Create an action |
243
+ | `POST /iam/action/get-all` | `action.read` | List all actions |
244
+ | `POST /iam/action/get/:id` | `action.read` | Get action by ID |
245
+ | `POST /iam/action/get-tree` | `action.read` | Get hierarchical action tree |
246
+ | `POST /iam/action/update` | `action.update` | Update action |
247
+ | `POST /iam/action/delete` | `action.delete` | Delete action |
377
248
 
378
- Extends RoleBase with:
379
- - `companyId` (uuid) - Company scope
249
+ ### Roles — `POST /iam/role/*` *(RBAC and FULL modes)*
380
250
 
381
- ### PermissionBase
251
+ | Endpoint | Permission | Description |
252
+ |----------|-----------|-------------|
253
+ | `POST /iam/role/insert` | `role.create` | Create a role |
254
+ | `POST /iam/role/get-all` | `role.read` | List all roles |
255
+ | `POST /iam/role/get/:id` | `role.read` | Get role by ID |
256
+ | `POST /iam/role/update` | `role.update` | Update role |
257
+ | `POST /iam/role/delete` | `role.delete` | Delete role |
382
258
 
383
- Core permission fields in `permission-base.entity.ts`:
259
+ ### Role Permissions `POST /iam/role-permission/*` *(RBAC and FULL modes)*
384
260
 
385
- | Column | Type | Description |
386
- |--------|------|-------------|
387
- | `permissionType` | enum | USER_ROLE, ROLE_ACTION, etc. |
388
- | `sourceType` | enum | USER, ROLE, COMPANY |
389
- | `sourceId` | uuid | Source entity ID |
390
- | `targetType` | enum | ROLE, ACTION |
391
- | `targetId` | uuid | Target entity ID |
392
- | `userId` | uuid | User ID (nullable) |
393
- | `validFrom` | timestamp | Permission valid from |
394
- | `validUntil` | timestamp | Permission valid until |
395
- | `reason` | text | Reason for permission |
396
- | `metadata` | json | Additional data |
261
+ | Endpoint | Permission | Description |
262
+ |----------|-----------|-------------|
263
+ | `POST /iam/role-permission/assign` | `role.update` | Assign action to role |
264
+ | `POST /iam/role-permission/remove` | `role.update` | Remove action from role |
265
+ | `POST /iam/role-permission/get-by-role` | `role.read` | Get all actions for a role |
266
+ | `POST /iam/role-permission/assign-user-role` | `role.update` | Assign role to user |
267
+ | `POST /iam/role-permission/remove-user-role` | `role.update` | Remove role from user |
397
268
 
398
- **Methods:**
269
+ ### User Action Permissions — `POST /iam/user-action-permission/*` *(DIRECT and FULL modes)*
399
270
 
400
- ```typescript
401
- // Check if permission is currently valid (date constraints)
402
- isValid(now: Date = new Date()): boolean {
403
- if (this.validFrom && now < this.validFrom) return false;
404
- if (this.validUntil && now > this.validUntil) return false;
405
- return true;
406
- }
407
- ```
271
+ | Endpoint | Permission | Description |
272
+ |----------|-----------|-------------|
273
+ | `POST /iam/user-action-permission/assign` | `action.update` | Directly assign action to user |
274
+ | `POST /iam/user-action-permission/remove` | `action.update` | Remove direct action from user |
275
+ | `POST /iam/user-action-permission/get-by-user` | `action.read` | Get all direct permissions for user |
408
276
 
409
- ### UserIamPermissionWithCompany
277
+ ### Company Action Whitelist — `POST /iam/company-action/*` *(`enableCompanyFeature: true`)*
410
278
 
411
- Extends PermissionBase with:
412
- - `companyId` (uuid) - Company scope
413
- - `branchId` (uuid) - Branch scope
279
+ | Endpoint | Permission | Description |
280
+ |----------|-----------|-------------|
281
+ | `POST /iam/company-action/assign` | `action.update` | Add action to company whitelist |
282
+ | `POST /iam/company-action/remove` | `action.update` | Remove action from whitelist |
283
+ | `POST /iam/company-action/get-by-company` | `action.read` | Get company's available actions |
414
284
 
415
- **Permission Granularity:**
285
+ ### My Permissions — `POST /iam/my-permission/*`
416
286
 
417
- | Level | companyId | branchId | Use Case |
418
- |-------|-----------|----------|----------|
419
- | Global | null | null | Super admin across all companies |
420
- | Company-wide | set | null | Manager for ALL branches in company |
421
- | Branch-specific | set | set | Manager for specific branch only |
287
+ | Endpoint | Auth | Description |
288
+ |----------|------|-------------|
289
+ | `POST /iam/my-permission/get-all` | JWT | Get all permissions for current user |
422
290
 
423
291
  ---
424
292
 
425
- ## Permission Modes
426
-
427
- Set in `bootstrapAppConfig.permissionMode`:
428
-
429
- ### Permission Mode Enum
430
-
431
- ```typescript
432
- enum IAMPermissionMode {
433
- RBAC = 1, // Role-Based: Action + RoleAction + UserRole
434
- DIRECT = 2, // Direct User Permissions: Action + UserAction
435
- FULL = 3, // Both RBAC + Direct permissions
436
- }
437
- ```
438
-
439
- ### RBAC Mode (Role-Based)
440
-
441
- ```
442
- User -> Role -> Action
443
- ```
444
-
445
- Only role-based permissions. Users get permissions through assigned roles.
446
-
447
- **Note:** Calling `assignUserActions()` in RBAC mode throws `BadRequestException`.
293
+ ## Entities
448
294
 
449
- ### DIRECT Mode (Action-Based)
295
+ ### Core Entities (always registered)
450
296
 
451
- ```
452
- User -> Action
453
- ```
297
+ | Entity | Table | Description |
298
+ |--------|-------|-------------|
299
+ | `Action` | `iam_action` | Permission actions (e.g., `product.create`) with type and hierarchy |
300
+ | `Menu` | `iam_menu` | Navigation menu items with optional action requirement |
454
301
 
455
- Only direct permissions. Users get actions assigned directly.
302
+ ### RBAC Entities (`permissionMode: 'RBAC'` or `'FULL'`)
456
303
 
457
- **Note:** Calling `assignUserRoles()` or `assignRoleActions()` in DIRECT mode throws `BadRequestException`.
304
+ | Entity | Table | Description |
305
+ |--------|-------|-------------|
306
+ | `Role` | `iam_role` | Role definitions |
307
+ | `RoleAction` | `iam_role_action` | Role → Action assignments |
308
+ | `UserRole` | `iam_user_role` | User → Role assignments |
458
309
 
459
- ### FULL Mode (Combined)
310
+ ### DIRECT Entities (`permissionMode: 'DIRECT'` or `'FULL'`)
460
311
 
461
- ```
462
- User -> Role -> Action
463
- User -> Action
464
- ```
312
+ | Entity | Table | Description |
313
+ |--------|-------|-------------|
314
+ | `UserAction` | `iam_user_action` | Direct User Action assignments |
465
315
 
466
- Both RBAC and direct permissions. Default mode. All assignment methods available.
316
+ ### Company Feature Entities (`enableCompanyFeature: true`)
467
317
 
468
- ---
318
+ All above entities get `WithCompany` variants with `companyId` and `branchId` columns, plus:
469
319
 
470
- ## Permission Logic
320
+ | Entity | Table | Description |
321
+ |--------|-------|-------------|
322
+ | `CompanyAction` | `iam_company_action` | Actions whitelisted per company |
471
323
 
472
- Complex permission rules using AND/OR operators stored in `permissionLogic` field:
324
+ #### Register Entities in TypeORM
473
325
 
474
326
  ```typescript
475
- interface LogicNode {
476
- id: string;
477
- type: 'group' | 'action';
478
- operator?: 'AND' | 'OR';
479
- children?: LogicNode[];
480
- actionId?: string;
481
- }
482
- ```
483
-
484
- **Example:** User must have "employee" AND ("department-head" OR "hr-clearance")
327
+ import { IAMModule } from '@flusys/nestjs-iam';
485
328
 
486
- ```typescript
487
- const logic: LogicNode = {
488
- id: 'root',
489
- type: 'group',
490
- operator: 'AND',
491
- children: [
492
- { id: '1', type: 'action', actionId: 'employee-action-id' },
493
- {
494
- id: '2',
495
- type: 'group',
496
- operator: 'OR',
497
- children: [
498
- { id: '2-1', type: 'action', actionId: 'department-head' },
499
- { id: '2-2', type: 'action', actionId: 'hr-clearance' },
500
- ],
501
- },
329
+ TypeOrmModule.forRoot({
330
+ entities: [
331
+ ...IAMModule.getEntities({
332
+ enableCompanyFeature: true,
333
+ permissionMode: 'FULL',
334
+ }),
502
335
  ],
503
- };
336
+ })
504
337
  ```
505
338
 
506
339
  ---
507
340
 
508
- ## Services
509
-
510
- | Service | Scope | Description |
511
- |---------|-------|-------------|
512
- | `ActionService` | REQUEST | Action CRUD, tree queries |
513
- | `RoleService` | REQUEST | Role CRUD (RBAC/FULL mode only) |
514
- | `PermissionService` | REQUEST | Permission assignments, my-permissions |
515
- | `PermissionCacheService` | SINGLETON | Cache management |
516
- | `IAMConfigService` | SINGLETON | IAM configuration access |
517
- | `IAMDataSourceService` | REQUEST | DataSource provider for multi-tenant |
518
-
519
- ### IAMConfigService
341
+ ## Permission Cache
520
342
 
521
- ```typescript
522
- import { IAMConfigService } from '@flusys/nestjs-iam';
523
-
524
- // Database mode
525
- const dbMode = config.getDatabaseMode(); // 'single' | 'multi-tenant'
526
- const isMultiTenant = config.isMultiTenant();
343
+ Permissions are cached with the key `iam:permissions:{userId}:{companyId}:{branchId}` for **1 hour**. The cache is automatically invalidated when:
527
344
 
528
- // Feature flags
529
- const hasCompany = config.isCompanyFeatureEnabled();
345
+ - A role is assigned to or removed from a user
346
+ - An action is directly assigned to or removed from a user
347
+ - A role's action set is modified
348
+ - The company action whitelist changes
530
349
 
531
- // Permission mode
532
- const mode = config.getPermissionMode(); // IAMPermissionMode enum
533
- const isRbac = config.isRbacEnabled(); // true for RBAC or FULL
534
- const isDirect = config.isDirectPermissionEnabled(); // true for DIRECT or FULL
535
-
536
- // Get raw options
537
- const options = config.getOptions();
538
- ```
350
+ Cache uses `HybridCache` (in-memory + Redis). If Redis is not configured, in-memory cache is used.
539
351
 
540
- ### IAMDataSourceService
541
-
542
- ```typescript
543
- import { IAMDataSourceService } from '@flusys/nestjs-iam';
544
-
545
- // Get repository for entity
546
- const actionRepo = await dataSourceProvider.getRepository(Action);
547
-
548
- // Tenant-specific features
549
- const enableCompany = dataSourceProvider.getEnableCompanyFeatureForCurrentTenant();
550
- const entities = await dataSourceProvider.getIAMEntities();
551
- ```
552
-
553
- ### ActionService
554
-
555
- ```typescript
556
- import { ActionService } from '@flusys/nestjs-iam';
557
-
558
- // Create action
559
- await actionService.insert({
560
- name: 'View Users',
561
- code: 'user.view',
562
- actionType: ActionType.BACKEND,
563
- parentId: parentAction.id,
564
- });
352
+ ---
565
353
 
566
- // Get hierarchical tree
567
- const tree = await actionService.getActionTree(user, 'search', true, false);
354
+ ## Exported Services
568
355
 
569
- // Get actions for permission (company-filtered)
570
- const actions = await actionService.getActionsForPermission(user);
571
- ```
356
+ | Service | Description |
357
+ |---------|-------------|
358
+ | `PermissionService` | Resolve and check permissions for a user |
359
+ | `ActionService` | Action CRUD and tree queries |
360
+ | `RoleService` | Role CRUD *(RBAC/FULL modes)* |
361
+ | `UserActionPermissionService` | Direct user-action assignment *(DIRECT/FULL modes)* |
362
+ | `IAMConfigService` | Exposes runtime config and feature flags |
363
+ | `IAMDataSourceProvider` | Dynamic DataSource resolution per request |
572
364
 
573
- ### RoleService
365
+ ---
574
366
 
575
- ```typescript
576
- import { RoleService } from '@flusys/nestjs-iam';
577
-
578
- // Create role
579
- await roleService.insert({
580
- name: 'Administrator',
581
- description: 'Full system access',
582
- companyId: 'company-id',
583
- });
584
- ```
367
+ ## Using Permissions in Controllers
585
368
 
586
- ### PermissionService
369
+ ### Using the Decorator
587
370
 
588
371
  ```typescript
589
- import { PermissionService, PermissionAction } from '@flusys/nestjs-iam';
590
-
591
- // Assign roles to user (RBAC/FULL mode only)
592
- await permissionService.assignUserRoles({
593
- userId: 'user-id',
594
- companyId: 'company-id',
595
- branchId: null, // null = company-wide
596
- items: [{ id: 'role-id', action: PermissionAction.ADD }],
597
- });
598
-
599
- // Assign actions directly (DIRECT/FULL mode only)
600
- await permissionService.assignUserActions({
601
- userId: 'user-id',
602
- companyId: 'company-id',
603
- branchId: 'branch-id',
604
- items: [{ id: 'action-id', action: PermissionAction.ADD }],
605
- });
606
-
607
- // Assign actions to role (RBAC/FULL mode only)
608
- await permissionService.assignRoleActions({
609
- roleId: 'role-id',
610
- items: [{ id: 'action-id', action: PermissionAction.ADD }],
611
- });
612
-
613
- // Get user's permissions
614
- const permissions = await permissionService.getMyPermissions(
615
- userId, branchId, companyId, parentCodes
616
- );
617
- ```
618
-
619
- ### PermissionCacheService
620
-
621
- Centralized cache management for IAM permissions. Cache key format matches PermissionGuard in nestjs-shared.
372
+ import { RequirePermission } from '@flusys/nestjs-shared/decorators';
373
+ import { JwtAuthGuard } from '@flusys/nestjs-shared/guards';
622
374
 
623
- **Cache TTLs:**
624
- - Permissions: 1 hour (3600000ms)
625
- - Action codes: 2 hours (7200000ms)
626
-
627
- **Interfaces:**
628
-
629
- ```typescript
630
- interface PermissionCacheKeyOptions {
631
- userId: string;
632
- companyId?: string | null;
633
- branchId?: string | null;
634
- enableCompanyFeature: boolean;
635
- }
375
+ @UseGuards(JwtAuthGuard)
376
+ @Controller('products')
377
+ export class ProductController {
378
+ @Post('insert')
379
+ @RequirePermission('product.create')
380
+ async create() { }
636
381
 
637
- interface CachedMyPermissions {
638
- frontendActions: Array<{
639
- id: string;
640
- code: string;
641
- name: string;
642
- description: string | null;
643
- parentId: string | null;
644
- }>;
645
- backendCodes: string[];
382
+ @Post('get-all')
383
+ @RequirePermission('product.read')
384
+ async getAll() { }
646
385
  }
647
386
  ```
648
387
 
649
- **Usage:**
650
-
651
- ```typescript
652
- import { PermissionCacheService } from '@flusys/nestjs-iam';
653
-
654
- // Set permissions (backend codes for PermissionGuard)
655
- await cacheService.setPermissions(
656
- { userId, companyId, branchId, enableCompanyFeature: true },
657
- ['users.read', 'users.write']
658
- );
659
-
660
- // Set my-permissions (full response with frontend actions)
661
- await cacheService.setMyPermissions(cacheOptions, {
662
- frontendActions: [...],
663
- backendCodes: ['users.read', 'users.write'],
664
- });
665
-
666
- // Get cached my-permissions
667
- const cached = await cacheService.getMyPermissions(cacheOptions);
668
-
669
- // Invalidate single user cache
670
- await cacheService.invalidateUser('user-id', 'company-id', ['branch-id']);
671
-
672
- // Invalidate multiple users cache
673
- await cacheService.invalidateUsers(userIds, 'company-id', branchIds);
674
-
675
- // Invalidate role members (all users assigned to role)
676
- await cacheService.invalidateRole('role-id', userIds, 'company-id', branchIds);
677
-
678
- // Tenant-aware action code cache
679
- await cacheService.setActionCodeMap(codeToIdMap, tenantId);
680
- await cacheService.getActionIdsByCodes(['user.view'], tenantId);
681
- ```
682
-
683
- **Cache Key Formats:**
684
- ```
685
- permissions:user:{userId} // Without company
686
- permissions:company:{companyId}:branch:{branchId}:user:{userId} // With company
687
- my-permissions:user:{userId} // My-permissions (no company)
688
- my-permissions:company:{companyId}:branch:{branchId}:user:{userId} // My-permissions (with company)
689
- action-codes:map // Single tenant
690
- action-codes:tenant:{tenantId}:map // Multi-tenant
691
- ```
692
-
693
- ---
694
-
695
- ## Helpers
696
-
697
- ### PermissionModeHelper
698
-
699
- Centralizes conversion from string to enum to prevent duplication:
388
+ ### Checking Permissions Programmatically
700
389
 
701
390
  ```typescript
702
- import { PermissionModeHelper } from '@flusys/nestjs-iam';
703
-
704
- // Convert string to enum
705
- const mode = PermissionModeHelper.fromString('RBAC');
706
- // Returns: IAMPermissionMode.RBAC
391
+ import { PermissionService } from '@flusys/nestjs-iam';
707
392
 
708
- const mode = PermissionModeHelper.fromString(undefined);
709
- // Returns: IAMPermissionMode.FULL (default)
393
+ @Injectable()
394
+ export class ProductService {
395
+ constructor(
396
+ @Inject(PermissionService) private readonly permissionService: PermissionService,
397
+ ) {}
710
398
 
711
- // Convert enum to string
712
- const str = PermissionModeHelper.toString(IAMPermissionMode.RBAC);
713
- // Returns: 'RBAC'
714
- ```
715
-
716
- **Used by:**
717
- - `iam.module.ts` (forRoot, forRootAsync)
718
- - `iam-config.service.ts` (getPermissionMode)
719
- - `main.ts` (Swagger setup)
720
-
721
- ### validateCompanyAccess
722
-
723
- Validates user access to a company for permission operations:
399
+ async canUserCreate(userId: string, companyId?: string): Promise<boolean> {
400
+ return this.permissionService.hasPermission(userId, 'product.create', companyId);
401
+ }
724
402
 
725
- ```typescript
726
- import { validateCompanyAccess } from '@flusys/nestjs-iam';
727
-
728
- // In a controller or service
729
- validateCompanyAccess(
730
- iamConfig,
731
- dto.companyId,
732
- user,
733
- 'You do not have access to this company',
734
- );
735
- // Throws ForbiddenException if user.companyId !== dto.companyId
403
+ async getUserPermissions(userId: string, companyId?: string): Promise<string[]> {
404
+ return this.permissionService.getPermissions(userId, companyId);
405
+ }
406
+ }
736
407
  ```
737
408
 
738
409
  ---
739
410
 
740
- ## REST API Endpoints
741
-
742
- ### Actions API
743
-
744
- | Endpoint | Method | Permission | Description |
745
- |----------|--------|------------|-------------|
746
- | `/iam/actions/insert` | POST | `action.create` | Create action |
747
- | `/iam/actions/insert-many` | POST | `action.create` | Create multiple actions |
748
- | `/iam/actions/get-all` | POST | `action.read` | List actions (paginated) |
749
- | `/iam/actions/get` | POST | `action.read` | Get action by ID |
750
- | `/iam/actions/tree` | POST | JWT Auth | Get hierarchical tree (ALL actions) |
751
- | `/iam/actions/tree-for-permission` | POST | JWT Auth | Get company-filtered actions |
752
- | `/iam/actions/update` | POST | `action.update` | Update action |
753
- | `/iam/actions/update-many` | POST | `action.update` | Update multiple actions |
754
- | `/iam/actions/delete` | POST | `action.delete` | Delete action |
755
-
756
- ### Roles API (RBAC/FULL mode only)
757
-
758
- | Endpoint | Method | Permission | Description |
759
- |----------|--------|------------|-------------|
760
- | `/iam/roles/insert` | POST | `role.create` | Create role |
761
- | `/iam/roles/insert-many` | POST | `role.create` | Create multiple roles |
762
- | `/iam/roles/get-all` | POST | `role.read` | List roles (paginated, auto-filtered by company) |
763
- | `/iam/roles/get` | POST | `role.read` | Get role by ID |
764
- | `/iam/roles/update` | POST | `role.update` | Update role |
765
- | `/iam/roles/update-many` | POST | `role.update` | Update multiple roles |
766
- | `/iam/roles/delete` | POST | `role.delete` | Delete role |
767
-
768
- ### Permissions API
769
-
770
- | Endpoint | Method | Permission | Modes | Description |
771
- |----------|--------|------------|-------|-------------|
772
- | `/iam/permissions/role-actions/assign` | POST | `role-action.assign` | RBAC, FULL | Assign actions to role |
773
- | `/iam/permissions/get-role-actions` | POST | `role-action.read` | RBAC, FULL | Get role actions |
774
- | `/iam/permissions/user-roles/assign` | POST | `user-role.assign` | RBAC, FULL | Assign roles to user |
775
- | `/iam/permissions/get-user-roles` | POST | `user-role.read` | RBAC, FULL | Get user roles |
776
- | `/iam/permissions/user-actions/assign` | POST | `user-action.assign` | DIRECT, FULL | Assign actions to user |
777
- | `/iam/permissions/get-user-actions` | POST | `user-action.read` | DIRECT, FULL | Get user actions |
778
- | `/iam/permissions/company-actions/assign` | POST | `company-action.assign` | Company enabled | Company action whitelist |
779
- | `/iam/permissions/get-company-actions` | POST | `company-action.read` | Company enabled | Get company actions |
780
- | `/iam/permissions/my-permissions` | POST | JWT Auth | All | Get current user's permissions |
781
-
782
- ### Permission Constants
783
-
784
- These permission codes are defined in `@flusys/nestjs-shared`:
411
+ ## Action Types
785
412
 
786
- ```typescript
787
- // Actions
788
- ACTION_PERMISSIONS = { CREATE: 'action.create', READ: 'action.read', UPDATE: 'action.update', DELETE: 'action.delete' }
789
-
790
- // Roles
791
- ROLE_PERMISSIONS = { CREATE: 'role.create', READ: 'role.read', UPDATE: 'role.update', DELETE: 'role.delete' }
792
-
793
- // Role-Action assignments
794
- ROLE_ACTION_PERMISSIONS = { ASSIGN: 'role-action.assign', READ: 'role-action.read' }
795
-
796
- // User-Role assignments
797
- USER_ROLE_PERMISSIONS = { ASSIGN: 'user-role.assign', READ: 'user-role.read' }
798
-
799
- // User-Action assignments (direct)
800
- USER_ACTION_PERMISSIONS = { ASSIGN: 'user-action.assign', READ: 'user-action.read' }
801
-
802
- // Company-Action assignments
803
- COMPANY_ACTION_PERMISSIONS = { ASSIGN: 'company-action.assign', READ: 'company-action.read' }
804
- ```
805
-
806
- **Controller Registration by Mode:**
807
-
808
- | Mode | Controllers |
413
+ | Type | Description |
809
414
  |------|-------------|
810
- | DIRECT | ActionController, MyPermissionController, UserActionPermissionController |
811
- | RBAC | ActionController, MyPermissionController, RoleController, RolePermissionController |
812
- | FULL | All controllers |
813
- | Company Enabled | + CompanyActionPermissionController |
814
-
815
- ---
816
-
817
- ## Multi-Tenant Support
818
-
819
- ### Company Feature Toggle
820
-
821
- ```typescript
822
- // config/app.config.ts
823
- export const bootstrapAppConfig: IBootstrapAppConfig = {
824
- databaseMode: 'single', // or 'multi-tenant'
825
- enableCompanyFeature: true,
826
- permissionMode: 'FULL',
827
- };
828
- ```
829
-
830
- ### When `enableCompanyFeature: true`
831
-
832
- - `companyId` and `branchId` fields available in DTOs
833
- - Company-Action Permissions controller registered
834
- - Three-level permission granularity (Global, Company-wide, Branch-specific)
835
- - Uses `UserIamPermissionWithCompany` and `RoleWithCompany` entities
836
-
837
- ### When `enableCompanyFeature: false`
838
-
839
- - Company endpoints NOT available (404)
840
- - `companyId`/`branchId` fields visible in Swagger but ignored
841
- - All permissions are global
842
- - Uses `UserIamPermission` and `Role` entities
843
-
844
- ### Multi-Tenant Database Mode
845
-
846
- When `databaseMode: 'multi-tenant'`:
847
- - Each tenant has isolated database/schema
848
- - Action code cache is tenant-aware (separate cache per tenant)
849
- - Uses `IAMDataSourceService` for tenant-specific connections
850
-
851
- ### Permission Merging
852
-
853
- When `getMyPermissions` is called:
854
-
855
- **With branchId specified:**
856
- 1. Company-wide roles (branchId=null) + Branch-specific roles for that branch
857
- 2. Actions from all merged roles
858
- 3. Company-wide user actions + Branch-specific user actions for that branch
859
-
860
- **Without branchId (null):**
861
- 1. ALL roles for the user in the company (any branch)
862
- 2. Actions from all roles
863
- 3. ALL user actions for the company (any branch)
864
-
865
- **Result:** Complete permission set without duplicates, filtered by company whitelist.
866
-
867
- ---
868
-
869
- ## Permission Guard Integration
870
-
871
- ```typescript
872
- import { RequirePermission, RequireAnyPermission } from '@flusys/nestjs-shared/decorators';
873
-
874
- @Controller('users')
875
- export class UserController {
876
- @RequirePermission('user.view')
877
- @Post('get-all')
878
- getUsers() {}
879
-
880
- @RequireAnyPermission('user.create', 'admin')
881
- @Post('insert')
882
- createUser() {}
415
+ | `BACKEND` | Enforced by `PermissionGuard` on API requests only. Not returned to the frontend. |
416
+ | `FRONTEND` | Returned to the frontend in permission lists. Not enforced server-side. |
417
+ | `BOTH` | Enforced server-side AND returned to the frontend. |
418
+
419
+ ```json
420
+ POST /iam/action/insert
421
+ {
422
+ "name": "Create Product",
423
+ "code": "product.create",
424
+ "type": "BOTH",
425
+ "parentId": null,
426
+ "description": "Allows creating new products"
883
427
  }
884
428
  ```
885
429
 
886
430
  ---
887
431
 
888
- ## Best Practices
432
+ ## Hierarchical Actions
889
433
 
890
- ### 1. Action Code Naming
434
+ Actions support parent-child relationships. Use `parentId` to create a tree:
891
435
 
892
- ```typescript
893
- // Hierarchical naming for backend
894
- 'user.view', 'user.create', 'user.delete'
895
-
896
- // UPPERCASE for frontend actions
897
- 'MENU_USERS', 'MENU_REPORTS', 'FEATURE_EXPORT'
898
436
  ```
899
-
900
- ### 2. Company-wide vs Branch-specific
901
-
902
- ```typescript
903
- // Company-wide for managers across ALL branches
904
- { companyId: 'company-a', branchId: null }
905
-
906
- // Branch-specific for location-bound staff
907
- { companyId: 'company-a', branchId: 'branch-x' }
437
+ product (BOTH - parent)
438
+ ├── product.read (BOTH - child)
439
+ ├── product.create (BOTH - child)
440
+ ├── product.update (BOTH - child)
441
+ └── product.delete (BOTH - child)
908
442
  ```
909
443
 
910
- ### 3. Use Roles for Common Patterns
911
-
912
- ```typescript
913
- // Create roles for common permission sets
914
- const roles = [
915
- { name: 'Viewer', actions: ['*.view'] },
916
- { name: 'Editor', actions: ['*.view', '*.create', '*.update'] },
917
- { name: 'Admin', actions: ['*'] },
918
- ];
444
+ Get the full tree:
445
+ ```json
446
+ POST /iam/action/get-tree
447
+ {}
919
448
  ```
920
449
 
921
- ### 4. Use Direct Actions Sparingly
922
-
923
- Direct actions should be exceptions and branch-specific overrides, not the primary permission mechanism.
450
+ The wildcard `product.*` in `@RequirePermission('product.*')` matches any action whose code starts with `product.`.
924
451
 
925
452
  ---
926
453
 
927
- ## DTOs Reference
928
-
929
- ### Permission Operation DTOs
930
-
931
- ```typescript
932
- // Permission action enum for add/remove operations
933
- enum PermissionAction {
934
- ADD = 'add',
935
- REMOVE = 'remove',
936
- }
454
+ ## Troubleshooting
937
455
 
938
- // Common item structure for permission assignments
939
- interface PermissionItemDto {
940
- id: string; // ID of target (action or role)
941
- action: PermissionAction; // ADD or REMOVE
942
- }
943
- ```
456
+ **`PermissionGuard` always denies user has correct role**
944
457
 
945
- ### Assignment DTOs
458
+ Check that the IAM entities are included in your `TypeOrmModule` registration. Missing entities cause the permission query to return empty results.
946
459
 
947
- ```typescript
948
- // Assign actions directly to user
949
- interface AssignUserActionsDto {
950
- userId: string;
951
- companyId?: string; // Company scope (when enabled)
952
- branchId?: string; // Branch scope (null = company-wide)
953
- items: PermissionItemDto[];
954
- }
955
-
956
- // Assign roles to user
957
- interface AssignUserRolesDto {
958
- userId: string;
959
- companyId?: string;
960
- branchId?: string;
961
- items: PermissionItemDto[];
962
- }
963
-
964
- // Assign actions to role
965
- interface AssignRoleActionsDto {
966
- roleId: string;
967
- items: PermissionItemDto[];
968
- }
969
-
970
- // Assign actions to company (whitelist)
971
- interface AssignCompanyActionsDto {
972
- companyId: string;
973
- items: PermissionItemDto[];
974
- }
975
- ```
976
-
977
- ### Query DTOs
978
-
979
- ```typescript
980
- // Query user's permissions (my-permissions)
981
- interface MyPermissionsQueryDto {
982
- parentCodes?: string[]; // Filter by parent action codes
983
- }
984
-
985
- // Query action tree
986
- interface ActionTreeQueryDto {
987
- search?: string; // Filter by name or code
988
- isActive?: boolean; // Filter by active status
989
- withDeleted?: boolean; // Include deleted actions
990
- }
991
- ```
992
-
993
- ### Response DTOs
994
-
995
- ```typescript
996
- // My-permissions response
997
- interface MyPermissionsResponseDto {
998
- frontendActions: FrontendActionDto[];
999
- cachedEndpoints: number; // Count of backend codes cached
1000
- }
460
+ ---
1001
461
 
1002
- interface FrontendActionDto {
1003
- id: string;
1004
- code: string;
1005
- name: string;
1006
- description: string | null;
1007
- }
462
+ **Permission cache not invalidating after role change**
1008
463
 
1009
- // Permission operation result
1010
- interface PermissionOperationResultDto {
1011
- success: boolean;
1012
- added: number;
1013
- removed: number;
1014
- message: string;
1015
- }
1016
- ```
464
+ Check that `REDIS_URL` is configured correctly. If multiple service instances share the same Redis, cache invalidation propagates automatically. In single-instance in-memory mode, invalidation only affects the current process.
1017
465
 
1018
466
  ---
1019
467
 
1020
- ## API Reference
1021
-
1022
- ### Exports
468
+ **`No metadata for entity` on startup**
1023
469
 
1024
- ```typescript
1025
- // Config
1026
- export { IAM_MODULE_OPTIONS } from './config';
1027
-
1028
- // Modules
1029
- export { IAMModule } from './modules';
1030
-
1031
- // Entities
1032
- export { Action } from './entities/action.entity';
1033
- export { ActionBase } from './entities/action-base.entity';
1034
- export { Role } from './entities/role.entity';
1035
- export { RoleBase } from './entities/role-base.entity';
1036
- export { RoleWithCompany } from './entities/role-with-company.entity';
1037
- export { PermissionBase } from './entities/permission-base.entity';
1038
- export { UserIamPermission } from './entities/user-iam-permission.entity';
1039
- export { UserIamPermissionWithCompany } from './entities/permission-with-company.entity';
1040
- export { IAMCoreEntities, IAMCompanyEntities, IAMAllEntities, getIAMEntitiesByConfig } from './entities';
1041
-
1042
- // Services
1043
- export { ActionService } from './services/action.service';
1044
- export { RoleService } from './services/role.service';
1045
- export { PermissionService } from './services/permission.service';
1046
- export { PermissionCacheService } from './services/permission-cache.service';
1047
- export { IAMConfigService } from './services/iam-config.service';
1048
- export { IAMDataSourceService } from './services/iam-datasource.service';
1049
-
1050
- // Helpers
1051
- export { PermissionModeHelper } from './helpers/permission-mode.helper';
1052
- export { validateCompanyAccess } from './helpers/company-access.helper';
1053
-
1054
- // Enums
1055
- export { ActionType } from './enums/action-type.enum';
1056
- export { IAMPermissionMode } from './enums/permission-type.enum';
1057
-
1058
- // Types
1059
- export { LogicNode } from './types/logic-node.type';
1060
-
1061
- // DTOs
1062
- export * from './dtos';
1063
- // Key DTOs:
1064
- // - CreateActionDto, UpdateActionDto, ActionResponseDto, ActionTreeDto, ActionTreeQueryDto
1065
- // - CreateRoleDto, UpdateRoleDto, RoleResponseDto
1066
- // - PermissionAction (enum: ADD, REMOVE), PermissionItemDto
1067
- // - AssignUserActionsDto, AssignRoleActionsDto, AssignUserRolesDto, AssignCompanyActionsDto
1068
- // - GetUserActionsDto, GetRoleActionsDto, GetUserRolesDto, GetCompanyActionsDto
1069
- // - UserActionResponseDto, RoleActionResponseDto, UserRoleResponseDto, CompanyActionResponseDto
1070
- // - MyPermissionsQueryDto, MyPermissionsResponseDto, FrontendActionDto
1071
- // - PermissionOperationResultDto
1072
-
1073
- // Interfaces
1074
- export * from './interfaces';
1075
- // Key Interfaces:
1076
- // - IAction, IActionTree (for action responses)
1077
- // - IRole (for role responses)
1078
- // - IIAMModuleConfig, IAMModuleOptions, IAMOptionsFactory, IAMModuleAsyncOptions
1079
-
1080
- // Swagger Config
1081
- export { iamSwaggerConfig } from './docs';
1082
- ```
1083
-
1084
- ### Swagger Configuration
470
+ Use `IAMModule.getEntities()` with both `enableCompanyFeature` and `permissionMode` matching your `bootstrapAppConfig`:
1085
471
 
1086
472
  ```typescript
1087
- import { iamSwaggerConfig } from '@flusys/nestjs-iam';
1088
-
1089
- // Generate swagger config based on features
1090
- const swaggerOptions = iamSwaggerConfig(
1091
- enableCompanyFeature, // boolean
1092
- permissionMode, // IAMPermissionMode
1093
- );
1094
-
1095
- // Returns IModuleSwaggerOptions with:
1096
- // - title, description, version, path, bearerAuth
1097
- // - excludeSchemaProperties (hides companyId/branchId when company disabled)
1098
- // - excludeTags (hides unused controllers based on mode)
1099
- // - excludeQueryParameters (hides unused query params)
473
+ ...IAMModule.getEntities({ enableCompanyFeature: true, permissionMode: 'FULL' })
1100
474
  ```
1101
475
 
1102
- The swagger config automatically:
1103
- - Excludes Auth-related tags (Authentication, Users, Companies, etc.)
1104
- - Hides Company Action endpoints when company feature is disabled
1105
- - Hides RBAC endpoints (Roles, User-Roles) in DIRECT mode
1106
- - Hides Direct endpoints (User-Actions) in RBAC mode
1107
- - Shows appropriate documentation based on current configuration
476
+ ---
1108
477
 
1109
- ### Module Static Methods
478
+ **Company action whitelist blocks all permissions**
1110
479
 
1111
- ```typescript
1112
- // Configure module
1113
- IAMModule.forRoot(options?: IAMModuleOptions): DynamicModule
1114
- IAMModule.forRootAsync(options: IAMModuleAsyncOptions): DynamicModule
1115
- IAMModule.forFeature(options?: IAMModuleOptions): DynamicModule
1116
- ```
480
+ When `enableCompanyFeature: true`, a user's permissions are intersected with the company's action whitelist. If the whitelist is empty, the user has no permissions. Seed the whitelist using `POST /iam/company-action/assign`.
1117
481
 
1118
482
  ---
1119
483
 
1120
- ## Package Architecture
484
+ ## License
1121
485
 
1122
- ```
1123
- nestjs-iam/src/
1124
- ├── config/
1125
- │ ├── iam.constants.ts # IAM_MODULE_OPTIONS token
1126
- │ └── index.ts
1127
- ├── modules/
1128
- │ └── iam.module.ts # Main module with dynamic config
1129
- ├── entities/
1130
- │ ├── action-base.entity.ts # Action base fields
1131
- │ ├── action.entity.ts # Action entity
1132
- │ ├── role-base.entity.ts # Role base fields
1133
- │ ├── role.entity.ts # Role (no company)
1134
- │ ├── role-with-company.entity.ts
1135
- │ ├── permission-base.entity.ts # Permission base fields
1136
- │ ├── user-iam-permission.entity.ts
1137
- │ ├── permission-with-company.entity.ts
1138
- │ └── index.ts # Entity groups & getIAMEntitiesByConfig
1139
- ├── services/
1140
- │ ├── action.service.ts # Action CRUD
1141
- │ ├── role.service.ts # Role CRUD
1142
- │ ├── permission.service.ts # Permission management
1143
- │ ├── permission-cache.service.ts # Cache management
1144
- │ ├── iam-config.service.ts # Configuration
1145
- │ └── iam-datasource.service.ts # DataSource provider
1146
- ├── controllers/
1147
- │ ├── action.controller.ts
1148
- │ ├── role.controller.ts
1149
- │ ├── my-permission.controller.ts
1150
- │ ├── role-permission.controller.ts
1151
- │ ├── user-action-permission.controller.ts
1152
- │ └── company-action-permission.controller.ts
1153
- ├── helpers/
1154
- │ ├── company-access.helper.ts # Company access validation
1155
- │ └── permission-mode.helper.ts # String to enum conversion
1156
- ├── dtos/
1157
- │ ├── action.dto.ts
1158
- │ ├── role.dto.ts
1159
- │ └── permission.dto.ts
1160
- ├── interfaces/
1161
- │ ├── action.interface.ts
1162
- │ ├── role.interface.ts
1163
- │ └── iam-module-options.interface.ts
1164
- ├── enums/
1165
- │ ├── action-type.enum.ts
1166
- │ └── permission-type.enum.ts
1167
- ├── types/
1168
- │ └── logic-node.type.ts
1169
- ├── docs/
1170
- │ └── iam-swagger.config.ts
1171
- └── index.ts
1172
- ```
486
+ MIT © FLUSYS
1173
487
 
1174
488
  ---
1175
489
 
1176
- **Last Updated:** 2026-02-25
490
+ > Part of the **FLUSYS** framework — a full-stack monorepo powering Angular 21 + NestJS 11 applications.