@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.
Files changed (76) hide show
  1. package/README.md +285 -115
  2. package/cjs/controllers/action.controller.js +45 -2
  3. package/cjs/controllers/company-action-permission.controller.js +16 -10
  4. package/cjs/controllers/my-permission.controller.js +7 -3
  5. package/cjs/controllers/role-permission.controller.js +35 -17
  6. package/cjs/controllers/role.controller.js +46 -3
  7. package/cjs/controllers/user-action-permission.controller.js +26 -11
  8. package/cjs/dtos/action.dto.js +0 -27
  9. package/cjs/dtos/permission.dto.js +117 -27
  10. package/cjs/dtos/role.dto.js +0 -27
  11. package/cjs/entities/permission-base.entity.js +0 -12
  12. package/cjs/helpers/company-access.helper.js +19 -0
  13. package/cjs/helpers/index.js +1 -1
  14. package/cjs/interfaces/iam-module-options.interface.js +0 -14
  15. package/cjs/interfaces/index.js +0 -1
  16. package/cjs/modules/iam.module.js +50 -102
  17. package/cjs/services/action.service.js +30 -41
  18. package/cjs/services/iam-config.service.js +2 -5
  19. package/cjs/services/{iam-datasource.provider.js → iam-datasource.service.js} +33 -36
  20. package/cjs/services/index.js +1 -1
  21. package/cjs/services/permission-cache.service.js +31 -61
  22. package/cjs/services/permission.service.js +160 -188
  23. package/cjs/services/role.service.js +8 -8
  24. package/cjs/types/logic-node.type.js +0 -24
  25. package/controllers/company-action-permission.controller.d.ts +3 -3
  26. package/controllers/my-permission.controller.d.ts +2 -2
  27. package/controllers/role-permission.controller.d.ts +7 -5
  28. package/controllers/user-action-permission.controller.d.ts +6 -4
  29. package/dtos/action.dto.d.ts +0 -7
  30. package/dtos/permission.dto.d.ts +4 -0
  31. package/dtos/role.dto.d.ts +0 -7
  32. package/entities/permission-base.entity.d.ts +0 -4
  33. package/fesm/controllers/action.controller.js +47 -4
  34. package/fesm/controllers/company-action-permission.controller.js +18 -12
  35. package/fesm/controllers/index.js +1 -1
  36. package/fesm/controllers/my-permission.controller.js +7 -3
  37. package/fesm/controllers/role-permission.controller.js +37 -19
  38. package/fesm/controllers/role.controller.js +45 -2
  39. package/fesm/controllers/user-action-permission.controller.js +28 -13
  40. package/fesm/dtos/action.dto.js +0 -24
  41. package/fesm/dtos/permission.dto.js +117 -29
  42. package/fesm/dtos/role.dto.js +0 -24
  43. package/fesm/entities/permission-base.entity.js +0 -12
  44. package/fesm/helpers/company-access.helper.js +14 -0
  45. package/fesm/helpers/index.js +1 -1
  46. package/fesm/interfaces/iam-module-options.interface.js +3 -1
  47. package/fesm/interfaces/index.js +0 -1
  48. package/fesm/modules/iam.module.js +52 -104
  49. package/fesm/services/action.service.js +32 -43
  50. package/fesm/services/iam-config.service.js +2 -5
  51. package/fesm/services/{iam-datasource.provider.js → iam-datasource.service.js} +31 -34
  52. package/fesm/services/index.js +1 -1
  53. package/fesm/services/permission-cache.service.js +31 -61
  54. package/fesm/services/permission.service.js +161 -189
  55. package/fesm/services/role.service.js +8 -8
  56. package/fesm/types/logic-node.type.js +1 -10
  57. package/helpers/company-access.helper.d.ts +3 -0
  58. package/helpers/index.d.ts +1 -1
  59. package/interfaces/iam-module-options.interface.d.ts +9 -1
  60. package/interfaces/index.d.ts +0 -1
  61. package/modules/iam.module.d.ts +2 -2
  62. package/package.json +3 -3
  63. package/services/action.service.d.ts +6 -4
  64. package/services/iam-config.service.d.ts +2 -2
  65. package/services/{iam-datasource.provider.d.ts → iam-datasource.service.d.ts} +4 -5
  66. package/services/index.d.ts +1 -1
  67. package/services/permission-cache.service.d.ts +4 -6
  68. package/services/permission.service.d.ts +8 -4
  69. package/services/role.service.d.ts +3 -3
  70. package/types/logic-node.type.d.ts +0 -8
  71. package/cjs/helpers/permission-evaluator.helper.js +0 -175
  72. package/cjs/interfaces/iam-module-async-options.interface.js +0 -4
  73. package/fesm/helpers/permission-evaluator.helper.js +0 -165
  74. package/fesm/interfaces/iam-module-async-options.interface.js +0 -3
  75. package/helpers/permission-evaluator.helper.d.ts +0 -26
  76. 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 _iamdatasourceprovider = require("./iam-datasource.provider");
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
- // Separate items into add and remove batches
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
- await permissionRepo.save(newPermissions);
111
- added = newPermissions.length;
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
- // Filter by companyId if provided
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
- // Separate items into add and remove batches
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
- await permissionRepo.save(newPermissions);
248
- added = newPermissions.length;
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.filter((item)=>item.action === _permissiondto.PermissionAction.ADD);
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
- // Separate items into add and remove batches
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
- await permissionRepo.save(newPermissions);
514
- added = newPermissions.length;
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
- // Filter by companyId if provided
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 parent IDs by codes, using cache first */ async getParentIdsByCodesWithCache(codes) {
642
- // Try cache first
643
- const cachedMap = await this.permissionCacheService.getActionIdsByCodes(codes);
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
- if (allActionIds.size === 0) {
694
- // Cache empty result to avoid repeated DB hits
695
- await this.permissionCacheService.setMyPermissions(cacheOptions, emptyData);
696
- return emptyData;
697
- }
698
- // Filter by company whitelist
699
- if (enableCompanyFeature && companyId) {
700
- const companyActionIds = await this.getCompanyActionIds(companyId);
701
- if (companyActionIds.length > 0) {
702
- const allowedActionIds = new Set(companyActionIds);
703
- for (const actionId of allActionIds){
704
- if (!allowedActionIds.has(actionId)) {
705
- allActionIds.delete(actionId);
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
- if (allActionIds.size === 0) {
711
- // Cache empty result to avoid repeated DB hits
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(allActionIds))
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
- // Step 1: Get company-wide roles (branchId = null)
762
- const companyWidePermissions = await permissionRepo.find({
763
- where: {
764
- permissionType: _useriampermissionentity.IamPermissionType.USER_ROLE,
765
- sourceType: _useriampermissionentity.IamEntityType.USER,
766
- sourceId: userId,
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
- permissionType: _useriampermissionentity.IamPermissionType.USER_ROLE,
777
- sourceType: _useriampermissionentity.IamEntityType.USER,
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
- // Step 1: Get company-wide actions (branchId = null, companyId = current)
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
- companyWideWhere.companyId = companyId;
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
- const branchWhere = {
831
- permissionType: _useriampermissionentity.IamPermissionType.USER_ACTION,
832
- sourceType: _useriampermissionentity.IamEntityType.USER,
833
- sourceId: userId,
834
- branchId
835
- };
836
- if (companyId) {
837
- branchWhere.companyId = companyId;
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: branchWhere
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
- branchPermissions.forEach((p)=>actionIds.add(p.targetId));
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
- /** Invalidate permission cache for all users with a specific role */ async invalidateRoleMembersCache(roleId) {
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
- // Invalidate cache for all users with all their branches
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(permissionEvaluator, permissionCacheService, iamConfigService, dataSourceProvider){
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)(_permissionevaluatorhelper.PermissionEvaluatorHelper)),
938
- _ts_param(1, (0, _common.Inject)(_permissioncacheservice.PermissionCacheService)),
939
- _ts_param(2, (0, _common.Inject)(_iamconfigservice.IAMConfigService)),
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 _iamdatasourceprovider.IAMDataSourceProvider === "undefined" ? Object : _iamdatasourceprovider.IAMDataSourceProvider
918
+ typeof _iamdatasourceservice.IAMDataSourceService === "undefined" ? Object : _iamdatasourceservice.IAMDataSourceService
947
919
  ])
948
920
  ], PermissionService);