@flusys/nestjs-iam 4.1.1 → 5.0.1
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 +87 -369
- package/cjs/config/message-keys.js +7 -49
- package/cjs/controllers/company-action-permission.controller.js +19 -18
- package/cjs/controllers/my-permission.controller.js +1 -4
- package/cjs/controllers/role-permission.controller.js +30 -20
- package/cjs/controllers/user-action-permission.controller.js +16 -11
- package/cjs/docs/iam-swagger.config.js +3 -2
- package/cjs/dtos/action.dto.js +0 -16
- package/cjs/dtos/permission.dto.js +4 -19
- package/cjs/dtos/role.dto.js +0 -16
- package/cjs/entities/action-base.entity.js +3 -8
- package/cjs/entities/permission-base.entity.js +1 -7
- package/cjs/entities/role-base.entity.js +1 -7
- package/cjs/services/action.service.js +1 -2
- package/cjs/services/permission.service.js +7 -14
- package/cjs/services/role.service.js +0 -1
- package/config/message-keys.d.ts +4 -84
- package/controllers/company-action-permission.controller.d.ts +3 -3
- package/controllers/role-permission.controller.d.ts +4 -4
- package/controllers/user-action-permission.controller.d.ts +3 -3
- package/docs/iam-swagger.config.d.ts +1 -1
- package/dtos/action.dto.d.ts +0 -2
- package/dtos/permission.dto.d.ts +1 -3
- package/dtos/role.dto.d.ts +0 -2
- package/entities/action-base.entity.d.ts +0 -1
- package/entities/permission-base.entity.d.ts +0 -1
- package/entities/role-base.entity.d.ts +0 -1
- package/fesm/config/message-keys.js +7 -44
- package/fesm/controllers/company-action-permission.controller.js +22 -21
- package/fesm/controllers/my-permission.controller.js +2 -5
- package/fesm/controllers/role-permission.controller.js +33 -23
- package/fesm/controllers/user-action-permission.controller.js +19 -14
- package/fesm/docs/iam-swagger.config.js +3 -2
- package/fesm/dtos/action.dto.js +0 -16
- package/fesm/dtos/permission.dto.js +4 -19
- package/fesm/dtos/role.dto.js +0 -16
- package/fesm/entities/action-base.entity.js +4 -9
- package/fesm/entities/permission-base.entity.js +1 -7
- package/fesm/entities/role-base.entity.js +1 -7
- package/fesm/services/action.service.js +1 -2
- package/fesm/services/permission.service.js +7 -14
- package/fesm/services/role.service.js +0 -1
- package/interfaces/action.interface.d.ts +0 -1
- package/interfaces/role.interface.d.ts +0 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,84 +1,9 @@
|
|
|
1
1
|
# @flusys/nestjs-iam
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Identity and Access Management for NestJS — RBAC, DIRECT, and FULL permission modes with caching and multi-tenant support.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@flusys/nestjs-iam)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
|
-
[](https://nestjs.com/)
|
|
8
|
-
[](https://www.typescriptlang.org/)
|
|
9
|
-
[](https://nodejs.org/)
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## Table of Contents
|
|
14
|
-
|
|
15
|
-
- [Overview](#overview)
|
|
16
|
-
- [Features](#features)
|
|
17
|
-
- [Permission Modes](#permission-modes)
|
|
18
|
-
- [Compatibility](#compatibility)
|
|
19
|
-
- [Installation](#installation)
|
|
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)
|
|
27
|
-
- [Entities](#entities)
|
|
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)
|
|
35
|
-
|
|
36
|
-
---
|
|
37
|
-
|
|
38
|
-
## Overview
|
|
39
|
-
|
|
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.
|
|
41
|
-
|
|
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`.
|
|
43
|
-
|
|
44
|
-
---
|
|
45
|
-
|
|
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` |
|
|
82
7
|
|
|
83
8
|
---
|
|
84
9
|
|
|
@@ -88,14 +13,13 @@ The module is fully independent from `nestjs-auth` — it only depends on `nestj
|
|
|
88
13
|
npm install @flusys/nestjs-iam @flusys/nestjs-shared @flusys/nestjs-core
|
|
89
14
|
```
|
|
90
15
|
|
|
91
|
-
|
|
16
|
+
## 1. Register the Module
|
|
92
17
|
|
|
93
|
-
|
|
18
|
+
### Synchronous
|
|
94
19
|
|
|
95
|
-
|
|
20
|
+
#### Mode 1: Single Database
|
|
96
21
|
|
|
97
22
|
```typescript
|
|
98
|
-
import { Module } from '@nestjs/common';
|
|
99
23
|
import { IAMModule } from '@flusys/nestjs-iam';
|
|
100
24
|
|
|
101
25
|
@Module({
|
|
@@ -106,13 +30,13 @@ import { IAMModule } from '@flusys/nestjs-iam';
|
|
|
106
30
|
bootstrapAppConfig: {
|
|
107
31
|
databaseMode: 'single',
|
|
108
32
|
enableCompanyFeature: false,
|
|
109
|
-
permissionMode: 'RBAC',
|
|
33
|
+
permissionMode: 'RBAC', // 'RBAC' | 'DIRECT' | 'FULL' (default: 'FULL')
|
|
110
34
|
},
|
|
111
35
|
config: {
|
|
112
36
|
defaultDatabaseConfig: {
|
|
113
|
-
type: '
|
|
37
|
+
type: 'mysql',
|
|
114
38
|
host: process.env.DB_HOST,
|
|
115
|
-
port: Number(process.env.DB_PORT ??
|
|
39
|
+
port: Number(process.env.DB_PORT ?? 3306),
|
|
116
40
|
username: process.env.DB_USER,
|
|
117
41
|
password: process.env.DB_PASSWORD,
|
|
118
42
|
database: process.env.DB_NAME,
|
|
@@ -124,45 +48,41 @@ import { IAMModule } from '@flusys/nestjs-iam';
|
|
|
124
48
|
export class AppModule {}
|
|
125
49
|
```
|
|
126
50
|
|
|
127
|
-
|
|
51
|
+
#### Mode 2: Multi-Tenant
|
|
128
52
|
|
|
129
53
|
```typescript
|
|
130
54
|
IAMModule.forRoot({
|
|
131
55
|
global: true,
|
|
132
56
|
includeController: true,
|
|
133
57
|
bootstrapAppConfig: {
|
|
134
|
-
databaseMode: '
|
|
135
|
-
enableCompanyFeature: true,
|
|
136
|
-
permissionMode: 'FULL',
|
|
58
|
+
databaseMode: 'multi-tenant',
|
|
59
|
+
enableCompanyFeature: true,
|
|
60
|
+
permissionMode: 'FULL',
|
|
137
61
|
},
|
|
138
|
-
config: {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
databaseMode: 'single' | 'multi-tenant';
|
|
154
|
-
enableCompanyFeature: boolean;
|
|
155
|
-
permissionMode?: 'FULL' | 'RBAC' | 'DIRECT'; // Default: 'FULL'
|
|
156
|
-
};
|
|
157
|
-
config?: IIAMModuleConfig;
|
|
158
|
-
})
|
|
62
|
+
config: {
|
|
63
|
+
tenantDefaultDatabaseConfig: {
|
|
64
|
+
type: 'mysql',
|
|
65
|
+
host: process.env.TENANT_DB_HOST,
|
|
66
|
+
port: Number(process.env.TENANT_DB_PORT ?? 3306),
|
|
67
|
+
username: process.env.TENANT_DB_USER,
|
|
68
|
+
password: process.env.TENANT_DB_PASSWORD,
|
|
69
|
+
database: process.env.TENANT_DB_NAME,
|
|
70
|
+
},
|
|
71
|
+
tenants: [
|
|
72
|
+
{ id: 'tenant-a', database: 'tenant_a_db', permissionMode: 'FULL' },
|
|
73
|
+
{ id: 'tenant-b', database: 'tenant_b_db', permissionMode: 'RBAC' },
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
});
|
|
159
77
|
```
|
|
160
78
|
|
|
161
|
-
###
|
|
79
|
+
### Asynchronous (with ConfigService)
|
|
162
80
|
|
|
163
81
|
```typescript
|
|
164
|
-
import { ConfigService } from '@nestjs/config';
|
|
82
|
+
import { ConfigModule, ConfigService } from '@nestjs/config';
|
|
83
|
+
import { IAMModule, ITenantDatabaseConfig } from '@flusys/nestjs-iam';
|
|
165
84
|
|
|
85
|
+
// Single database
|
|
166
86
|
IAMModule.forRootAsync({
|
|
167
87
|
global: true,
|
|
168
88
|
includeController: true,
|
|
@@ -172,9 +92,9 @@ IAMModule.forRootAsync({
|
|
|
172
92
|
permissionMode: 'FULL',
|
|
173
93
|
},
|
|
174
94
|
imports: [ConfigModule],
|
|
175
|
-
useFactory:
|
|
95
|
+
useFactory: (configService: ConfigService) => ({
|
|
176
96
|
defaultDatabaseConfig: {
|
|
177
|
-
type: '
|
|
97
|
+
type: 'mysql',
|
|
178
98
|
host: configService.get('DB_HOST'),
|
|
179
99
|
port: configService.get<number>('DB_PORT'),
|
|
180
100
|
username: configService.get('DB_USER'),
|
|
@@ -183,308 +103,106 @@ IAMModule.forRootAsync({
|
|
|
183
103
|
},
|
|
184
104
|
}),
|
|
185
105
|
inject: [ConfigService],
|
|
186
|
-
})
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
---
|
|
190
|
-
|
|
191
|
-
## Configuration Reference
|
|
192
|
-
|
|
193
|
-
```typescript
|
|
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[]
|
|
200
|
-
}
|
|
201
|
-
```
|
|
106
|
+
});
|
|
202
107
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
108
|
+
// Multi-tenant
|
|
109
|
+
IAMModule.forRootAsync({
|
|
110
|
+
global: true,
|
|
111
|
+
includeController: true,
|
|
112
|
+
bootstrapAppConfig: {
|
|
113
|
+
databaseMode: 'multi-tenant',
|
|
114
|
+
enableCompanyFeature: true,
|
|
115
|
+
permissionMode: 'FULL',
|
|
116
|
+
},
|
|
117
|
+
imports: [ConfigModule],
|
|
118
|
+
useFactory: (configService: ConfigService) => ({
|
|
119
|
+
tenantDefaultDatabaseConfig: {
|
|
120
|
+
type: 'mysql',
|
|
121
|
+
host: configService.get('TENANT_DB_HOST'),
|
|
122
|
+
port: configService.get<number>('TENANT_DB_PORT'),
|
|
123
|
+
username: configService.get('TENANT_DB_USER'),
|
|
124
|
+
password: configService.get('TENANT_DB_PASSWORD'),
|
|
125
|
+
database: configService.get('TENANT_DB_NAME'),
|
|
126
|
+
},
|
|
127
|
+
tenants: configService.get<ITenantDatabaseConfig[]>('TENANTS'),
|
|
128
|
+
}),
|
|
129
|
+
inject: [ConfigService],
|
|
130
|
+
});
|
|
211
131
|
```
|
|
212
132
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
## Feature Toggles
|
|
216
|
-
|
|
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 |
|
|
223
|
-
|
|
224
|
-
**Controller loading by mode:**
|
|
225
|
-
|
|
226
|
-
| Mode | + `enableCompanyFeature` | Additional controller |
|
|
227
|
-
|------|--------------------------|-----------------------|
|
|
228
|
-
| `RBAC` | true | `CompanyActionPermissionController` |
|
|
229
|
-
| `DIRECT` | true | `CompanyActionPermissionController` |
|
|
230
|
-
| `FULL` | true | `CompanyActionPermissionController` |
|
|
231
|
-
|
|
232
|
-
---
|
|
233
|
-
|
|
234
|
-
## API Endpoints
|
|
235
|
-
|
|
236
|
-
All endpoints use **POST** and require JWT authentication.
|
|
237
|
-
|
|
238
|
-
### Actions — `POST /iam/action/*`
|
|
239
|
-
|
|
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 |
|
|
248
|
-
|
|
249
|
-
### Roles — `POST /iam/role/*` *(RBAC and FULL modes)*
|
|
250
|
-
|
|
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 |
|
|
258
|
-
|
|
259
|
-
### Role Permissions — `POST /iam/role-permission/*` *(RBAC and FULL modes)*
|
|
260
|
-
|
|
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 |
|
|
268
|
-
|
|
269
|
-
### User Action Permissions — `POST /iam/user-action-permission/*` *(DIRECT and FULL modes)*
|
|
133
|
+
## 2. Register Entities in TypeORM
|
|
270
134
|
|
|
271
|
-
|
|
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 |
|
|
276
|
-
|
|
277
|
-
### Company Action Whitelist — `POST /iam/company-action/*` *(`enableCompanyFeature: true`)*
|
|
278
|
-
|
|
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 |
|
|
284
|
-
|
|
285
|
-
### My Permissions — `POST /iam/my-permission/*`
|
|
286
|
-
|
|
287
|
-
| Endpoint | Auth | Description |
|
|
288
|
-
|----------|------|-------------|
|
|
289
|
-
| `POST /iam/my-permission/get-all` | JWT | Get all permissions for current user |
|
|
290
|
-
|
|
291
|
-
---
|
|
292
|
-
|
|
293
|
-
## Entities
|
|
294
|
-
|
|
295
|
-
### Core Entities (always registered)
|
|
296
|
-
|
|
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 |
|
|
301
|
-
|
|
302
|
-
### RBAC Entities (`permissionMode: 'RBAC'` or `'FULL'`)
|
|
303
|
-
|
|
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 |
|
|
309
|
-
|
|
310
|
-
### DIRECT Entities (`permissionMode: 'DIRECT'` or `'FULL'`)
|
|
311
|
-
|
|
312
|
-
| Entity | Table | Description |
|
|
313
|
-
|--------|-------|-------------|
|
|
314
|
-
| `UserAction` | `iam_user_action` | Direct User → Action assignments |
|
|
315
|
-
|
|
316
|
-
### Company Feature Entities (`enableCompanyFeature: true`)
|
|
317
|
-
|
|
318
|
-
All above entities get `WithCompany` variants with `companyId` and `branchId` columns, plus:
|
|
319
|
-
|
|
320
|
-
| Entity | Table | Description |
|
|
321
|
-
|--------|-------|-------------|
|
|
322
|
-
| `CompanyAction` | `iam_company_action` | Actions whitelisted per company |
|
|
323
|
-
|
|
324
|
-
#### Register Entities in TypeORM
|
|
135
|
+
Use `getIAMEntitiesByConfig()` — arguments must match `bootstrapAppConfig`:
|
|
325
136
|
|
|
326
137
|
```typescript
|
|
327
|
-
import {
|
|
138
|
+
import { getIAMEntitiesByConfig } from '@flusys/nestjs-iam/entities';
|
|
328
139
|
|
|
329
140
|
TypeOrmModule.forRoot({
|
|
330
141
|
entities: [
|
|
331
|
-
...
|
|
332
|
-
|
|
333
|
-
permissionMode: 'FULL'
|
|
334
|
-
|
|
142
|
+
...getIAMEntitiesByConfig(
|
|
143
|
+
true, // enableCompanyFeature
|
|
144
|
+
'FULL', // permissionMode: 'FULL' | 'RBAC' | 'DIRECT'
|
|
145
|
+
),
|
|
335
146
|
],
|
|
336
|
-
})
|
|
147
|
+
});
|
|
337
148
|
```
|
|
338
149
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
## Permission Cache
|
|
342
|
-
|
|
343
|
-
Permissions are cached with the key `iam:permissions:{userId}:{companyId}:{branchId}` for **1 hour**. The cache is automatically invalidated when:
|
|
344
|
-
|
|
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
|
|
349
|
-
|
|
350
|
-
Cache uses `HybridCache` (in-memory + Redis). If Redis is not configured, in-memory cache is used.
|
|
351
|
-
|
|
352
|
-
---
|
|
353
|
-
|
|
354
|
-
## Exported Services
|
|
150
|
+
## 3. Protect Endpoints
|
|
355
151
|
|
|
356
|
-
|
|
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 |
|
|
364
|
-
|
|
365
|
-
---
|
|
366
|
-
|
|
367
|
-
## Using Permissions in Controllers
|
|
368
|
-
|
|
369
|
-
### Using the Decorator
|
|
152
|
+
All FLUSYS endpoints use POST. Apply `JwtAuthGuard` and `@RequirePermission` from `nestjs-shared`:
|
|
370
153
|
|
|
371
154
|
```typescript
|
|
372
|
-
import { RequirePermission } from '@flusys/nestjs-shared/decorators';
|
|
373
155
|
import { JwtAuthGuard } from '@flusys/nestjs-shared/guards';
|
|
156
|
+
import { RequirePermission, CurrentUser } from '@flusys/nestjs-shared/decorators';
|
|
157
|
+
import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
|
|
374
158
|
|
|
375
159
|
@UseGuards(JwtAuthGuard)
|
|
376
160
|
@Controller('products')
|
|
377
161
|
export class ProductController {
|
|
378
162
|
@Post('insert')
|
|
379
163
|
@RequirePermission('product.create')
|
|
380
|
-
async create() {
|
|
164
|
+
async create(@CurrentUser() user: ILoggedUserInfo) {
|
|
165
|
+
/* ... */
|
|
166
|
+
}
|
|
381
167
|
|
|
382
168
|
@Post('get-all')
|
|
383
169
|
@RequirePermission('product.read')
|
|
384
|
-
async getAll() {
|
|
170
|
+
async getAll(@CurrentUser() user: ILoggedUserInfo) {
|
|
171
|
+
/* ... */
|
|
172
|
+
}
|
|
385
173
|
}
|
|
386
174
|
```
|
|
387
175
|
|
|
388
|
-
|
|
176
|
+
Wildcard matching is supported: `@RequirePermission('product.*')` matches any action whose code starts with `product.`.
|
|
177
|
+
|
|
178
|
+
## 5. Programmatic Permission Check
|
|
179
|
+
|
|
180
|
+
Inject `PermissionService` (use `@Inject()` — required for bundled code):
|
|
389
181
|
|
|
390
182
|
```typescript
|
|
391
183
|
import { PermissionService } from '@flusys/nestjs-iam';
|
|
392
184
|
|
|
393
185
|
@Injectable()
|
|
394
186
|
export class ProductService {
|
|
395
|
-
constructor(
|
|
396
|
-
@Inject(PermissionService) private readonly permissionService: PermissionService,
|
|
397
|
-
) {}
|
|
187
|
+
constructor(@Inject(PermissionService) private readonly permissionService: PermissionService) {}
|
|
398
188
|
|
|
399
|
-
async
|
|
189
|
+
async canCreate(userId: string, companyId?: string): Promise<boolean> {
|
|
400
190
|
return this.permissionService.hasPermission(userId, 'product.create', companyId);
|
|
401
191
|
}
|
|
402
|
-
|
|
403
|
-
async getUserPermissions(userId: string, companyId?: string): Promise<string[]> {
|
|
404
|
-
return this.permissionService.getPermissions(userId, companyId);
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
---
|
|
410
|
-
|
|
411
|
-
## Action Types
|
|
412
|
-
|
|
413
|
-
| Type | Description |
|
|
414
|
-
|------|-------------|
|
|
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"
|
|
427
192
|
}
|
|
428
193
|
```
|
|
429
194
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
## Hierarchical Actions
|
|
433
|
-
|
|
434
|
-
Actions support parent-child relationships. Use `parentId` to create a tree:
|
|
435
|
-
|
|
436
|
-
```
|
|
437
|
-
product (BOTH - parent)
|
|
438
|
-
├── product.read (BOTH - child)
|
|
439
|
-
├── product.create (BOTH - child)
|
|
440
|
-
├── product.update (BOTH - child)
|
|
441
|
-
└── product.delete (BOTH - child)
|
|
442
|
-
```
|
|
443
|
-
|
|
444
|
-
Get the full tree:
|
|
445
|
-
```json
|
|
446
|
-
POST /iam/action/get-tree
|
|
447
|
-
{}
|
|
448
|
-
```
|
|
449
|
-
|
|
450
|
-
The wildcard `product.*` in `@RequirePermission('product.*')` matches any action whose code starts with `product.`.
|
|
451
|
-
|
|
452
|
-
---
|
|
453
|
-
|
|
454
|
-
## Troubleshooting
|
|
455
|
-
|
|
456
|
-
**`PermissionGuard` always denies — user has correct role**
|
|
457
|
-
|
|
458
|
-
Check that the IAM entities are included in your `TypeOrmModule` registration. Missing entities cause the permission query to return empty results.
|
|
459
|
-
|
|
460
|
-
---
|
|
461
|
-
|
|
462
|
-
**Permission cache not invalidating after role change**
|
|
463
|
-
|
|
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.
|
|
465
|
-
|
|
466
|
-
---
|
|
467
|
-
|
|
468
|
-
**`No metadata for entity` on startup**
|
|
469
|
-
|
|
470
|
-
Use `IAMModule.getEntities()` with both `enableCompanyFeature` and `permissionMode` matching your `bootstrapAppConfig`:
|
|
471
|
-
|
|
472
|
-
```typescript
|
|
473
|
-
...IAMModule.getEntities({ enableCompanyFeature: true, permissionMode: 'FULL' })
|
|
474
|
-
```
|
|
475
|
-
|
|
476
|
-
---
|
|
195
|
+
## Exported Services
|
|
477
196
|
|
|
478
|
-
|
|
197
|
+
| Service | Scope | Description |
|
|
198
|
+
| ------------------- | ------- | -------------------------------------------------------------- |
|
|
199
|
+
| `PermissionService` | REQUEST | `hasPermission()`, `getUserPermissions()`, `getRolesForUser()` |
|
|
200
|
+
| |
|
|
479
201
|
|
|
480
|
-
|
|
202
|
+
All constructor injections need explicit `@Inject()` — TypeScript metadata is lost during esbuild bundling.
|
|
481
203
|
|
|
482
204
|
---
|
|
483
205
|
|
|
484
206
|
## License
|
|
485
207
|
|
|
486
208
|
MIT © FLUSYS
|
|
487
|
-
|
|
488
|
-
---
|
|
489
|
-
|
|
490
|
-
> Part of the **FLUSYS** framework — a full-stack monorepo powering Angular 21 + NestJS 11 applications.
|
|
@@ -19,18 +19,12 @@ _export(exports, {
|
|
|
19
19
|
get IAM_MODE_MESSAGES () {
|
|
20
20
|
return IAM_MODE_MESSAGES;
|
|
21
21
|
},
|
|
22
|
-
get IAM_MODULE_MESSAGES () {
|
|
23
|
-
return IAM_MODULE_MESSAGES;
|
|
24
|
-
},
|
|
25
22
|
get MY_PERMISSION_MESSAGES () {
|
|
26
23
|
return MY_PERMISSION_MESSAGES;
|
|
27
24
|
},
|
|
28
25
|
get PERMISSION_OPERATION_MESSAGES () {
|
|
29
26
|
return PERMISSION_OPERATION_MESSAGES;
|
|
30
27
|
},
|
|
31
|
-
get ROLE_MESSAGES () {
|
|
32
|
-
return ROLE_MESSAGES;
|
|
33
|
-
},
|
|
34
28
|
get ROLE_PERMISSION_MESSAGES () {
|
|
35
29
|
return ROLE_PERMISSION_MESSAGES;
|
|
36
30
|
},
|
|
@@ -39,43 +33,22 @@ _export(exports, {
|
|
|
39
33
|
}
|
|
40
34
|
});
|
|
41
35
|
const ACTION_MESSAGES = {
|
|
42
|
-
|
|
43
|
-
CREATE_MANY_SUCCESS: 'action.create.many.success',
|
|
44
|
-
GET_SUCCESS: 'action.get.success',
|
|
45
|
-
GET_ALL_SUCCESS: 'action.get.all.success',
|
|
46
|
-
UPDATE_SUCCESS: 'action.update.success',
|
|
47
|
-
UPDATE_MANY_SUCCESS: 'action.update.many.success',
|
|
48
|
-
DELETE_SUCCESS: 'action.delete.success',
|
|
49
|
-
RESTORE_SUCCESS: 'action.restore.success',
|
|
50
|
-
NOT_FOUND: 'action.not.found'
|
|
36
|
+
GET_ALL_SUCCESS: 'action.get.all.success'
|
|
51
37
|
};
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
GET_ALL_SUCCESS: 'role.get.all.success',
|
|
57
|
-
UPDATE_SUCCESS: 'role.update.success',
|
|
58
|
-
UPDATE_MANY_SUCCESS: 'role.update.many.success',
|
|
59
|
-
DELETE_SUCCESS: 'role.delete.success',
|
|
60
|
-
RESTORE_SUCCESS: 'role.restore.success',
|
|
61
|
-
NOT_FOUND: 'role.not.found'
|
|
38
|
+
const PERMISSION_OPERATION_MESSAGES = {
|
|
39
|
+
PROCESS_SUCCESS: 'permission.process.success',
|
|
40
|
+
ALREADY_EXISTS: 'permission.already.exists',
|
|
41
|
+
USER_REQUIRED: 'permission.user.required'
|
|
62
42
|
};
|
|
63
43
|
const ROLE_PERMISSION_MESSAGES = {
|
|
64
|
-
GET_SUCCESS: 'role.permission.get.success',
|
|
65
|
-
ASSIGN_SUCCESS: 'role.permission.assign.success',
|
|
66
44
|
ACTIONS_SUCCESS: 'role.permission.actions.success',
|
|
67
|
-
USERS_SUCCESS: 'role.permission.users.success',
|
|
68
45
|
USER_ROLES_SUCCESS: 'role.permission.user.roles.success'
|
|
69
46
|
};
|
|
70
47
|
const USER_ACTION_PERMISSION_MESSAGES = {
|
|
71
|
-
GET_SUCCESS: 'user.action.permission.get.success'
|
|
72
|
-
ASSIGN_SUCCESS: 'user.action.permission.assign.success',
|
|
73
|
-
REVOKE_SUCCESS: 'user.action.permission.revoke.success'
|
|
48
|
+
GET_SUCCESS: 'user.action.permission.get.success'
|
|
74
49
|
};
|
|
75
50
|
const COMPANY_ACTION_PERMISSION_MESSAGES = {
|
|
76
|
-
GET_SUCCESS: 'company.action.permission.get.success'
|
|
77
|
-
ASSIGN_SUCCESS: 'company.action.permission.assign.success',
|
|
78
|
-
REVOKE_SUCCESS: 'company.action.permission.revoke.success'
|
|
51
|
+
GET_SUCCESS: 'company.action.permission.get.success'
|
|
79
52
|
};
|
|
80
53
|
const MY_PERMISSION_MESSAGES = {
|
|
81
54
|
GET_SUCCESS: 'my.permission.get.success'
|
|
@@ -85,18 +58,3 @@ const IAM_MODE_MESSAGES = {
|
|
|
85
58
|
RBAC_MODE_UNAVAILABLE: 'iam.rbac.mode.unavailable',
|
|
86
59
|
ROLE_ASSIGNMENT_UNAVAILABLE: 'iam.role.assignment.unavailable'
|
|
87
60
|
};
|
|
88
|
-
const PERMISSION_OPERATION_MESSAGES = {
|
|
89
|
-
PROCESS_SUCCESS: 'permission.process.success',
|
|
90
|
-
ALREADY_EXISTS: 'permission.already.exists',
|
|
91
|
-
USER_REQUIRED: 'permission.user.required'
|
|
92
|
-
};
|
|
93
|
-
const IAM_MODULE_MESSAGES = {
|
|
94
|
-
ACTION: ACTION_MESSAGES,
|
|
95
|
-
ROLE: ROLE_MESSAGES,
|
|
96
|
-
ROLE_PERMISSION: ROLE_PERMISSION_MESSAGES,
|
|
97
|
-
USER_ACTION_PERMISSION: USER_ACTION_PERMISSION_MESSAGES,
|
|
98
|
-
COMPANY_ACTION_PERMISSION: COMPANY_ACTION_PERMISSION_MESSAGES,
|
|
99
|
-
MY_PERMISSION: MY_PERMISSION_MESSAGES,
|
|
100
|
-
IAM_MODE: IAM_MODE_MESSAGES,
|
|
101
|
-
PERMISSION_OPERATION: PERMISSION_OPERATION_MESSAGES
|
|
102
|
-
};
|