@flusys/nestjs-iam 6.0.2 → 6.1.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.
Files changed (61) hide show
  1. package/cjs/controllers/action.controller.spec.js +79 -0
  2. package/cjs/controllers/company-action-permission.controller.js +1 -1
  3. package/cjs/controllers/company-action-permission.controller.spec.js +65 -0
  4. package/cjs/controllers/my-permission.controller.spec.js +56 -0
  5. package/cjs/controllers/role-permission.controller.spec.js +140 -0
  6. package/cjs/controllers/role.controller.spec.js +86 -0
  7. package/cjs/controllers/user-action-permission.controller.js +1 -1
  8. package/cjs/controllers/user-action-permission.controller.spec.js +68 -0
  9. package/cjs/docs/iam-swagger.config.spec.js +99 -0
  10. package/cjs/dtos/permission.dto.js +7 -0
  11. package/cjs/entities/index.spec.js +56 -0
  12. package/cjs/entities/permission-base.entity.js +1 -10
  13. package/cjs/entities/permission-with-company.entity.js +0 -3
  14. package/cjs/entities/user-iam-permission.entity.js +0 -3
  15. package/cjs/helpers/company-access.helper.spec.js +68 -0
  16. package/cjs/helpers/permission-mode.helper.spec.js +54 -0
  17. package/cjs/modules/iam.module.js +2 -2
  18. package/cjs/modules/iam.module.spec.js +293 -0
  19. package/cjs/services/action.service.js +4 -4
  20. package/cjs/services/action.service.spec.js +314 -0
  21. package/cjs/services/iam-config.service.spec.js +110 -0
  22. package/cjs/services/iam-datasource.service.spec.js +117 -0
  23. package/cjs/services/permission-cache.service.js +363 -52
  24. package/cjs/services/permission-cache.service.spec.js +699 -0
  25. package/cjs/services/permission.service.js +92 -396
  26. package/cjs/services/permission.service.spec.js +692 -0
  27. package/cjs/services/role.service.js +1 -1
  28. package/cjs/services/role.service.spec.js +309 -0
  29. package/dtos/permission.dto.d.ts +1 -0
  30. package/entities/permission-base.entity.d.ts +0 -1
  31. package/fesm/controllers/action.controller.spec.js +75 -0
  32. package/fesm/controllers/company-action-permission.controller.js +1 -1
  33. package/fesm/controllers/company-action-permission.controller.spec.js +61 -0
  34. package/fesm/controllers/my-permission.controller.spec.js +52 -0
  35. package/fesm/controllers/role-permission.controller.spec.js +136 -0
  36. package/fesm/controllers/role.controller.spec.js +82 -0
  37. package/fesm/controllers/user-action-permission.controller.js +1 -1
  38. package/fesm/controllers/user-action-permission.controller.spec.js +64 -0
  39. package/fesm/docs/iam-swagger.config.spec.js +95 -0
  40. package/fesm/dtos/permission.dto.js +7 -0
  41. package/fesm/entities/index.spec.js +52 -0
  42. package/fesm/entities/permission-base.entity.js +1 -10
  43. package/fesm/entities/permission-with-company.entity.js +0 -3
  44. package/fesm/entities/user-iam-permission.entity.js +0 -3
  45. package/fesm/helpers/company-access.helper.spec.js +64 -0
  46. package/fesm/helpers/permission-mode.helper.spec.js +50 -0
  47. package/fesm/modules/iam.module.js +4 -4
  48. package/fesm/modules/iam.module.spec.js +289 -0
  49. package/fesm/services/action.service.js +4 -4
  50. package/fesm/services/action.service.spec.js +310 -0
  51. package/fesm/services/iam-config.service.spec.js +106 -0
  52. package/fesm/services/iam-datasource.service.spec.js +113 -0
  53. package/fesm/services/permission-cache.service.js +365 -51
  54. package/fesm/services/permission-cache.service.spec.js +695 -0
  55. package/fesm/services/permission.service.js +92 -396
  56. package/fesm/services/permission.service.spec.js +688 -0
  57. package/fesm/services/role.service.js +1 -1
  58. package/fesm/services/role.service.spec.js +305 -0
  59. package/package.json +3 -3
  60. package/services/permission-cache.service.d.ts +20 -3
  61. package/services/permission.service.d.ts +2 -16
