@flusys/nestjs-iam 1.1.0-beta → 2.0.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 +285 -115
- package/cjs/controllers/action.controller.js +45 -2
- package/cjs/controllers/company-action-permission.controller.js +16 -10
- package/cjs/controllers/my-permission.controller.js +7 -3
- package/cjs/controllers/role-permission.controller.js +35 -17
- package/cjs/controllers/role.controller.js +46 -3
- package/cjs/controllers/user-action-permission.controller.js +26 -11
- package/cjs/dtos/action.dto.js +0 -27
- package/cjs/dtos/permission.dto.js +117 -27
- package/cjs/dtos/role.dto.js +0 -27
- package/cjs/entities/permission-base.entity.js +0 -12
- package/cjs/helpers/company-access.helper.js +19 -0
- package/cjs/helpers/index.js +1 -1
- package/cjs/interfaces/iam-module-options.interface.js +0 -14
- package/cjs/interfaces/index.js +0 -1
- package/cjs/modules/iam.module.js +50 -102
- package/cjs/services/action.service.js +30 -41
- package/cjs/services/iam-config.service.js +2 -5
- package/cjs/services/{iam-datasource.provider.js → iam-datasource.service.js} +33 -36
- package/cjs/services/index.js +1 -1
- package/cjs/services/permission-cache.service.js +31 -61
- package/cjs/services/permission.service.js +160 -188
- package/cjs/services/role.service.js +8 -8
- package/cjs/types/logic-node.type.js +0 -24
- package/controllers/company-action-permission.controller.d.ts +3 -3
- package/controllers/my-permission.controller.d.ts +2 -2
- package/controllers/role-permission.controller.d.ts +7 -5
- package/controllers/user-action-permission.controller.d.ts +6 -4
- package/dtos/action.dto.d.ts +0 -7
- package/dtos/permission.dto.d.ts +4 -0
- package/dtos/role.dto.d.ts +0 -7
- package/entities/permission-base.entity.d.ts +0 -4
- package/fesm/controllers/action.controller.js +47 -4
- package/fesm/controllers/company-action-permission.controller.js +18 -12
- package/fesm/controllers/index.js +1 -1
- package/fesm/controllers/my-permission.controller.js +7 -3
- package/fesm/controllers/role-permission.controller.js +37 -19
- package/fesm/controllers/role.controller.js +45 -2
- package/fesm/controllers/user-action-permission.controller.js +28 -13
- package/fesm/dtos/action.dto.js +0 -24
- package/fesm/dtos/permission.dto.js +117 -29
- package/fesm/dtos/role.dto.js +0 -24
- package/fesm/entities/permission-base.entity.js +0 -12
- package/fesm/helpers/company-access.helper.js +14 -0
- package/fesm/helpers/index.js +1 -1
- package/fesm/interfaces/iam-module-options.interface.js +3 -1
- package/fesm/interfaces/index.js +0 -1
- package/fesm/modules/iam.module.js +52 -104
- package/fesm/services/action.service.js +32 -43
- package/fesm/services/iam-config.service.js +2 -5
- package/fesm/services/{iam-datasource.provider.js → iam-datasource.service.js} +31 -34
- package/fesm/services/index.js +1 -1
- package/fesm/services/permission-cache.service.js +31 -61
- package/fesm/services/permission.service.js +161 -189
- package/fesm/services/role.service.js +8 -8
- package/fesm/types/logic-node.type.js +1 -10
- package/helpers/company-access.helper.d.ts +3 -0
- package/helpers/index.d.ts +1 -1
- package/interfaces/iam-module-options.interface.d.ts +9 -1
- package/interfaces/index.d.ts +0 -1
- package/modules/iam.module.d.ts +2 -2
- package/package.json +3 -3
- package/services/action.service.d.ts +6 -4
- package/services/iam-config.service.d.ts +2 -2
- package/services/{iam-datasource.provider.d.ts → iam-datasource.service.d.ts} +4 -5
- package/services/index.d.ts +1 -1
- package/services/permission-cache.service.d.ts +4 -6
- package/services/permission.service.d.ts +8 -4
- package/services/role.service.d.ts +3 -3
- package/types/logic-node.type.d.ts +0 -8
- package/cjs/helpers/permission-evaluator.helper.js +0 -175
- package/cjs/interfaces/iam-module-async-options.interface.js +0 -4
- package/fesm/helpers/permission-evaluator.helper.js +0 -165
- package/fesm/interfaces/iam-module-async-options.interface.js +0 -3
- package/helpers/permission-evaluator.helper.d.ts +0 -26
- package/interfaces/iam-module-async-options.interface.d.ts +0 -11
|
@@ -18,9 +18,8 @@ const _roleentity = require("../entities/role.entity");
|
|
|
18
18
|
const _useriampermissionentity = require("../entities/user-iam-permission.entity");
|
|
19
19
|
const _actiontypeenum = require("../enums/action-type.enum");
|
|
20
20
|
const _permissiontypeenum = require("../enums/permission-type.enum");
|
|
21
|
-
const _permissionevaluatorhelper = require("../helpers/permission-evaluator.helper");
|
|
22
21
|
const _iamconfigservice = require("./iam-config.service");
|
|
23
|
-
const
|
|
22
|
+
const _iamdatasourceservice = require("./iam-datasource.service");
|
|
24
23
|
const _permissioncacheservice = require("./permission-cache.service");
|
|
25
24
|
function _define_property(obj, key, value) {
|
|
26
25
|
if (key in obj) {
|
|
@@ -66,16 +65,16 @@ let PermissionService = class PermissionService {
|
|
|
66
65
|
}
|
|
67
66
|
// User-Action Permissions
|
|
68
67
|
async assignUserActions(dto) {
|
|
68
|
+
if (!this.iamConfigService.isDirectPermissionEnabled()) {
|
|
69
|
+
throw new _common.BadRequestException('Direct permission assignment not available in RBAC-only mode. Use role-based permissions instead.');
|
|
70
|
+
}
|
|
69
71
|
const permissionRepo = await this.getPermissionRepository();
|
|
70
72
|
const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
|
|
71
73
|
const branchId = dto.branchId ?? null;
|
|
72
74
|
const companyId = dto.companyId ?? null;
|
|
73
|
-
|
|
74
|
-
const itemsToAdd = dto.items.filter((item)=>item.action === _permissiondto.PermissionAction.ADD);
|
|
75
|
-
const itemsToRemove = dto.items.filter((item)=>item.action === _permissiondto.PermissionAction.REMOVE);
|
|
75
|
+
const { toAdd: itemsToAdd, toRemove: itemsToRemove } = this.splitItemsByAction(dto.items);
|
|
76
76
|
let added = 0;
|
|
77
77
|
let removed = 0;
|
|
78
|
-
// Batch add: Find existing permissions in one query
|
|
79
78
|
if (itemsToAdd.length > 0) {
|
|
80
79
|
const actionIdsToAdd = itemsToAdd.map((item)=>item.id);
|
|
81
80
|
const whereFind = {
|
|
@@ -107,11 +106,17 @@ let PermissionService = class PermissionService {
|
|
|
107
106
|
branchId: enableCompanyFeature ? branchId : null
|
|
108
107
|
}));
|
|
109
108
|
if (newPermissions.length > 0) {
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
try {
|
|
110
|
+
await permissionRepo.save(newPermissions);
|
|
111
|
+
added = newPermissions.length;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (error?.code === 'ER_DUP_ENTRY' || error?.message?.includes('Duplicate entry')) {
|
|
114
|
+
throw new _common.ConflictException('Some permissions already exist for this user. Please refresh and try again.');
|
|
115
|
+
}
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
112
118
|
}
|
|
113
119
|
}
|
|
114
|
-
// Batch remove: Delete all at once using IN clause
|
|
115
120
|
if (itemsToRemove.length > 0) {
|
|
116
121
|
const actionIdsToRemove = itemsToRemove.map((item)=>item.id);
|
|
117
122
|
const whereDelete = {
|
|
@@ -128,14 +133,8 @@ let PermissionService = class PermissionService {
|
|
|
128
133
|
const result = await permissionRepo.delete(whereDelete);
|
|
129
134
|
removed = result.affected || 0;
|
|
130
135
|
}
|
|
131
|
-
// Invalidate permission cache for this user
|
|
132
136
|
await this.invalidateUserPermissionCache(dto.userId, branchId, companyId);
|
|
133
|
-
return
|
|
134
|
-
success: true,
|
|
135
|
-
added,
|
|
136
|
-
removed,
|
|
137
|
-
message: `Successfully processed ${dto.items.length} items: ${added} added, ${removed} removed`
|
|
138
|
-
};
|
|
137
|
+
return this.buildOperationResult(dto.items.length, added, removed);
|
|
139
138
|
}
|
|
140
139
|
async getUserActions(userId, branchId, companyId) {
|
|
141
140
|
const permissionRepo = await this.getPermissionRepository();
|
|
@@ -146,22 +145,9 @@ let PermissionService = class PermissionService {
|
|
|
146
145
|
sourceType: _useriampermissionentity.IamEntityType.USER,
|
|
147
146
|
sourceId: userId
|
|
148
147
|
};
|
|
149
|
-
// When company feature enabled:
|
|
150
|
-
// - companyId provided → Filter by that company (optional additional filter)
|
|
151
|
-
// - branchId provided → Filter by that branch (branch-specific actions)
|
|
152
|
-
// - branchId not provided → Filter by IS NULL (company-wide actions)
|
|
153
148
|
if (enableCompanyFeature) {
|
|
154
|
-
if (companyId)
|
|
155
|
-
|
|
156
|
-
where.companyId = companyId;
|
|
157
|
-
}
|
|
158
|
-
if (branchId) {
|
|
159
|
-
// Branch-specific actions only
|
|
160
|
-
where.branchId = branchId;
|
|
161
|
-
} else {
|
|
162
|
-
// Company-wide actions only (branchId IS NULL)
|
|
163
|
-
where.branchId = null;
|
|
164
|
-
}
|
|
149
|
+
if (companyId) where.companyId = companyId;
|
|
150
|
+
where.branchId = branchId ?? null;
|
|
165
151
|
}
|
|
166
152
|
const permissions = await permissionRepo.find({
|
|
167
153
|
where
|
|
@@ -195,9 +181,11 @@ let PermissionService = class PermissionService {
|
|
|
195
181
|
}
|
|
196
182
|
// Role-Action Permissions
|
|
197
183
|
async assignRoleActions(dto) {
|
|
184
|
+
if (!this.iamConfigService.isRbacEnabled()) {
|
|
185
|
+
throw new _common.BadRequestException('Role-based permission assignment not available in DIRECT-only mode. Use direct user permissions instead.');
|
|
186
|
+
}
|
|
198
187
|
const permissionRepo = await this.getPermissionRepository();
|
|
199
188
|
const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
|
|
200
|
-
// Fetch role companyId if company feature is enabled
|
|
201
189
|
let roleCompanyId = null;
|
|
202
190
|
if (enableCompanyFeature) {
|
|
203
191
|
const roleRepo = await this.getRoleRepository();
|
|
@@ -212,12 +200,9 @@ let PermissionService = class PermissionService {
|
|
|
212
200
|
});
|
|
213
201
|
roleCompanyId = role?.companyId ?? null;
|
|
214
202
|
}
|
|
215
|
-
|
|
216
|
-
const itemsToAdd = dto.items.filter((item)=>item.action === _permissiondto.PermissionAction.ADD);
|
|
217
|
-
const itemsToRemove = dto.items.filter((item)=>item.action === _permissiondto.PermissionAction.REMOVE);
|
|
203
|
+
const { toAdd: itemsToAdd, toRemove: itemsToRemove } = this.splitItemsByAction(dto.items);
|
|
218
204
|
let added = 0;
|
|
219
205
|
let removed = 0;
|
|
220
|
-
// Batch add: Find existing permissions in one query
|
|
221
206
|
if (itemsToAdd.length > 0) {
|
|
222
207
|
const actionIdsToAdd = itemsToAdd.map((item)=>item.id);
|
|
223
208
|
const existingPermissions = await permissionRepo.find({
|
|
@@ -244,11 +229,17 @@ let PermissionService = class PermissionService {
|
|
|
244
229
|
branchId: null
|
|
245
230
|
}));
|
|
246
231
|
if (newPermissions.length > 0) {
|
|
247
|
-
|
|
248
|
-
|
|
232
|
+
try {
|
|
233
|
+
await permissionRepo.save(newPermissions);
|
|
234
|
+
added = newPermissions.length;
|
|
235
|
+
} catch (error) {
|
|
236
|
+
if (error?.code === 'ER_DUP_ENTRY' || error?.message?.includes('Duplicate entry')) {
|
|
237
|
+
throw new _common.ConflictException('Some role-action permissions already exist. Please refresh and try again.');
|
|
238
|
+
}
|
|
239
|
+
throw error;
|
|
240
|
+
}
|
|
249
241
|
}
|
|
250
242
|
}
|
|
251
|
-
// Batch remove: Delete all at once using IN clause
|
|
252
243
|
if (itemsToRemove.length > 0) {
|
|
253
244
|
const actionIdsToRemove = itemsToRemove.map((item)=>item.id);
|
|
254
245
|
const result = await permissionRepo.delete({
|
|
@@ -260,14 +251,8 @@ let PermissionService = class PermissionService {
|
|
|
260
251
|
});
|
|
261
252
|
removed = result.affected || 0;
|
|
262
253
|
}
|
|
263
|
-
// Invalidate cache for all users with this role
|
|
264
254
|
const affectedUsers = await this.invalidateRoleMembersCache(dto.roleId);
|
|
265
|
-
return {
|
|
266
|
-
success: true,
|
|
267
|
-
added,
|
|
268
|
-
removed,
|
|
269
|
-
message: `Successfully processed ${dto.items.length} items: ${added} added, ${removed} removed. Invalidated cache for ${affectedUsers} users.`
|
|
270
|
-
};
|
|
255
|
+
return this.buildOperationResult(dto.items.length, added, removed, `. Invalidated cache for ${affectedUsers} users.`);
|
|
271
256
|
}
|
|
272
257
|
async getRoleActions(roleId) {
|
|
273
258
|
const permissionRepo = await this.getPermissionRepository();
|
|
@@ -309,8 +294,7 @@ let PermissionService = class PermissionService {
|
|
|
309
294
|
/** Assign or remove actions to/from a company (whitelist) */ async assignCompanyActions(dto) {
|
|
310
295
|
const permissionRepo = await this.getPermissionRepository();
|
|
311
296
|
const dataSource = permissionRepo.manager.connection;
|
|
312
|
-
const itemsToAdd = dto.items
|
|
313
|
-
const itemsToRemove = dto.items.filter((item)=>item.action === _permissiondto.PermissionAction.REMOVE);
|
|
297
|
+
const { toAdd: itemsToAdd, toRemove: itemsToRemove } = this.splitItemsByAction(dto.items);
|
|
314
298
|
let added = 0;
|
|
315
299
|
let removed = 0;
|
|
316
300
|
let removedRoleActions = 0;
|
|
@@ -330,12 +314,7 @@ let PermissionService = class PermissionService {
|
|
|
330
314
|
});
|
|
331
315
|
const affectedCacheEntries = await this.invalidateCompanyMembersCache(dto.companyId);
|
|
332
316
|
const cascadeInfo = removedRoleActions > 0 || removedUserActions > 0 ? ` Cascaded removal: ${removedRoleActions} role permissions, ${removedUserActions} user permissions.` : '';
|
|
333
|
-
return {
|
|
334
|
-
success: true,
|
|
335
|
-
added,
|
|
336
|
-
removed,
|
|
337
|
-
message: `Successfully processed ${dto.items.length} items: ${added} added, ${removed} removed.${cascadeInfo} Invalidated ${affectedCacheEntries} cache entries.`
|
|
338
|
-
};
|
|
317
|
+
return this.buildOperationResult(dto.items.length, added, removed, `.${cascadeInfo} Invalidated ${affectedCacheEntries} cache entries.`);
|
|
339
318
|
}
|
|
340
319
|
async addCompanyActions(permissionRepo, companyId, actionIds) {
|
|
341
320
|
const existingPermissions = await permissionRepo.find({
|
|
@@ -469,16 +448,16 @@ let PermissionService = class PermissionService {
|
|
|
469
448
|
}
|
|
470
449
|
// User-Role Permissions
|
|
471
450
|
/** Assign user to roles (branch-scoped when company feature is enabled) */ async assignUserRoles(dto) {
|
|
451
|
+
if (!this.iamConfigService.isRbacEnabled()) {
|
|
452
|
+
throw new _common.BadRequestException('Role assignment not available in DIRECT-only mode. Use direct user permissions instead.');
|
|
453
|
+
}
|
|
472
454
|
const permissionRepo = await this.getPermissionRepository();
|
|
473
455
|
const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
|
|
474
456
|
const branchId = dto.branchId ?? null;
|
|
475
457
|
const companyId = dto.companyId ?? null;
|
|
476
|
-
|
|
477
|
-
const itemsToAdd = dto.items.filter((item)=>item.action === _permissiondto.PermissionAction.ADD);
|
|
478
|
-
const itemsToRemove = dto.items.filter((item)=>item.action === _permissiondto.PermissionAction.REMOVE);
|
|
458
|
+
const { toAdd: itemsToAdd, toRemove: itemsToRemove } = this.splitItemsByAction(dto.items);
|
|
479
459
|
let added = 0;
|
|
480
460
|
let removed = 0;
|
|
481
|
-
// Batch add: Find existing permissions in one query
|
|
482
461
|
if (itemsToAdd.length > 0) {
|
|
483
462
|
const roleIdsToAdd = itemsToAdd.map((item)=>item.id);
|
|
484
463
|
const whereFind = {
|
|
@@ -510,11 +489,17 @@ let PermissionService = class PermissionService {
|
|
|
510
489
|
branchId: enableCompanyFeature ? branchId : null
|
|
511
490
|
}));
|
|
512
491
|
if (newPermissions.length > 0) {
|
|
513
|
-
|
|
514
|
-
|
|
492
|
+
try {
|
|
493
|
+
await permissionRepo.save(newPermissions);
|
|
494
|
+
added = newPermissions.length;
|
|
495
|
+
} catch (error) {
|
|
496
|
+
if (error?.code === 'ER_DUP_ENTRY' || error?.message?.includes('Duplicate entry')) {
|
|
497
|
+
throw new _common.ConflictException('Some user-role permissions already exist. Please refresh and try again.');
|
|
498
|
+
}
|
|
499
|
+
throw error;
|
|
500
|
+
}
|
|
515
501
|
}
|
|
516
502
|
}
|
|
517
|
-
// Batch remove: Delete all at once using IN clause
|
|
518
503
|
if (itemsToRemove.length > 0) {
|
|
519
504
|
const roleIdsToRemove = itemsToRemove.map((item)=>item.id);
|
|
520
505
|
const whereDelete = {
|
|
@@ -531,41 +516,21 @@ let PermissionService = class PermissionService {
|
|
|
531
516
|
const result = await permissionRepo.delete(whereDelete);
|
|
532
517
|
removed = result.affected || 0;
|
|
533
518
|
}
|
|
534
|
-
// Invalidate permission cache for this user
|
|
535
519
|
await this.invalidateUserPermissionCache(dto.userId, branchId, companyId);
|
|
536
|
-
return
|
|
537
|
-
success: true,
|
|
538
|
-
added,
|
|
539
|
-
removed,
|
|
540
|
-
message: `Successfully processed ${dto.items.length} items: ${added} added, ${removed} removed`
|
|
541
|
-
};
|
|
520
|
+
return this.buildOperationResult(dto.items.length, added, removed);
|
|
542
521
|
}
|
|
543
522
|
/** Get user's roles (branch-scoped, filtered by companyId and branchId if provided) */ async getUserRoles(userId, branchId, companyId) {
|
|
544
523
|
const permissionRepo = await this.getPermissionRepository();
|
|
545
524
|
const roleRepo = await this.getRoleRepository();
|
|
546
525
|
const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
|
|
547
|
-
// Build where clause with optional companyId and branchId filter
|
|
548
|
-
// When company feature enabled:
|
|
549
|
-
// - companyId provided → Filter by that company (optional additional filter)
|
|
550
|
-
// - branchId provided → Filter by that branch (branch-specific roles)
|
|
551
|
-
// - branchId not provided → Filter by IS NULL (company-wide roles)
|
|
552
526
|
const where = {
|
|
553
527
|
permissionType: _useriampermissionentity.IamPermissionType.USER_ROLE,
|
|
554
528
|
sourceType: _useriampermissionentity.IamEntityType.USER,
|
|
555
529
|
sourceId: userId
|
|
556
530
|
};
|
|
557
531
|
if (enableCompanyFeature) {
|
|
558
|
-
if (companyId)
|
|
559
|
-
|
|
560
|
-
where.companyId = companyId;
|
|
561
|
-
}
|
|
562
|
-
if (branchId) {
|
|
563
|
-
// Branch-specific roles only
|
|
564
|
-
where.branchId = branchId;
|
|
565
|
-
} else {
|
|
566
|
-
// Company-wide roles only (branchId IS NULL)
|
|
567
|
-
where.branchId = null;
|
|
568
|
-
}
|
|
532
|
+
if (companyId) where.companyId = companyId;
|
|
533
|
+
where.branchId = branchId ?? null;
|
|
569
534
|
}
|
|
570
535
|
const permissions = await permissionRepo.find({
|
|
571
536
|
where
|
|
@@ -606,20 +571,15 @@ let PermissionService = class PermissionService {
|
|
|
606
571
|
branchId,
|
|
607
572
|
enableCompanyFeature
|
|
608
573
|
};
|
|
609
|
-
// Step 1: Check cache first
|
|
610
574
|
const cachedData = await this.permissionCacheService.getMyPermissions(cacheOptions);
|
|
611
575
|
if (cachedData) {
|
|
612
|
-
// Cache hit - use cached data (even if empty)
|
|
613
576
|
return this.buildResponseFromCache(cachedData, parentCodes);
|
|
614
577
|
}
|
|
615
|
-
// Step 2: Cache miss - fetch from DB and cache (even if empty)
|
|
616
578
|
const freshData = await this.fetchAndCachePermissions(userId, branchId, companyId);
|
|
617
|
-
// Step 3: Apply parentCodes filter and return
|
|
618
579
|
return this.buildResponseFromCache(freshData, parentCodes);
|
|
619
580
|
}
|
|
620
581
|
/** Build response from cached data, applying optional parentCodes filter */ async buildResponseFromCache(cachedData, parentCodes) {
|
|
621
582
|
let frontendActions = cachedData.frontendActions;
|
|
622
|
-
// Apply parentCodes filter if provided
|
|
623
583
|
if (parentCodes?.length) {
|
|
624
584
|
const parentIds = await this.getParentIdsByCodesWithCache(parentCodes);
|
|
625
585
|
if (parentIds.size > 0) {
|
|
@@ -638,13 +598,19 @@ let PermissionService = class PermissionService {
|
|
|
638
598
|
cachedEndpoints: cachedData.backendCodes.length
|
|
639
599
|
};
|
|
640
600
|
}
|
|
641
|
-
/** Get
|
|
642
|
-
|
|
643
|
-
|
|
601
|
+
/** Get current tenant ID for multi-tenant cache keys */ getCurrentTenantId() {
|
|
602
|
+
if (!this.iamConfigService.isMultiTenant()) {
|
|
603
|
+
return undefined;
|
|
604
|
+
}
|
|
605
|
+
const tenant = this.dataSourceProvider.getCurrentTenant();
|
|
606
|
+
return tenant?.id;
|
|
607
|
+
}
|
|
608
|
+
/** Get parent IDs by codes, using cache first (tenant-aware) */ async getParentIdsByCodesWithCache(codes) {
|
|
609
|
+
const tenantId = this.getCurrentTenantId();
|
|
610
|
+
const cachedMap = await this.permissionCacheService.getActionIdsByCodes(codes, tenantId);
|
|
644
611
|
if (cachedMap) {
|
|
645
612
|
return new Set(Object.values(cachedMap));
|
|
646
613
|
}
|
|
647
|
-
// Cache miss - fetch from DB and cache
|
|
648
614
|
const actionRepo = await this.getActionRepository();
|
|
649
615
|
const allActions = await actionRepo.find({
|
|
650
616
|
select: [
|
|
@@ -652,33 +618,42 @@ let PermissionService = class PermissionService {
|
|
|
652
618
|
'code'
|
|
653
619
|
]
|
|
654
620
|
});
|
|
655
|
-
// Build full code → ID map and cache it
|
|
656
621
|
const fullMap = {};
|
|
657
622
|
for (const action of allActions){
|
|
658
623
|
if (action.code) {
|
|
659
624
|
fullMap[action.code] = action.id;
|
|
660
625
|
}
|
|
661
626
|
}
|
|
662
|
-
await this.permissionCacheService.setActionCodeMap(fullMap);
|
|
663
|
-
// Return only requested codes
|
|
627
|
+
await this.permissionCacheService.setActionCodeMap(fullMap, tenantId);
|
|
664
628
|
return new Set(codes.map((code)=>fullMap[code]).filter(Boolean));
|
|
665
629
|
}
|
|
666
630
|
/** Fetch permissions from DB and cache them (empty permissions are also cached) */ async fetchAndCachePermissions(userId, branchId, companyId) {
|
|
667
631
|
const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
|
|
668
|
-
const permissionMode = this.iamConfigService.getPermissionMode();
|
|
669
632
|
const cacheOptions = {
|
|
670
633
|
userId,
|
|
671
634
|
companyId,
|
|
672
635
|
branchId,
|
|
673
636
|
enableCompanyFeature
|
|
674
637
|
};
|
|
675
|
-
// Empty cache data for users with no permissions
|
|
676
638
|
const emptyData = {
|
|
677
639
|
frontendActions: [],
|
|
678
640
|
backendCodes: []
|
|
679
641
|
};
|
|
642
|
+
const allActionIds = await this.collectAllActionIds(userId, branchId, companyId);
|
|
643
|
+
if (allActionIds.size === 0) {
|
|
644
|
+
await this.permissionCacheService.setMyPermissions(cacheOptions, emptyData);
|
|
645
|
+
return emptyData;
|
|
646
|
+
}
|
|
647
|
+
await this.applyCompanyWhitelist(allActionIds, companyId);
|
|
648
|
+
if (allActionIds.size === 0) {
|
|
649
|
+
await this.permissionCacheService.setMyPermissions(cacheOptions, emptyData);
|
|
650
|
+
return emptyData;
|
|
651
|
+
}
|
|
652
|
+
return this.buildAndCachePermissionData(allActionIds, cacheOptions);
|
|
653
|
+
}
|
|
654
|
+
/** Collect all action IDs based on permission mode (RBAC, DIRECT, or FULL) */ async collectAllActionIds(userId, branchId, companyId) {
|
|
655
|
+
const permissionMode = this.iamConfigService.getPermissionMode();
|
|
680
656
|
const allActionIds = new Set();
|
|
681
|
-
// Collect action IDs based on permission mode
|
|
682
657
|
if (permissionMode === _permissiontypeenum.IAMPermissionMode.RBAC || permissionMode === _permissiontypeenum.IAMPermissionMode.FULL) {
|
|
683
658
|
const userRoleIds = await this.getUserRoleIds(userId, branchId, companyId);
|
|
684
659
|
if (userRoleIds.length > 0) {
|
|
@@ -690,40 +665,33 @@ let PermissionService = class PermissionService {
|
|
|
690
665
|
const userActionIds = await this.getUserActionIds(userId, branchId, companyId);
|
|
691
666
|
userActionIds.forEach((id)=>allActionIds.add(id));
|
|
692
667
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
}
|
|
668
|
+
return allActionIds;
|
|
669
|
+
}
|
|
670
|
+
/** Apply company whitelist filter to action IDs (mutates the set) */ async applyCompanyWhitelist(actionIds, companyId) {
|
|
671
|
+
if (!this.iamConfigService.isCompanyFeatureEnabled() || !companyId) {
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
const companyActionIds = await this.getCompanyActionIds(companyId);
|
|
675
|
+
if (companyActionIds.length === 0) {
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
const allowedActionIds = new Set(companyActionIds);
|
|
679
|
+
for (const actionId of actionIds){
|
|
680
|
+
if (!allowedActionIds.has(actionId)) {
|
|
681
|
+
actionIds.delete(actionId);
|
|
708
682
|
}
|
|
709
683
|
}
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
await this.permissionCacheService.setMyPermissions(cacheOptions, emptyData);
|
|
713
|
-
return emptyData;
|
|
714
|
-
}
|
|
715
|
-
// Fetch actions from DB
|
|
684
|
+
}
|
|
685
|
+
/** Fetch actions from DB, separate by type, build cache data, and cache it */ async buildAndCachePermissionData(actionIds, cacheOptions) {
|
|
716
686
|
const actionRepo = await this.getActionRepository();
|
|
717
687
|
const actions = await actionRepo.find({
|
|
718
688
|
where: {
|
|
719
|
-
id: (0, _typeorm.In)(Array.from(
|
|
689
|
+
id: (0, _typeorm.In)(Array.from(actionIds))
|
|
720
690
|
}
|
|
721
691
|
});
|
|
722
|
-
// Separate by type
|
|
723
692
|
const backendActions = actions.filter((a)=>a.actionType === _actiontypeenum.ActionType.BACKEND || a.actionType === _actiontypeenum.ActionType.BOTH);
|
|
724
693
|
const frontendActions = actions.filter((a)=>a.actionType === _actiontypeenum.ActionType.FRONTEND || a.actionType === _actiontypeenum.ActionType.BOTH);
|
|
725
694
|
const backendCodes = backendActions.map((a)=>a.code).filter((c)=>!!c);
|
|
726
|
-
// Build cache data (includes parentId for filtering)
|
|
727
695
|
const cacheData = {
|
|
728
696
|
frontendActions: frontendActions.map((a)=>({
|
|
729
697
|
id: a.id,
|
|
@@ -734,7 +702,6 @@ let PermissionService = class PermissionService {
|
|
|
734
702
|
})),
|
|
735
703
|
backendCodes
|
|
736
704
|
};
|
|
737
|
-
// Cache both: full permissions and backend codes for PermissionGuard
|
|
738
705
|
await Promise.all([
|
|
739
706
|
this.permissionCacheService.setMyPermissions(cacheOptions, cacheData),
|
|
740
707
|
this.permissionCacheService.setPermissions(cacheOptions, backendCodes)
|
|
@@ -742,11 +709,24 @@ let PermissionService = class PermissionService {
|
|
|
742
709
|
return cacheData;
|
|
743
710
|
}
|
|
744
711
|
// Helper Methods
|
|
712
|
+
/** Split permission items into add and remove arrays */ splitItemsByAction(items) {
|
|
713
|
+
return {
|
|
714
|
+
toAdd: items.filter((item)=>item.action === _permissiondto.PermissionAction.ADD),
|
|
715
|
+
toRemove: items.filter((item)=>item.action === _permissiondto.PermissionAction.REMOVE)
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
/** Build standard operation result DTO */ buildOperationResult(totalItems, added, removed, additionalMessage = '') {
|
|
719
|
+
return {
|
|
720
|
+
success: true,
|
|
721
|
+
added,
|
|
722
|
+
removed,
|
|
723
|
+
message: `Successfully processed ${totalItems} items: ${added} added, ${removed} removed${additionalMessage}`
|
|
724
|
+
};
|
|
725
|
+
}
|
|
745
726
|
/** Get role IDs assigned to a user (merges company-wide + branch-specific roles) */ async getUserRoleIds(userId, branchId, companyId) {
|
|
746
727
|
const permissionRepo = await this.getPermissionRepository();
|
|
747
728
|
const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
|
|
748
729
|
if (!enableCompanyFeature) {
|
|
749
|
-
// Simple case: no company feature, get all user roles
|
|
750
730
|
const permissions = await permissionRepo.find({
|
|
751
731
|
where: {
|
|
752
732
|
permissionType: _useriampermissionentity.IamPermissionType.USER_ROLE,
|
|
@@ -754,33 +734,37 @@ let PermissionService = class PermissionService {
|
|
|
754
734
|
sourceId: userId
|
|
755
735
|
}
|
|
756
736
|
});
|
|
757
|
-
return permissions.map((p)=>p.targetId);
|
|
737
|
+
return permissions.filter((p)=>p.isValid()).map((p)=>p.targetId);
|
|
758
738
|
}
|
|
759
|
-
// Company feature enabled: merge company-wide + branch-specific roles
|
|
760
739
|
const roleIds = new Set();
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
branchId: (0, _typeorm.IsNull)(),
|
|
768
|
-
companyId: companyId
|
|
769
|
-
}
|
|
770
|
-
});
|
|
771
|
-
companyWidePermissions.forEach((p)=>roleIds.add(p.targetId));
|
|
772
|
-
// Step 2: Get branch-specific roles (branchId = value) if branchId provided
|
|
740
|
+
const baseWhere = {
|
|
741
|
+
permissionType: _useriampermissionentity.IamPermissionType.USER_ROLE,
|
|
742
|
+
sourceType: _useriampermissionentity.IamEntityType.USER,
|
|
743
|
+
sourceId: userId,
|
|
744
|
+
companyId: companyId
|
|
745
|
+
};
|
|
773
746
|
if (branchId) {
|
|
747
|
+
// Get company-wide + branch-specific roles
|
|
748
|
+
const companyWidePermissions = await permissionRepo.find({
|
|
749
|
+
where: {
|
|
750
|
+
...baseWhere,
|
|
751
|
+
branchId: (0, _typeorm.IsNull)()
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
companyWidePermissions.filter((p)=>p.isValid()).forEach((p)=>roleIds.add(p.targetId));
|
|
774
755
|
const branchPermissions = await permissionRepo.find({
|
|
775
756
|
where: {
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
sourceId: userId,
|
|
779
|
-
branchId,
|
|
780
|
-
companyId: companyId
|
|
757
|
+
...baseWhere,
|
|
758
|
+
branchId
|
|
781
759
|
}
|
|
782
760
|
});
|
|
783
|
-
branchPermissions.forEach((p)=>roleIds.add(p.targetId));
|
|
761
|
+
branchPermissions.filter((p)=>p.isValid()).forEach((p)=>roleIds.add(p.targetId));
|
|
762
|
+
} else {
|
|
763
|
+
// No branch context: get all roles for this company
|
|
764
|
+
const allPermissions = await permissionRepo.find({
|
|
765
|
+
where: baseWhere
|
|
766
|
+
});
|
|
767
|
+
allPermissions.filter((p)=>p.isValid()).forEach((p)=>roleIds.add(p.targetId));
|
|
784
768
|
}
|
|
785
769
|
return Array.from(roleIds);
|
|
786
770
|
}
|
|
@@ -793,13 +777,12 @@ let PermissionService = class PermissionService {
|
|
|
793
777
|
sourceId: (0, _typeorm.In)(roleIds)
|
|
794
778
|
}
|
|
795
779
|
});
|
|
796
|
-
return permissions.map((p)=>p.targetId);
|
|
780
|
+
return permissions.filter((p)=>p.isValid()).map((p)=>p.targetId);
|
|
797
781
|
}
|
|
798
782
|
/** Get action IDs directly assigned to a user (merges company-wide + branch-specific actions) */ async getUserActionIds(userId, branchId, companyId) {
|
|
799
783
|
const permissionRepo = await this.getPermissionRepository();
|
|
800
784
|
const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
|
|
801
785
|
if (!enableCompanyFeature) {
|
|
802
|
-
// Simple case: no company feature, get all user actions
|
|
803
786
|
const permissions = await permissionRepo.find({
|
|
804
787
|
where: {
|
|
805
788
|
permissionType: _useriampermissionentity.IamPermissionType.USER_ACTION,
|
|
@@ -807,44 +790,43 @@ let PermissionService = class PermissionService {
|
|
|
807
790
|
sourceId: userId
|
|
808
791
|
}
|
|
809
792
|
});
|
|
810
|
-
return permissions.map((p)=>p.targetId);
|
|
793
|
+
return permissions.filter((p)=>p.isValid()).map((p)=>p.targetId);
|
|
811
794
|
}
|
|
812
|
-
// Company feature enabled: merge company-wide + branch-specific actions
|
|
813
795
|
const actionIds = new Set();
|
|
814
|
-
|
|
815
|
-
const companyWideWhere = {
|
|
796
|
+
const baseWhere = {
|
|
816
797
|
permissionType: _useriampermissionentity.IamPermissionType.USER_ACTION,
|
|
817
798
|
sourceType: _useriampermissionentity.IamEntityType.USER,
|
|
818
|
-
sourceId: userId
|
|
819
|
-
branchId: (0, _typeorm.IsNull)()
|
|
799
|
+
sourceId: userId
|
|
820
800
|
};
|
|
821
801
|
if (companyId) {
|
|
822
|
-
|
|
802
|
+
baseWhere.companyId = companyId;
|
|
823
803
|
}
|
|
824
|
-
const companyWidePermissions = await permissionRepo.find({
|
|
825
|
-
where: companyWideWhere
|
|
826
|
-
});
|
|
827
|
-
companyWidePermissions.forEach((p)=>actionIds.add(p.targetId));
|
|
828
|
-
// Step 2: Get branch-specific actions (branchId = value, companyId = current) if branchId provided
|
|
829
804
|
if (branchId) {
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
}
|
|
805
|
+
// Get company-wide + branch-specific actions
|
|
806
|
+
const companyWidePermissions = await permissionRepo.find({
|
|
807
|
+
where: {
|
|
808
|
+
...baseWhere,
|
|
809
|
+
branchId: (0, _typeorm.IsNull)()
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
companyWidePermissions.filter((p)=>p.isValid()).forEach((p)=>actionIds.add(p.targetId));
|
|
839
813
|
const branchPermissions = await permissionRepo.find({
|
|
840
|
-
where:
|
|
814
|
+
where: {
|
|
815
|
+
...baseWhere,
|
|
816
|
+
branchId
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
branchPermissions.filter((p)=>p.isValid()).forEach((p)=>actionIds.add(p.targetId));
|
|
820
|
+
} else {
|
|
821
|
+
// No branch context: get all actions for this company
|
|
822
|
+
const allPermissions = await permissionRepo.find({
|
|
823
|
+
where: baseWhere
|
|
841
824
|
});
|
|
842
|
-
|
|
825
|
+
allPermissions.filter((p)=>p.isValid()).forEach((p)=>actionIds.add(p.targetId));
|
|
843
826
|
}
|
|
844
827
|
return Array.from(actionIds);
|
|
845
828
|
}
|
|
846
829
|
/** Invalidate permission cache for a user */ async invalidateUserPermissionCache(userId, branchId, companyId) {
|
|
847
|
-
// Wrap branchId in array if provided, otherwise invalidate null branch only
|
|
848
830
|
const branchIds = branchId !== undefined ? [
|
|
849
831
|
branchId
|
|
850
832
|
] : [
|
|
@@ -852,11 +834,10 @@ let PermissionService = class PermissionService {
|
|
|
852
834
|
];
|
|
853
835
|
await this.permissionCacheService.invalidateUser(userId, companyId, branchIds);
|
|
854
836
|
}
|
|
855
|
-
|
|
837
|
+
async invalidateRoleMembersCache(roleId) {
|
|
856
838
|
const permissionRepo = await this.getPermissionRepository();
|
|
857
839
|
const roleRepo = await this.getRoleRepository();
|
|
858
840
|
const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
|
|
859
|
-
// Find all users assigned to this role with their branch IDs
|
|
860
841
|
const userRoles = await permissionRepo.find({
|
|
861
842
|
where: {
|
|
862
843
|
permissionType: _useriampermissionentity.IamPermissionType.USER_ROLE,
|
|
@@ -865,21 +846,18 @@ let PermissionService = class PermissionService {
|
|
|
865
846
|
targetId: roleId
|
|
866
847
|
}
|
|
867
848
|
});
|
|
868
|
-
// Get unique user IDs
|
|
869
849
|
const userIds = [
|
|
870
850
|
...new Set(userRoles.map((ur)=>ur.sourceId))
|
|
871
851
|
];
|
|
872
852
|
if (userIds.length === 0) {
|
|
873
853
|
return 0;
|
|
874
854
|
}
|
|
875
|
-
// Get company ID from role (if company feature enabled)
|
|
876
855
|
const role = await roleRepo.findOne({
|
|
877
856
|
where: {
|
|
878
857
|
id: roleId
|
|
879
858
|
}
|
|
880
859
|
});
|
|
881
860
|
const companyId = role?.companyId || null;
|
|
882
|
-
// Get all branch IDs for these users (to invalidate all their cached branches)
|
|
883
861
|
let branchIds = [
|
|
884
862
|
null
|
|
885
863
|
];
|
|
@@ -893,15 +871,13 @@ let PermissionService = class PermissionService {
|
|
|
893
871
|
...new Set(userBranches.map((p)=>p.branchId))
|
|
894
872
|
];
|
|
895
873
|
}
|
|
896
|
-
|
|
897
|
-
return await this.permissionCacheService.invalidateRole(roleId, userIds, companyId, branchIds);
|
|
874
|
+
return this.permissionCacheService.invalidateRole(roleId, userIds, companyId, branchIds);
|
|
898
875
|
}
|
|
899
876
|
/** Invalidate permission cache for all users in a company */ async invalidateCompanyMembersCache(companyId) {
|
|
900
877
|
if (!this.iamConfigService.isCompanyFeatureEnabled()) {
|
|
901
878
|
return 0;
|
|
902
879
|
}
|
|
903
880
|
const permissionRepo = await this.getPermissionRepository();
|
|
904
|
-
// Find all unique user IDs and branch IDs that have permissions in this company
|
|
905
881
|
const userPermissions = await permissionRepo.createQueryBuilder('p').select('DISTINCT p.user_id', 'userId').addSelect('p.branch_id', 'branchId').where('p.company_id = :companyId', {
|
|
906
882
|
companyId
|
|
907
883
|
}).andWhere('p.user_id IS NOT NULL').getRawMany();
|
|
@@ -917,13 +893,11 @@ let PermissionService = class PermissionService {
|
|
|
917
893
|
return await this.permissionCacheService.invalidateUsers(userIds, companyId, branchIds);
|
|
918
894
|
}
|
|
919
895
|
// NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
|
|
920
|
-
constructor(
|
|
921
|
-
_define_property(this, "permissionEvaluator", void 0);
|
|
896
|
+
constructor(permissionCacheService, iamConfigService, dataSourceProvider){
|
|
922
897
|
_define_property(this, "permissionCacheService", void 0);
|
|
923
898
|
_define_property(this, "iamConfigService", void 0);
|
|
924
899
|
_define_property(this, "dataSourceProvider", void 0);
|
|
925
900
|
_define_property(this, "logger", void 0);
|
|
926
|
-
this.permissionEvaluator = permissionEvaluator;
|
|
927
901
|
this.permissionCacheService = permissionCacheService;
|
|
928
902
|
this.iamConfigService = iamConfigService;
|
|
929
903
|
this.dataSourceProvider = dataSourceProvider;
|
|
@@ -934,15 +908,13 @@ PermissionService = _ts_decorate([
|
|
|
934
908
|
(0, _common.Injectable)({
|
|
935
909
|
scope: _common.Scope.REQUEST
|
|
936
910
|
}),
|
|
937
|
-
_ts_param(0, (0, _common.Inject)(
|
|
938
|
-
_ts_param(1, (0, _common.Inject)(
|
|
939
|
-
_ts_param(2, (0, _common.Inject)(
|
|
940
|
-
_ts_param(3, (0, _common.Inject)(_iamdatasourceprovider.IAMDataSourceProvider)),
|
|
911
|
+
_ts_param(0, (0, _common.Inject)(_permissioncacheservice.PermissionCacheService)),
|
|
912
|
+
_ts_param(1, (0, _common.Inject)(_iamconfigservice.IAMConfigService)),
|
|
913
|
+
_ts_param(2, (0, _common.Inject)(_iamdatasourceservice.IAMDataSourceService)),
|
|
941
914
|
_ts_metadata("design:type", Function),
|
|
942
915
|
_ts_metadata("design:paramtypes", [
|
|
943
|
-
typeof _permissionevaluatorhelper.PermissionEvaluatorHelper === "undefined" ? Object : _permissionevaluatorhelper.PermissionEvaluatorHelper,
|
|
944
916
|
typeof _permissioncacheservice.PermissionCacheService === "undefined" ? Object : _permissioncacheservice.PermissionCacheService,
|
|
945
917
|
typeof _iamconfigservice.IAMConfigService === "undefined" ? Object : _iamconfigservice.IAMConfigService,
|
|
946
|
-
typeof
|
|
918
|
+
typeof _iamdatasourceservice.IAMDataSourceService === "undefined" ? Object : _iamdatasourceservice.IAMDataSourceService
|
|
947
919
|
])
|
|
948
920
|
], PermissionService);
|