@@ -35,10 +35,8 @@ import { UserIamPermissionWithCompany } from '../entities/permission-with-compan
35
35
  import { RoleWithCompany } from '../entities/role-with-company.entity';
36
36
  import { Role } from '../entities/role.entity';
37
37
  import { UserIamPermission } from '../entities/user-iam-permission.entity';
38
- import { ActionType } from '../enums/action-type.enum';
39
38
  import { IamEntityType } from '../enums/iam-entity-type.enum';
40
39
  import { IamPermissionType } from '../enums/iam-permission-type.enum';
41
- import { IAMPermissionMode } from '../enums/permission-type.enum';
42
40
  import { IAMConfigService } from './iam-config.service';
43
41
  import { IAMDataSourceService } from './iam-datasource.service';
44
42
  import { PermissionCacheService } from './permission-cache.service';
@@ -110,7 +108,6 @@ export class PermissionService {
110
108
  sourceId: dto.userId,
111
109
  targetType: IamEntityType.ACTION,
112
110
  targetId: item.id,
113
- userId: dto.userId,
114
111
  companyId: enableCompanyFeature ? companyId : null,
115
112
  branchId: enableCompanyFeature ? branchId : null
116
113
  }));
@@ -145,12 +142,12 @@ export class PermissionService {
145
142
  const result = await permissionRepo.delete(whereDelete);
146
143
  removed = result.affected || 0;
147
144
  }
148
- await this.permissionCacheService.invalidateUser(dto.userId, enableCompanyFeature ? companyId : null, [
145
+ await this.permissionCacheService.invalidateUser(dto.userId, enableCompanyFeature ? companyId : null, enableCompanyFeature ? [
149
146
  branchId
150
- ]);
147
+ ] : []);
151
148
  return this.buildOperationResult(dto.items.length, added, removed);
152
149
  }
153
- async getUserActions(userId, branchId, companyId) {
150
+ async getUserActions(userId, companyId, branchId) {
154
151
  const permissionRepo = await this.getPermissionRepository();
155
152
  const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
156
153
  const where = {
@@ -160,7 +157,7 @@ export class PermissionService {
160
157
  };
161
158
  if (enableCompanyFeature) {
162
159
  if (companyId) where.companyId = companyId;
163
- where.branchId = branchId ?? null;
160
+ if (branchId) where.branchId = branchId;
164
161
  }
165
162
  const permissions = await permissionRepo.find({
166
163
  where
@@ -174,10 +171,11 @@ export class PermissionService {
174
171
  const action = actionMap.get(p.targetId);
175
172
  return {
176
173
  id: p.id,
177
- userId: p.userId,
174
+ userId: p.sourceId,
178
175
  actionId: action.id,
179
176
  actionCode: action.code ?? '',
180
177
  actionName: action.name,
178
+ companyId: ('companyId' in p ? p.companyId : null) ?? null,
181
179
  branchId: ('branchId' in p ? p.branchId : null) ?? null,
182
180
  createdAt: p.createdAt
183
181
  };
@@ -231,7 +229,6 @@ export class PermissionService {
231
229
  sourceId: dto.roleId,
232
230
  targetType: IamEntityType.ACTION,
233
231
  targetId: item.id,
234
- userId: null,
235
232
  companyId: enableCompanyFeature ? roleCompanyId : null,
236
233
  branchId: null
237
234
  }));
@@ -261,7 +258,7 @@ export class PermissionService {
261
258
  });
262
259
  removed = result.affected || 0;
263
260
  }
264
- await this.invalidateRoleMembersCache(dto.roleId);
261
+ await this.permissionCacheService.invalidateRoleMembersCache(dto.roleId);
265
262
  return this.buildOperationResult(dto.items.length, added, removed);
266
263
  }
267
264
  async getRoleActions(roleId) {
@@ -293,94 +290,88 @@ export class PermissionService {
293
290
  // Company-Action Permissions (Whitelist)
294
291
  /** Assign or remove actions to/from a company (whitelist) */ async assignCompanyActions(dto) {
295
292
  const permissionRepo = await this.getPermissionRepository();
296
- const dataSource = permissionRepo.manager.connection;
293
+ const dataSource = await this.dataSourceProvider.getDataSource();
294
+ const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
297
295
  const { toAdd: itemsToAdd, toRemove: itemsToRemove } = this.splitItemsByAction(dto.items);
298
296
  let added = 0;
299
297
  let removed = 0;
300
298
  await dataSource.transaction(async (manager)=>{
301
299
  const transactionalPermissionRepo = manager.getRepository(permissionRepo.target);
302
300
  if (itemsToAdd.length > 0) {
303
- added = await this.addCompanyActions(transactionalPermissionRepo, dto.companyId, itemsToAdd.map((item)=>item.id));
301
+ const actionIdsToAdd = itemsToAdd.map((item)=>item.id);
302
+ const existingPermissions = await transactionalPermissionRepo.find({
303
+ where: {
304
+ permissionType: IamPermissionType.COMPANY_ACTION,
305
+ sourceType: IamEntityType.COMPANY,
306
+ sourceId: dto.companyId,
307
+ targetType: IamEntityType.ACTION,
308
+ targetId: In(actionIdsToAdd)
309
+ },
310
+ select: [
311
+ 'targetId'
312
+ ]
313
+ });
314
+ const existingActionIds = new Set(existingPermissions.map((p)=>p.targetId));
315
+ const newActionIds = actionIdsToAdd.filter((id)=>!existingActionIds.has(id));
316
+ if (newActionIds.length > 0) {
317
+ const newPermissions = newActionIds.map((actionId)=>transactionalPermissionRepo.create({
318
+ permissionType: IamPermissionType.COMPANY_ACTION,
319
+ sourceType: IamEntityType.COMPANY,
320
+ sourceId: dto.companyId,
321
+ targetType: IamEntityType.ACTION,
322
+ targetId: actionId
323
+ }));
324
+ await transactionalPermissionRepo.save(newPermissions);
325
+ added = newPermissions.length;
326
+ }
304
327
  }
305
328
  if (itemsToRemove.length > 0) {
306
329
  const actionIdsToRemove = itemsToRemove.map((item)=>item.id);
307
- removed = await this.removeCompanyActionsWithCascade(manager, dto.companyId, actionIdsToRemove);
330
+ const companyResult = await transactionalPermissionRepo.delete({
331
+ permissionType: IamPermissionType.COMPANY_ACTION,
332
+ sourceType: IamEntityType.COMPANY,
333
+ sourceId: dto.companyId,
334
+ targetType: IamEntityType.ACTION,
335
+ targetId: In(actionIdsToRemove)
336
+ });
337
+ removed = companyResult.affected || 0;
338
+ // Cascade: an action no longer on the company whitelist must not remain
339
+ // reachable via role grants or direct user grants scoped to this company.
340
+ const roleEntity = enableCompanyFeature ? RoleWithCompany : Role;
341
+ const roleRepo = manager.getRepository(roleEntity);
342
+ const companyRoles = await roleRepo.find({
343
+ where: {
344
+ companyId: dto.companyId,
345
+ deletedAt: IsNull()
346
+ },
347
+ select: [
348
+ 'id'
349
+ ]
350
+ });
351
+ if (companyRoles.length > 0) {
352
+ const roleIds = companyRoles.map((role)=>role.id);
353
+ await transactionalPermissionRepo.delete({
354
+ permissionType: IamPermissionType.ROLE_ACTION,
355
+ sourceType: IamEntityType.ROLE,
356
+ sourceId: In(roleIds),
357
+ targetType: IamEntityType.ACTION,
358
+ targetId: In(actionIdsToRemove)
359
+ });
360
+ }
361
+ if (enableCompanyFeature) {
362
+ await transactionalPermissionRepo.delete({
363
+ permissionType: IamPermissionType.USER_ACTION,
364
+ companyId: dto.companyId,
365
+ targetType: IamEntityType.ACTION,
366
+ targetId: In(actionIdsToRemove)
367
+ });
368
+ }
308
369
  }
309
370
  });
310
- await this.invalidateCompanyMembersCache(dto.companyId);
371
+ await this.permissionCacheService.invalidateCompanyMembersCache(dto.companyId);
311
372
  return this.buildOperationResult(dto.items.length, added, removed);
312
373
  }
313
- async addCompanyActions(permissionRepo, companyId, actionIds) {
314
- const existingPermissions = await permissionRepo.find({
315
- where: {
316
- permissionType: IamPermissionType.COMPANY_ACTION,
317
- sourceType: IamEntityType.COMPANY,
318
- sourceId: companyId,
319
- targetType: IamEntityType.ACTION,
320
- targetId: In(actionIds)
321
- },
322
- select: [
323
- 'targetId'
324
- ]
325
- });
326
- const existingActionIds = new Set(existingPermissions.map((p)=>p.targetId));
327
- const newActionIds = actionIds.filter((id)=>!existingActionIds.has(id));
328
- if (newActionIds.length === 0) {
329
- return 0;
330
- }
331
- const newPermissions = newActionIds.map((actionId)=>permissionRepo.create({
332
- permissionType: IamPermissionType.COMPANY_ACTION,
333
- sourceType: IamEntityType.COMPANY,
334
- sourceId: companyId,
335
- targetType: IamEntityType.ACTION,
336
- targetId: actionId,
337
- userId: null
338
- }));
339
- await permissionRepo.save(newPermissions);
340
- return newPermissions.length;
341
- }
342
- async removeCompanyActionsWithCascade(manager, companyId, actionIds) {
343
- const permissionEntity = this.iamConfigService.isCompanyFeatureEnabled() ? UserIamPermissionWithCompany : UserIamPermission;
344
- const permissionRepo = manager.getRepository(permissionEntity);
345
- const companyResult = await permissionRepo.delete({
346
- permissionType: IamPermissionType.COMPANY_ACTION,
347
- sourceType: IamEntityType.COMPANY,
348
- sourceId: companyId,
349
- targetType: IamEntityType.ACTION,
350
- targetId: In(actionIds)
351
- });
352
- const roleEntity = this.iamConfigService.isCompanyFeatureEnabled() ? RoleWithCompany : Role;
353
- const roleRepo = manager.getRepository(roleEntity);
354
- const companyRoles = await roleRepo.find({
355
- where: {
356
- companyId,
357
- deletedAt: IsNull()
358
- },
359
- select: [
360
- 'id'
361
- ]
362
- });
363
- if (companyRoles.length > 0) {
364
- const roleIds = companyRoles.map((role)=>role.id);
365
- await permissionRepo.delete({
366
- permissionType: IamPermissionType.ROLE_ACTION,
367
- sourceType: IamEntityType.ROLE,
368
- sourceId: In(roleIds),
369
- targetType: IamEntityType.ACTION,
370
- targetId: In(actionIds)
371
- });
372
- }
373
- if (this.iamConfigService.isCompanyFeatureEnabled()) {
374
- await permissionRepo.delete({
375
- permissionType: IamPermissionType.USER_ACTION,
376
- companyId,
377
- targetType: IamEntityType.ACTION,
378
- targetId: In(actionIds)
379
- });
380
- }
381
- return companyResult.affected || 0;
382
- }
383
- /** Get all actions assigned to a company (whitelist) */ async getCompanyActions(companyId) {
374
+ /** Get all actions assigned to a company (whitelist) */ async getCompanyActions(companyId, isOnlyId = false) {
384
375
  const permissionRepo = await this.getPermissionRepository();
385
376
  const permissions = await permissionRepo.find({
386
377
  where: {
@@ -393,6 +384,7 @@ export class PermissionService {
393
384
  return [];
394
385
  }
395
386
  const actionIds = permissions.map((p)=>p.targetId);
387
+ if (isOnlyId) return actionIds;
396
388
  const actionMap = await this.getActionsMap(actionIds);
397
389
  return permissions.filter((p)=>actionMap.has(p.targetId)).map((p)=>{
398
390
  const action = actionMap.get(p.targetId);
@@ -406,20 +398,6 @@ export class PermissionService {
406
398
  };
407
399
  });
408
400
  }
409
- /** Get action IDs whitelisted for a company */ async getCompanyActionIds(companyId) {
410
- const permissionRepo = await this.getPermissionRepository();
411
- const permissions = await permissionRepo.find({
412
- where: {
413
- permissionType: IamPermissionType.COMPANY_ACTION,
414
- sourceType: IamEntityType.COMPANY,
415
- sourceId: companyId
416
- },
417
- select: [
418
- 'targetId'
419
- ]
420
- });
421
- return permissions.map((p)=>p.targetId);
422
- }
423
401
  // User-Role Permissions
424
402
  /** Assign user to roles (branch-scoped when company feature is enabled) */ async assignUserRoles(dto) {
425
403
  if (!this.iamConfigService.isRbacEnabled()) {
@@ -461,7 +439,6 @@ export class PermissionService {
461
439
  sourceId: dto.userId,
462
440
  targetType: IamEntityType.ROLE,
463
441
  targetId: item.id,
464
- userId: dto.userId,
465
442
  companyId: enableCompanyFeature ? companyId : null,
466
443
  branchId: enableCompanyFeature ? branchId : null
467
444
  }));
@@ -512,7 +489,7 @@ export class PermissionService {
512
489
  };
513
490
  if (enableCompanyFeature) {
514
491
  if (companyId) where.companyId = companyId;
515
- where.branchId = branchId ?? null;
492
+ if (branchId) where.branchId = branchId;
516
493
  }
517
494
  const permissions = await permissionRepo.find({
518
495
  where
@@ -536,7 +513,7 @@ export class PermissionService {
536
513
  const permissionEntity = p;
537
514
  return {
538
515
  id: p.id,
539
- userId: p.userId,
516
+ userId: p.sourceId,
540
517
  roleId: role.id,
541
518
  roleName: role.name,
542
519
  branchId: enableCompanyFeature ? permissionEntity.branchId ?? null : null,
@@ -546,129 +523,7 @@ export class PermissionService {
546
523
  }
547
524
  // My Permissions
548
525
  /** Get user's effective permissions (cache-first approach) */ async getMyPermissions(userId, branchId, companyId, parentCodes) {
549
- const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
550
- const cacheOptions = {
551
- userId,
552
- companyId: enableCompanyFeature ? companyId : null,
553
- branchId
554
- };
555
- const cachedData = await this.permissionCacheService.getMyPermissions(cacheOptions);
556
- if (cachedData) {
557
- return this.buildResponseFromCache(cachedData, parentCodes);
558
- }
559
- const freshData = await this.fetchAndCachePermissions(userId, branchId, companyId);
560
- return this.buildResponseFromCache(freshData, parentCodes);
561
- }
562
- /** Build response from cached data, applying optional parentCodes filter */ async buildResponseFromCache(cachedData, parentCodes) {
563
- let frontendActions = cachedData.frontendActions;
564
- if (parentCodes?.length) {
565
- const parentIds = await this.getParentIdsByCodesWithCache(parentCodes);
566
- if (parentIds.size > 0) {
567
- frontendActions = frontendActions.filter((a)=>a.parentId && parentIds.has(a.parentId));
568
- } else {
569
- frontendActions = [];
570
- }
571
- }
572
- return {
573
- frontendActions: frontendActions.map((a)=>({
574
- id: a.id,
575
- code: a.code,
576
- name: a.name,
577
- description: a.description
578
- })),
579
- cachedEndpoints: cachedData.backendCodes.length
580
- };
581
- }
582
- /** Resolve action IDs for the given codes */ async getParentIdsByCodesWithCache(codes) {
583
- const actionRepo = await this.getActionRepository();
584
- const actions = await actionRepo.find({
585
- where: {
586
- code: In(codes)
587
- },
588
- select: [
589
- 'id',
590
- 'code'
591
- ]
592
- });
593
- return new Set(actions.map((action)=>action.id));
594
- }
595
- /** Fetch permissions from DB and cache them (empty permissions are also cached) */ async fetchAndCachePermissions(userId, branchId, companyId) {
596
- const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
597
- const cacheOptions = {
598
- userId,
599
- companyId: enableCompanyFeature ? companyId : null,
600
- branchId
601
- };
602
- const emptyData = {
603
- frontendActions: [],
604
- backendCodes: []
605
- };
606
- const allActionIds = await this.collectAllActionIds(userId, branchId, companyId);
607
- if (allActionIds.size === 0) {
608
- await this.permissionCacheService.setMyPermissions(cacheOptions, emptyData);
609
- return emptyData;
610
- }
611
- await this.applyCompanyWhitelist(allActionIds, companyId);
612
- if (allActionIds.size === 0) {
613
- await this.permissionCacheService.setMyPermissions(cacheOptions, emptyData);
614
- return emptyData;
615
- }
616
- return this.buildAndCachePermissionData(allActionIds, cacheOptions);
617
- }
618
- /** Collect all action IDs based on permission mode (RBAC, DIRECT, or FULL) */ async collectAllActionIds(userId, branchId, companyId) {
619
- const permissionMode = this.iamConfigService.getPermissionMode();
620
- const allActionIds = new Set();
621
- if (permissionMode === IAMPermissionMode.RBAC || permissionMode === IAMPermissionMode.FULL) {
622
- const userRoleIds = await this.getUserRoleIds(userId, branchId, companyId);
623
- if (userRoleIds.length > 0) {
624
- const roleActionIds = await this.getRoleActionIds(userRoleIds);
625
- roleActionIds.forEach((id)=>allActionIds.add(id));
626
- }
627
- }
628
- if (permissionMode === IAMPermissionMode.DIRECT || permissionMode === IAMPermissionMode.FULL) {
629
- const userActionIds = await this.getUserActionIds(userId, branchId, companyId);
630
- userActionIds.forEach((id)=>allActionIds.add(id));
631
- }
632
- return allActionIds;
633
- }
634
- /** Apply company whitelist filter to action IDs (mutates the set) */ async applyCompanyWhitelist(actionIds, companyId) {
635
- if (!this.iamConfigService.isCompanyFeatureEnabled() || !companyId) {
636
- return;
637
- }
638
- const companyActionIds = await this.getCompanyActionIds(companyId);
639
- if (companyActionIds.length === 0) {
640
- return;
641
- }
642
- const allowedActionIds = new Set(companyActionIds);
643
- for (const actionId of actionIds){
644
- if (!allowedActionIds.has(actionId)) {
645
- actionIds.delete(actionId);
646
- }
647
- }
648
- }
649
- /** Fetch actions from DB, separate by type, build cache data, and cache it */ async buildAndCachePermissionData(actionIds, cacheOptions) {
650
- const actionRepo = await this.getActionRepository();
651
- const actions = await actionRepo.find({
652
- where: {
653
- id: In(Array.from(actionIds)),
654
- isActive: true
655
- }
656
- });
657
- const backendActions = actions.filter((a)=>a.actionType === ActionType.BACKEND || a.actionType === ActionType.BOTH);
658
- const frontendActions = actions.filter((a)=>a.actionType === ActionType.FRONTEND || a.actionType === ActionType.BOTH);
659
- const backendCodes = backendActions.map((a)=>a.code).filter((c)=>!!c);
660
- const cacheData = {
661
- frontendActions: frontendActions.map((a)=>({
662
- id: a.id,
663
- code: a.code ?? '',
664
- name: a.name,
665
- description: a.description,
666
- parentId: a.parentId
667
- })),
668
- backendCodes
669
- };
670
- await this.permissionCacheService.setMyPermissions(cacheOptions, cacheData);
671
- return cacheData;
526
+ return this.permissionCacheService.getMyPermissionsResponse(userId, branchId, companyId, parentCodes);
672
527
  }
673
528
  // Helper Methods
674
529
  /** Split permission items into add and remove arrays */ splitItemsByAction(items) {
@@ -684,176 +539,13 @@ export class PermissionService {
684
539
  total: _totalItems
685
540
  };
686
541
  }
687
- /** Get role IDs assigned to a user (merges company-wide + branch-specific roles) */ async getUserRoleIds(userId, branchId, companyId) {
688
- const permissionRepo = await this.getPermissionRepository();
689
- const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
690
- if (!enableCompanyFeature) {
691
- const permissions = await permissionRepo.find({
692
- where: {
693
- permissionType: IamPermissionType.USER_ROLE,
694
- sourceType: IamEntityType.USER,
695
- sourceId: userId
696
- }
697
- });
698
- return permissions.filter((p)=>p.isValid()).map((p)=>p.targetId);
699
- }
700
- const roleIds = new Set();
701
- const baseWhere = {
702
- permissionType: IamPermissionType.USER_ROLE,
703
- sourceType: IamEntityType.USER,
704
- sourceId: userId
705
- };
706
- if (companyId) {
707
- baseWhere.companyId = companyId;
708
- }
709
- if (branchId) {
710
- // Get company-wide + branch-specific roles
711
- const companyWidePermissions = await permissionRepo.find({
712
- where: {
713
- ...baseWhere,
714
- branchId: IsNull()
715
- }
716
- });
717
- companyWidePermissions.filter((p)=>p.isValid()).forEach((p)=>roleIds.add(p.targetId));
718
- const branchPermissions = await permissionRepo.find({
719
- where: {
720
- ...baseWhere,
721
- branchId
722
- }
723
- });
724
- branchPermissions.filter((p)=>p.isValid()).forEach((p)=>roleIds.add(p.targetId));
725
- } else {
726
- // No branch context: get all roles for this company
727
- const allPermissions = await permissionRepo.find({
728
- where: baseWhere
729
- });
730
- allPermissions.filter((p)=>p.isValid()).forEach((p)=>roleIds.add(p.targetId));
731
- }
732
- return Array.from(roleIds);
733
- }
734
- async getRoleActionIds(roleIds) {
735
- const permissionRepo = await this.getPermissionRepository();
736
- const permissions = await permissionRepo.find({
737
- where: {
738
- permissionType: IamPermissionType.ROLE_ACTION,
739
- sourceType: IamEntityType.ROLE,
740
- sourceId: In(roleIds)
741
- }
742
- });
743
- return permissions.filter((p)=>p.isValid()).map((p)=>p.targetId);
744
- }
745
- /** Get action IDs directly assigned to a user (merges company-wide + branch-specific actions) */ async getUserActionIds(userId, branchId, companyId) {
746
- const permissionRepo = await this.getPermissionRepository();
747
- const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
748
- if (!enableCompanyFeature) {
749
- const permissions = await permissionRepo.find({
750
- where: {
751
- permissionType: IamPermissionType.USER_ACTION,
752
- sourceType: IamEntityType.USER,
753
- sourceId: userId
754
- }
755
- });
756
- return permissions.filter((p)=>p.isValid()).map((p)=>p.targetId);
757
- }
758
- const actionIds = new Set();
759
- const baseWhere = {
760
- permissionType: IamPermissionType.USER_ACTION,
761
- sourceType: IamEntityType.USER,
762
- sourceId: userId
763
- };
764
- if (companyId) {
765
- baseWhere.companyId = companyId;
766
- }
767
- if (branchId) {
768
- // Get company-wide + branch-specific actions
769
- const companyWidePermissions = await permissionRepo.find({
770
- where: {
771
- ...baseWhere,
772
- branchId: IsNull()
773
- }
774
- });
775
- companyWidePermissions.filter((p)=>p.isValid()).forEach((p)=>actionIds.add(p.targetId));
776
- const branchPermissions = await permissionRepo.find({
777
- where: {
778
- ...baseWhere,
779
- branchId
780
- }
781
- });
782
- branchPermissions.filter((p)=>p.isValid()).forEach((p)=>actionIds.add(p.targetId));
783
- } else {
784
- // No branch context: get all actions for this company
785
- const allPermissions = await permissionRepo.find({
786
- where: baseWhere
787
- });
788
- allPermissions.filter((p)=>p.isValid()).forEach((p)=>actionIds.add(p.targetId));
789
- }
790
- return Array.from(actionIds);
791
- }
792
- async invalidateRoleMembersCache(roleId) {
793
- const permissionRepo = await this.getPermissionRepository();
794
- const roleRepo = await this.getRoleRepository();
795
- const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
796
- const userRoles = await permissionRepo.find({
797
- where: {
798
- permissionType: IamPermissionType.USER_ROLE,
799
- sourceType: IamEntityType.USER,
800
- targetType: IamEntityType.ROLE,
801
- targetId: roleId
802
- }
803
- });
804
- const userIds = [
805
- ...new Set(userRoles.map((ur)=>ur.sourceId))
806
- ];
807
- if (userIds.length === 0) {
808
- return 0;
809
- }
810
- const role = await roleRepo.findOne({
811
- where: {
812
- id: roleId
813
- }
814
- });
815
- const companyId = role?.companyId || null;
816
- let branchIds = [
817
- null
818
- ];
819
- if (enableCompanyFeature && companyId) {
820
- const userBranches = await permissionRepo.createQueryBuilder('p').select('DISTINCT p.branch_id', 'branchId').where('p.user_id IN (:...userIds)', {
821
- userIds
822
- }).andWhere('p.company_id = :companyId', {
823
- companyId
824
- }).getRawMany();
825
- branchIds = [
826
- ...new Set(userBranches.map((p)=>p.branchId))
827
- ];
828
- }
829
- return this.permissionCacheService.invalidateUsers(userIds, companyId, branchIds);
830
- }
831
- /** Invalidate permission cache for all users in a company */ async invalidateCompanyMembersCache(companyId) {
832
- if (!this.iamConfigService.isCompanyFeatureEnabled()) {
833
- return 0;
834
- }
835
- const permissionRepo = await this.getPermissionRepository();
836
- const userPermissions = await permissionRepo.createQueryBuilder('p').select('DISTINCT p.user_id', 'userId').addSelect('p.branch_id', 'branchId').where('p.company_id = :companyId', {
837
- companyId
838
- }).andWhere('p.user_id IS NOT NULL').getRawMany();
839
- const userIds = [
840
- ...new Set(userPermissions.map((p)=>p.userId).filter(Boolean))
841
- ];
842
- const branchIds = [
843
- ...new Set(userPermissions.map((p)=>p.branchId))
844
- ]; // includes null
845
- if (userIds.length === 0) {
846
- return 0;
847
- }
848
- return await this.permissionCacheService.invalidateUsers(userIds, companyId, branchIds);
849
- }
850
542
  async revokeCompanyPermissions(companyId) {
851
543
  if (!this.iamConfigService.isCompanyFeatureEnabled()) {
852
544
  return;
853
545
  }
854
546
  const permissionRepo = await this.getPermissionRepository();
855
547
  const roleRepo = await this.getRoleRepository();
856
- const dataSource = permissionRepo.manager.connection;
548
+ const dataSource = await this.dataSourceProvider.getDataSource();
857
549
  const companyRoles = await roleRepo.find({
858
550
  where: {
859
551
  companyId,
@@ -870,14 +562,15 @@ export class PermissionService {
870
562
  IamPermissionType.USER_ROLE,
871
563
  IamPermissionType.USER_ACTION
872
564
  ]),
565
+ sourceType: IamEntityType.USER,
873
566
  companyId
874
567
  },
875
568
  select: [
876
- 'userId'
569
+ 'sourceId'
877
570
  ]
878
571
  });
879
572
  const affectedUserIds = [
880
- ...new Set(affectedUserRows.map((row)=>row.userId).filter((id)=>!!id))
573
+ ...new Set(affectedUserRows.map((row)=>row.sourceId))
881
574
  ];
882
575
  await dataSource.transaction(async (manager)=>{
883
576
  const txRepo = manager.getRepository(permissionRepo.target);
@@ -919,17 +612,20 @@ export class PermissionService {
919
612
  IamPermissionType.USER_ROLE,
920
613
  IamPermissionType.USER_ACTION
921
614
  ]),
922
- branchId,
923
- companyId
615
+ companyId,
616
+ branchId
924
617
  };
925
618
  const affectedUserRows = await permissionRepo.find({
926
- where,
619
+ where: {
620
+ ...where,
621
+ sourceType: IamEntityType.USER
622
+ },
927
623
  select: [
928
- 'userId'
624
+ 'sourceId'
929
625
  ]
930
626
  });
931
627
  const affectedUserIds = [
932
- ...new Set(affectedUserRows.map((row)=>row.userId).filter((id)=>!!id))
628
+ ...new Set(affectedUserRows.map((row)=>row.sourceId))
933
629
  ];
934
630
  await permissionRepo.delete(where);
935
631
  if (affectedUserIds.length > 0) {