@flusys/nestjs-iam 6.4.1 → 7.0.0-beta.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.
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Domain event actions published by the IAM module, alongside the CRUD
3
+ * lifecycle actions every ApiService emits.
4
+ */ "use strict";
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ function _export(target, all) {
9
+ for(var name in all)Object.defineProperty(target, name, {
10
+ enumerable: true,
11
+ get: Object.getOwnPropertyDescriptor(all, name).get
12
+ });
13
+ }
14
+ _export(exports, {
15
+ get IAM_EVENT_ACTIONS () {
16
+ return IAM_EVENT_ACTIONS;
17
+ },
18
+ get IAM_EVENT_ENTITIES () {
19
+ return IAM_EVENT_ENTITIES;
20
+ },
21
+ get IAM_EVENT_MODULE () {
22
+ return IAM_EVENT_MODULE;
23
+ }
24
+ });
25
+ const IAM_EVENT_ACTIONS = {
26
+ PERMISSIONS_ASSIGNED: 'permissions-assigned'
27
+ };
28
+ const IAM_EVENT_ENTITIES = {
29
+ ACTION: 'action',
30
+ ROLE: 'role',
31
+ USER_ACTION: 'user_action',
32
+ ROLE_ACTION: 'role_action',
33
+ COMPANY_ACTION: 'company_action',
34
+ USER_ROLE: 'user_role'
35
+ };
36
+ const IAM_EVENT_MODULE = 'iam';
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
4
  });
5
+ _export_star(require("./event-actions"), exports);
5
6
  _export_star(require("./iam.constants"), exports);
6
7
  _export_star(require("./message-keys"), exports);
7
8
  function _export_star(from, to) {
@@ -25,6 +25,9 @@ _export(exports, {
25
25
  get PERMISSION_OPERATION_MESSAGES () {
26
26
  return PERMISSION_OPERATION_MESSAGES;
27
27
  },
28
+ get ROLE_MESSAGES () {
29
+ return ROLE_MESSAGES;
30
+ },
28
31
  get ROLE_PERMISSION_MESSAGES () {
29
32
  return ROLE_PERMISSION_MESSAGES;
30
33
  },
@@ -33,7 +36,13 @@ _export(exports, {
33
36
  }
34
37
  });
35
38
  const ACTION_MESSAGES = {
36
- GET_ALL_SUCCESS: 'action.get.all.success'
39
+ GET_ALL_SUCCESS: 'action.get.all.success',
40
+ READONLY_UPDATE_FORBIDDEN: 'action.readonly.update.forbidden',
41
+ READONLY_DELETE_FORBIDDEN: 'action.readonly.delete.forbidden'
42
+ };
43
+ const ROLE_MESSAGES = {
44
+ READONLY_UPDATE_FORBIDDEN: 'role.readonly.update.forbidden',
45
+ READONLY_DELETE_FORBIDDEN: 'role.readonly.delete.forbidden'
37
46
  };
38
47
  const PERMISSION_OPERATION_MESSAGES = {
39
48
  PROCESS_SUCCESS: 'permission.process.success',
@@ -101,7 +101,8 @@ let IAMModule = class IAMModule {
101
101
  _iamconfigservice.IAMConfigService,
102
102
  _iamdatasourceservice.IAMDataSourceService,
103
103
  ...this.getServices(permissionMode),
104
- this.getPermissionGuardConfigProvider(enableCompanyFeature)
104
+ this.getPermissionGuardConfigProvider(enableCompanyFeature),
105
+ (0, _modules.createModuleEventsProvider)('iam', _iamconstants.IAM_MODULE_OPTIONS)
105
106
  ];
106
107
  const module = {
107
108
  module: IAMModule,
@@ -128,7 +129,8 @@ let IAMModule = class IAMModule {
128
129
  _iamconfigservice.IAMConfigService,
129
130
  _iamdatasourceservice.IAMDataSourceService,
130
131
  ...this.getServices(permissionMode),
131
- this.getPermissionGuardConfigProvider(enableCompanyFeature)
132
+ this.getPermissionGuardConfigProvider(enableCompanyFeature),
133
+ (0, _modules.createModuleEventsProvider)('iam', _iamconstants.IAM_MODULE_OPTIONS)
132
134
  ];
133
135
  const module = {
134
136
  module: IAMModule,
@@ -50,6 +50,30 @@ function _ts_param(paramIndex, decorator) {
50
50
  };
51
51
  }
52
52
  let ActionService = class ActionService extends _classes.ApiService {
53
+ async beforeUpdateOperation(dto, _user, _queryRunner) {
54
+ const dtos = Array.isArray(dto) ? dto : [
55
+ dto
56
+ ];
57
+ const ids = dtos.map((d)=>d.id);
58
+ await this.ensureDataSourceRepository();
59
+ const readonlyActions = await this.repository.find({
60
+ where: {
61
+ id: (0, _typeorm.In)(ids),
62
+ readOnly: true,
63
+ deletedAt: (0, _typeorm.IsNull)()
64
+ }
65
+ });
66
+ if (readonlyActions.length > 0) {
67
+ const names = readonlyActions.map((a)=>a.name).join(', ');
68
+ throw new _common.ForbiddenException({
69
+ message: `Cannot modify readonly action(s): ${names}`,
70
+ messageKey: _config.ACTION_MESSAGES.READONLY_UPDATE_FORBIDDEN,
71
+ messageVariables: {
72
+ names
73
+ }
74
+ });
75
+ }
76
+ }
53
77
  async beforeDeleteOperation(dto, user, queryRunner) {
54
78
  const actionIds = Array.isArray(dto.id) ? dto.id : [
55
79
  dto.id
@@ -57,6 +81,24 @@ let ActionService = class ActionService extends _classes.ApiService {
57
81
  if (actionIds.length === 0) {
58
82
  return;
59
83
  }
84
+ await this.ensureDataSourceRepository();
85
+ const readonlyActions = await this.repository.find({
86
+ where: {
87
+ id: (0, _typeorm.In)(actionIds),
88
+ readOnly: true,
89
+ deletedAt: (0, _typeorm.IsNull)()
90
+ }
91
+ });
92
+ if (readonlyActions.length > 0) {
93
+ const names = readonlyActions.map((a)=>a.name).join(', ');
94
+ throw new _common.ForbiddenException({
95
+ message: `Cannot delete readonly action(s): ${names}`,
96
+ messageKey: _config.ACTION_MESSAGES.READONLY_DELETE_FORBIDDEN,
97
+ messageVariables: {
98
+ names
99
+ }
100
+ });
101
+ }
60
102
  const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
61
103
  const permissionEntity = enableCompanyFeature ? _permissionwithcompanyentity.UserIamPermissionWithCompany : _useriampermissionentity.UserIamPermission;
62
104
  const permissionRepo = queryRunner.manager.getRepository(permissionEntity);
@@ -136,6 +178,7 @@ let ActionService = class ActionService extends _classes.ApiService {
136
178
  'actionType',
137
179
  'permissionLogic',
138
180
  'isActive',
181
+ 'readOnly',
139
182
  'parentId',
140
183
  'serial',
141
184
  'createdAt'
@@ -41,6 +41,7 @@ describe('ActionService', ()=>{
41
41
  let mockPermissionCacheService;
42
42
  beforeEach(async ()=>{
43
43
  mockRepo = (0, _repositorymock.createMockRepository)();
44
+ mockRepo.find.mockResolvedValue([]);
44
45
  const mockDataSourceProvider = (0, _repositorymock.createMockDataSourceProvider)(mockRepo);
45
46
  mockIamConfigService = {
46
47
  isCompanyFeatureEnabled: jest.fn().mockReturnValue(false)
@@ -310,5 +311,41 @@ describe('ActionService', ()=>{
310
311
  }, (0, _loggedusermock.buildMockUser)(), queryRunner);
311
312
  expect(mockPermissionCacheService.invalidateUsers).not.toHaveBeenCalled();
312
313
  });
314
+ it('rejects deleting a readonly action', async ()=>{
315
+ mockRepo.find.mockResolvedValue([
316
+ buildAction({
317
+ readOnly: true
318
+ })
319
+ ]);
320
+ const permissionRepo = (0, _repositorymock.createMockRepository)();
321
+ const queryRunner = buildQueryRunner(permissionRepo);
322
+ await expect(service.beforeDeleteOperation({
323
+ id: 'action-uuid-1',
324
+ type: 'delete'
325
+ }, (0, _loggedusermock.buildMockUser)(), queryRunner)).rejects.toThrow('Cannot delete readonly action');
326
+ expect(permissionRepo.find).not.toHaveBeenCalled();
327
+ });
328
+ });
329
+ describe('beforeUpdateOperation', ()=>{
330
+ it('rejects updating a readonly action', async ()=>{
331
+ mockRepo.find.mockResolvedValue([
332
+ buildAction({
333
+ readOnly: true
334
+ })
335
+ ]);
336
+ await expect(service.beforeUpdateOperation({
337
+ id: 'action-uuid-1',
338
+ name: 'Renamed'
339
+ }, (0, _loggedusermock.buildMockUser)(), {})).rejects.toThrow('Cannot modify readonly action');
340
+ });
341
+ it('allows updating a non-readonly action', async ()=>{
342
+ // The service filters readOnly: true in the query itself, so a non-readonly
343
+ // action never comes back — simulate that by resolving an empty array.
344
+ mockRepo.find.mockResolvedValue([]);
345
+ await expect(service.beforeUpdateOperation({
346
+ id: 'action-uuid-1',
347
+ name: 'Renamed'
348
+ }, (0, _loggedusermock.buildMockUser)(), {})).resolves.toBeUndefined();
349
+ });
313
350
  });
314
351
  });
@@ -9,6 +9,7 @@ Object.defineProperty(exports, "PermissionService", {
9
9
  }
10
10
  });
11
11
  const _nestjsshared = require("@flusys/nestjs-shared");
12
+ const _classes = require("@flusys/nestjs-shared/classes");
12
13
  const _common = require("@nestjs/common");
13
14
  const _typeorm = require("typeorm");
14
15
  const _config = require("../config");
@@ -155,7 +156,9 @@ let PermissionService = class PermissionService {
155
156
  await this.permissionCacheService.invalidateUser(dto.userId, enableCompanyFeature ? companyId : null, enableCompanyFeature ? [
156
157
  branchId
157
158
  ] : []);
158
- return this.buildOperationResult(dto.items.length, added, removed);
159
+ const result = this.buildOperationResult(dto.items.length, added, removed);
160
+ await this.publishAssignmentEvent(_config.IAM_EVENT_ENTITIES.USER_ACTION, dto.userId, result);
161
+ return result;
159
162
  }
160
163
  async getUserActions(userId, companyId, branchId) {
161
164
  const permissionRepo = await this.getPermissionRepository();
@@ -269,7 +272,9 @@ let PermissionService = class PermissionService {
269
272
  removed = result.affected || 0;
270
273
  }
271
274
  await this.permissionCacheService.invalidateRoleMembersCache(dto.roleId);
272
- return this.buildOperationResult(dto.items.length, added, removed);
275
+ const result = this.buildOperationResult(dto.items.length, added, removed);
276
+ await this.publishAssignmentEvent(_config.IAM_EVENT_ENTITIES.ROLE_ACTION, dto.roleId, result);
277
+ return result;
273
278
  }
274
279
  async getRoleActions(roleId) {
275
280
  const permissionRepo = await this.getPermissionRepository();
@@ -379,7 +384,9 @@ let PermissionService = class PermissionService {
379
384
  }
380
385
  });
381
386
  await this.permissionCacheService.invalidateCompanyMembersCache(dto.companyId);
382
- return this.buildOperationResult(dto.items.length, added, removed);
387
+ const result = this.buildOperationResult(dto.items.length, added, removed);
388
+ await this.publishAssignmentEvent(_config.IAM_EVENT_ENTITIES.COMPANY_ACTION, dto.companyId, result);
389
+ return result;
383
390
  }
384
391
  /** Get all actions assigned to a company (whitelist) */ async getCompanyActions(companyId, isOnlyId = false) {
385
392
  const permissionRepo = await this.getPermissionRepository();
@@ -486,7 +493,9 @@ let PermissionService = class PermissionService {
486
493
  await this.permissionCacheService.invalidateUser(dto.userId, enableCompanyFeature ? companyId : null, [
487
494
  branchId
488
495
  ]);
489
- return this.buildOperationResult(dto.items.length, added, removed);
496
+ const result = this.buildOperationResult(dto.items.length, added, removed);
497
+ await this.publishAssignmentEvent(_config.IAM_EVENT_ENTITIES.USER_ROLE, dto.userId, result);
498
+ return result;
490
499
  }
491
500
  /** Get user's roles (branch-scoped, filtered by companyId and branchId if provided) */ async getUserRoles(userId, branchId, companyId) {
492
501
  const permissionRepo = await this.getPermissionRepository();
@@ -640,6 +649,21 @@ let PermissionService = class PermissionService {
640
649
  toRemove: items.filter((item)=>item.action === _permissiondto.PermissionAction.REMOVE)
641
650
  };
642
651
  }
652
+ async publishAssignmentEvent(entity, targetId, result) {
653
+ await (0, _classes.publishDomainEvent)({
654
+ module: _config.IAM_EVENT_MODULE,
655
+ entity,
656
+ action: _config.IAM_EVENT_ACTIONS.PERMISSIONS_ASSIGNED,
657
+ ids: [
658
+ targetId
659
+ ],
660
+ metadata: {
661
+ added: result.added,
662
+ removed: result.removed,
663
+ total: result.total
664
+ }
665
+ });
666
+ }
643
667
  /** Build standard operation result DTO */ buildOperationResult(_totalItems, added, removed) {
644
668
  return {
645
669
  added,
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
4
  });
5
5
  const _testing = require("@nestjs/testing");
6
+ const _classes = require("@flusys/nestjs-shared/classes");
6
7
  const _repositorymock = require("@test-utils/mocks/repository.mock");
7
8
  const _config = require("../config");
8
9
  const _permissiondto = require("../dtos/permission.dto");
@@ -834,4 +835,84 @@ describe('PermissionService', ()=>{
834
835
  ]);
835
836
  });
836
837
  });
838
+ describe('domain events', ()=>{
839
+ let publish;
840
+ beforeEach(()=>{
841
+ publish = jest.fn().mockResolvedValue(undefined);
842
+ _classes.EventBusRegistry.reset();
843
+ _classes.EventBusRegistry.setBus({
844
+ publish,
845
+ subscribe: jest.fn(),
846
+ close: jest.fn()
847
+ });
848
+ _classes.EventBusRegistry.configureModule('iam', {
849
+ enabled: true
850
+ });
851
+ mockIamConfigService.isDirectPermissionEnabled.mockReturnValue(true);
852
+ mockPermissionRepo.find.mockResolvedValue([]);
853
+ mockPermissionRepo.save.mockResolvedValue([
854
+ {}
855
+ ]);
856
+ mockPermissionRepo.delete.mockResolvedValue(buildDeleteResult(1));
857
+ });
858
+ afterEach(()=>{
859
+ _classes.EventBusRegistry.reset();
860
+ });
861
+ it('publishes iam.user_action.permissions-assigned with the operation counts', async ()=>{
862
+ await service.assignUserActions({
863
+ userId: 'user-1',
864
+ items: [
865
+ {
866
+ id: 'action-1',
867
+ action: _permissiondto.PermissionAction.ADD
868
+ }
869
+ ]
870
+ });
871
+ expect(publish).toHaveBeenCalledWith(expect.objectContaining({
872
+ name: 'iam.user_action.permissions-assigned',
873
+ payload: expect.objectContaining({
874
+ ids: [
875
+ 'user-1'
876
+ ],
877
+ metadata: expect.objectContaining({
878
+ total: 1
879
+ })
880
+ })
881
+ }));
882
+ });
883
+ it('publishes iam.user_role.permissions-assigned when roles are assigned', async ()=>{
884
+ mockRoleRepo.find.mockResolvedValue([
885
+ {
886
+ id: 'role-1'
887
+ }
888
+ ]);
889
+ await service.assignUserRoles({
890
+ userId: 'user-1',
891
+ items: [
892
+ {
893
+ id: 'role-1',
894
+ action: _permissiondto.PermissionAction.ADD
895
+ }
896
+ ]
897
+ });
898
+ expect(publish).toHaveBeenCalledWith(expect.objectContaining({
899
+ name: 'iam.user_role.permissions-assigned'
900
+ }));
901
+ });
902
+ it('does not publish when the iam module has events disabled', async ()=>{
903
+ _classes.EventBusRegistry.configureModule('iam', {
904
+ enabled: false
905
+ });
906
+ await service.assignUserActions({
907
+ userId: 'user-1',
908
+ items: [
909
+ {
910
+ id: 'action-1',
911
+ action: _permissiondto.PermissionAction.ADD
912
+ }
913
+ ]
914
+ });
915
+ expect(publish).not.toHaveBeenCalled();
916
+ });
917
+ });
837
918
  });
@@ -13,6 +13,7 @@ const _modules = require("@flusys/nestjs-shared/modules");
13
13
  const _utils = require("@flusys/nestjs-shared/utils");
14
14
  const _common = require("@nestjs/common");
15
15
  const _typeorm = require("typeorm");
16
+ const _config = require("../config");
16
17
  const _permissionwithcompanyentity = require("../entities/permission-with-company.entity");
17
18
  const _rolewithcompanyentity = require("../entities/role-with-company.entity");
18
19
  const _roleentity = require("../entities/role.entity");
@@ -50,6 +51,30 @@ function _ts_param(paramIndex, decorator) {
50
51
  };
51
52
  }
52
53
  let RoleService = class RoleService extends _classes.ApiService {
54
+ async beforeUpdateOperation(dto, _user, _queryRunner) {
55
+ const dtos = Array.isArray(dto) ? dto : [
56
+ dto
57
+ ];
58
+ const ids = dtos.map((d)=>d.id);
59
+ await this.ensureDataSourceRepository();
60
+ const readonlyRoles = await this.repository.find({
61
+ where: {
62
+ id: (0, _typeorm.In)(ids),
63
+ readOnly: true,
64
+ deletedAt: (0, _typeorm.IsNull)()
65
+ }
66
+ });
67
+ if (readonlyRoles.length > 0) {
68
+ const names = readonlyRoles.map((r)=>r.name).join(', ');
69
+ throw new _common.ForbiddenException({
70
+ message: `Cannot modify readonly role(s): ${names}`,
71
+ messageKey: _config.ROLE_MESSAGES.READONLY_UPDATE_FORBIDDEN,
72
+ messageVariables: {
73
+ names
74
+ }
75
+ });
76
+ }
77
+ }
53
78
  async beforeDeleteOperation(dto, user, queryRunner) {
54
79
  await super.beforeDeleteOperation(dto, user, queryRunner);
55
80
  const roleIds = Array.isArray(dto.id) ? dto.id : [
@@ -58,6 +83,24 @@ let RoleService = class RoleService extends _classes.ApiService {
58
83
  if (roleIds.length === 0) {
59
84
  return;
60
85
  }
86
+ await this.ensureDataSourceRepository();
87
+ const readonlyRoles = await this.repository.find({
88
+ where: {
89
+ id: (0, _typeorm.In)(roleIds),
90
+ readOnly: true,
91
+ deletedAt: (0, _typeorm.IsNull)()
92
+ }
93
+ });
94
+ if (readonlyRoles.length > 0) {
95
+ const names = readonlyRoles.map((r)=>r.name).join(', ');
96
+ throw new _common.ForbiddenException({
97
+ message: `Cannot delete readonly role(s): ${names}`,
98
+ messageKey: _config.ROLE_MESSAGES.READONLY_DELETE_FORBIDDEN,
99
+ messageVariables: {
100
+ names
101
+ }
102
+ });
103
+ }
61
104
  const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
62
105
  const permissionEntity = enableCompanyFeature ? _permissionwithcompanyentity.UserIamPermissionWithCompany : _useriampermissionentity.UserIamPermission;
63
106
  const permissionRepo = queryRunner.manager.getRepository(permissionEntity);
@@ -111,6 +154,7 @@ let RoleService = class RoleService extends _classes.ApiService {
111
154
  'name',
112
155
  'description',
113
156
  'isActive',
157
+ 'readOnly',
114
158
  'serial',
115
159
  'createdAt'
116
160
  ];
@@ -36,6 +36,7 @@ describe('RoleService', ()=>{
36
36
  let mockPermissionCacheService;
37
37
  beforeEach(async ()=>{
38
38
  mockRepo = (0, _repositorymock.createMockRepository)();
39
+ mockRepo.find.mockResolvedValue([]);
39
40
  const mockDataSourceProvider = (0, _repositorymock.createMockDataSourceProvider)(mockRepo);
40
41
  mockIamConfigService = {
41
42
  isCompanyFeatureEnabled: jest.fn().mockReturnValue(false)
@@ -305,5 +306,39 @@ describe('RoleService', ()=>{
305
306
  'user-1'
306
307
  ]);
307
308
  });
309
+ it('rejects deleting a readonly role', async ()=>{
310
+ mockRepo.find.mockResolvedValue([
311
+ buildRole({
312
+ readOnly: true
313
+ })
314
+ ]);
315
+ const permissionRepo = (0, _repositorymock.createMockRepository)();
316
+ const queryRunner = buildQueryRunner(permissionRepo);
317
+ await expect(service.beforeDeleteOperation({
318
+ id: 'role-uuid-1',
319
+ type: 'delete'
320
+ }, (0, _loggedusermock.buildMockUser)(), queryRunner)).rejects.toThrow('Cannot delete readonly role');
321
+ expect(permissionRepo.find).not.toHaveBeenCalled();
322
+ });
323
+ });
324
+ describe('beforeUpdateOperation', ()=>{
325
+ it('rejects updating a readonly role', async ()=>{
326
+ mockRepo.find.mockResolvedValue([
327
+ buildRole({
328
+ readOnly: true
329
+ })
330
+ ]);
331
+ await expect(service.beforeUpdateOperation({
332
+ id: 'role-uuid-1',
333
+ name: 'Renamed'
334
+ }, (0, _loggedusermock.buildMockUser)(), {})).rejects.toThrow('Cannot modify readonly role');
335
+ });
336
+ it('allows updating a non-readonly role', async ()=>{
337
+ mockRepo.find.mockResolvedValue([]);
338
+ await expect(service.beforeUpdateOperation({
339
+ id: 'role-uuid-1',
340
+ name: 'Renamed'
341
+ }, (0, _loggedusermock.buildMockUser)(), {})).resolves.toBeUndefined();
342
+ });
308
343
  });
309
344
  });
@@ -0,0 +1,12 @@
1
+ export declare const IAM_EVENT_ACTIONS: {
2
+ readonly PERMISSIONS_ASSIGNED: "permissions-assigned";
3
+ };
4
+ export declare const IAM_EVENT_ENTITIES: {
5
+ readonly ACTION: "action";
6
+ readonly ROLE: "role";
7
+ readonly USER_ACTION: "user_action";
8
+ readonly ROLE_ACTION: "role_action";
9
+ readonly COMPANY_ACTION: "company_action";
10
+ readonly USER_ROLE: "user_role";
11
+ };
12
+ export declare const IAM_EVENT_MODULE = "iam";
package/config/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
+ export * from './event-actions';
1
2
  export * from './iam.constants';
2
3
  export * from './message-keys';
@@ -1,5 +1,11 @@
1
1
  export declare const ACTION_MESSAGES: {
2
2
  readonly GET_ALL_SUCCESS: "action.get.all.success";
3
+ readonly READONLY_UPDATE_FORBIDDEN: "action.readonly.update.forbidden";
4
+ readonly READONLY_DELETE_FORBIDDEN: "action.readonly.delete.forbidden";
5
+ };
6
+ export declare const ROLE_MESSAGES: {
7
+ readonly READONLY_UPDATE_FORBIDDEN: "role.readonly.update.forbidden";
8
+ readonly READONLY_DELETE_FORBIDDEN: "role.readonly.delete.forbidden";
3
9
  };
4
10
  export declare const PERMISSION_OPERATION_MESSAGES: {
5
11
  readonly PROCESS_SUCCESS: "permission.process.success";
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Domain event actions published by the IAM module, alongside the CRUD
3
+ * lifecycle actions every ApiService emits.
4
+ */ export const IAM_EVENT_ACTIONS = {
5
+ PERMISSIONS_ASSIGNED: 'permissions-assigned'
6
+ };
7
+ /** Every entity the IAM module publishes under, ApiService backed or not */ export const IAM_EVENT_ENTITIES = {
8
+ ACTION: 'action',
9
+ ROLE: 'role',
10
+ USER_ACTION: 'user_action',
11
+ ROLE_ACTION: 'role_action',
12
+ COMPANY_ACTION: 'company_action',
13
+ USER_ROLE: 'user_role'
14
+ };
15
+ export const IAM_EVENT_MODULE = 'iam';
@@ -1,2 +1,3 @@
1
+ export * from './event-actions';
1
2
  export * from './iam.constants';
2
3
  export * from './message-keys';
@@ -1,6 +1,12 @@
1
1
  // ==================== IAM MODULE MESSAGE KEYS ====================
2
2
  export const ACTION_MESSAGES = {
3
- GET_ALL_SUCCESS: 'action.get.all.success'
3
+ GET_ALL_SUCCESS: 'action.get.all.success',
4
+ READONLY_UPDATE_FORBIDDEN: 'action.readonly.update.forbidden',
5
+ READONLY_DELETE_FORBIDDEN: 'action.readonly.delete.forbidden'
6
+ };
7
+ export const ROLE_MESSAGES = {
8
+ READONLY_UPDATE_FORBIDDEN: 'role.readonly.update.forbidden',
9
+ READONLY_DELETE_FORBIDDEN: 'role.readonly.delete.forbidden'
4
10
  };
5
11
  export const PERMISSION_OPERATION_MESSAGES = {
6
12
  PROCESS_SUCCESS: 'permission.process.success',
@@ -5,7 +5,7 @@ function _ts_decorate(decorators, target, key, desc) {
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  }
7
7
  import { PERMISSION_GUARD_CONFIG, PERMISSIONS_CACHE_PREFIX } from '@flusys/nestjs-shared';
8
- import { CacheModule, UtilsModule } from '@flusys/nestjs-shared/modules';
8
+ import { CacheModule, UtilsModule, createModuleEventsProvider } from '@flusys/nestjs-shared/modules';
9
9
  import { Module } from '@nestjs/common';
10
10
  import { IAM_MODULE_OPTIONS } from '../config/iam.constants';
11
11
  import { ActionController, CompanyActionPermissionController, MyPermissionController, RoleController, RolePermissionController, UserActionPermissionController } from '../controllers';
@@ -91,7 +91,8 @@ export class IAMModule {
91
91
  IAMConfigService,
92
92
  IAMDataSourceService,
93
93
  ...this.getServices(permissionMode),
94
- this.getPermissionGuardConfigProvider(enableCompanyFeature)
94
+ this.getPermissionGuardConfigProvider(enableCompanyFeature),
95
+ createModuleEventsProvider('iam', IAM_MODULE_OPTIONS)
95
96
  ];
96
97
  const module = {
97
98
  module: IAMModule,
@@ -118,7 +119,8 @@ export class IAMModule {
118
119
  IAMConfigService,
119
120
  IAMDataSourceService,
120
121
  ...this.getServices(permissionMode),
121
- this.getPermissionGuardConfigProvider(enableCompanyFeature)
122
+ this.getPermissionGuardConfigProvider(enableCompanyFeature),
123
+ createModuleEventsProvider('iam', IAM_MODULE_OPTIONS)
122
124
  ];
123
125
  const module = {
124
126
  module: IAMModule,
@@ -27,9 +27,9 @@ function _ts_param(paramIndex, decorator) {
27
27
  }
28
28
  import { ApiService, HybridCache } from '@flusys/nestjs-shared/classes';
29
29
  import { UtilsService } from '@flusys/nestjs-shared/modules';
30
- import { BadRequestException, Inject, Injectable, Scope } from '@nestjs/common';
31
- import { In } from 'typeorm';
32
- import { PERMISSION_OPERATION_MESSAGES } from '../config';
30
+ import { BadRequestException, ForbiddenException, Inject, Injectable, Scope } from '@nestjs/common';
31
+ import { In, IsNull } from 'typeorm';
32
+ import { ACTION_MESSAGES, PERMISSION_OPERATION_MESSAGES } from '../config';
33
33
  import { Action } from '../entities/action.entity';
34
34
  import { UserIamPermissionWithCompany } from '../entities/permission-with-company.entity';
35
35
  import { UserIamPermission } from '../entities/user-iam-permission.entity';
@@ -40,6 +40,30 @@ import { IAMDataSourceService } from './iam-datasource.service';
40
40
  import { PermissionCacheService } from './permission-cache.service';
41
41
  import { PermissionService } from './permission.service';
42
42
  export class ActionService extends ApiService {
43
+ async beforeUpdateOperation(dto, _user, _queryRunner) {
44
+ const dtos = Array.isArray(dto) ? dto : [
45
+ dto
46
+ ];
47
+ const ids = dtos.map((d)=>d.id);
48
+ await this.ensureDataSourceRepository();
49
+ const readonlyActions = await this.repository.find({
50
+ where: {
51
+ id: In(ids),
52
+ readOnly: true,
53
+ deletedAt: IsNull()
54
+ }
55
+ });
56
+ if (readonlyActions.length > 0) {
57
+ const names = readonlyActions.map((a)=>a.name).join(', ');
58
+ throw new ForbiddenException({
59
+ message: `Cannot modify readonly action(s): ${names}`,
60
+ messageKey: ACTION_MESSAGES.READONLY_UPDATE_FORBIDDEN,
61
+ messageVariables: {
62
+ names
63
+ }
64
+ });
65
+ }
66
+ }
43
67
  async beforeDeleteOperation(dto, user, queryRunner) {
44
68
  const actionIds = Array.isArray(dto.id) ? dto.id : [
45
69
  dto.id
@@ -47,6 +71,24 @@ export class ActionService extends ApiService {
47
71
  if (actionIds.length === 0) {
48
72
  return;
49
73
  }
74
+ await this.ensureDataSourceRepository();
75
+ const readonlyActions = await this.repository.find({
76
+ where: {
77
+ id: In(actionIds),
78
+ readOnly: true,
79
+ deletedAt: IsNull()
80
+ }
81
+ });
82
+ if (readonlyActions.length > 0) {
83
+ const names = readonlyActions.map((a)=>a.name).join(', ');
84
+ throw new ForbiddenException({
85
+ message: `Cannot delete readonly action(s): ${names}`,
86
+ messageKey: ACTION_MESSAGES.READONLY_DELETE_FORBIDDEN,
87
+ messageVariables: {
88
+ names
89
+ }
90
+ });
91
+ }
50
92
  const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
51
93
  const permissionEntity = enableCompanyFeature ? UserIamPermissionWithCompany : UserIamPermission;
52
94
  const permissionRepo = queryRunner.manager.getRepository(permissionEntity);
@@ -126,6 +168,7 @@ export class ActionService extends ApiService {
126
168
  'actionType',
127
169
  'permissionLogic',
128
170
  'isActive',
171
+ 'readOnly',
129
172
  'parentId',
130
173
  'serial',
131
174
  'createdAt'
@@ -37,6 +37,7 @@ describe('ActionService', ()=>{
37
37
  let mockPermissionCacheService;
38
38
  beforeEach(async ()=>{
39
39
  mockRepo = createMockRepository();
40
+ mockRepo.find.mockResolvedValue([]);
40
41
  const mockDataSourceProvider = createMockDataSourceProvider(mockRepo);
41
42
  mockIamConfigService = {
42
43
  isCompanyFeatureEnabled: jest.fn().mockReturnValue(false)
@@ -306,5 +307,41 @@ describe('ActionService', ()=>{
306
307
  }, buildMockUser(), queryRunner);
307
308
  expect(mockPermissionCacheService.invalidateUsers).not.toHaveBeenCalled();
308
309
  });
310
+ it('rejects deleting a readonly action', async ()=>{
311
+ mockRepo.find.mockResolvedValue([
312
+ buildAction({
313
+ readOnly: true
314
+ })
315
+ ]);
316
+ const permissionRepo = createMockRepository();
317
+ const queryRunner = buildQueryRunner(permissionRepo);
318
+ await expect(service.beforeDeleteOperation({
319
+ id: 'action-uuid-1',
320
+ type: 'delete'
321
+ }, buildMockUser(), queryRunner)).rejects.toThrow('Cannot delete readonly action');
322
+ expect(permissionRepo.find).not.toHaveBeenCalled();
323
+ });
324
+ });
325
+ describe('beforeUpdateOperation', ()=>{
326
+ it('rejects updating a readonly action', async ()=>{
327
+ mockRepo.find.mockResolvedValue([
328
+ buildAction({
329
+ readOnly: true
330
+ })
331
+ ]);
332
+ await expect(service.beforeUpdateOperation({
333
+ id: 'action-uuid-1',
334
+ name: 'Renamed'
335
+ }, buildMockUser(), {})).rejects.toThrow('Cannot modify readonly action');
336
+ });
337
+ it('allows updating a non-readonly action', async ()=>{
338
+ // The service filters readOnly: true in the query itself, so a non-readonly
339
+ // action never comes back — simulate that by resolving an empty array.
340
+ mockRepo.find.mockResolvedValue([]);
341
+ await expect(service.beforeUpdateOperation({
342
+ id: 'action-uuid-1',
343
+ name: 'Renamed'
344
+ }, buildMockUser(), {})).resolves.toBeUndefined();
345
+ });
309
346
  });
310
347
  });
@@ -26,9 +26,10 @@ function _ts_param(paramIndex, decorator) {
26
26
  };
27
27
  }
28
28
  import { LogAction } from '@flusys/nestjs-shared';
29
+ import { publishDomainEvent } from '@flusys/nestjs-shared/classes';
29
30
  import { BadRequestException, ConflictException, Inject, Injectable, Scope } from '@nestjs/common';
30
31
  import { In, IsNull } from 'typeorm';
31
- import { IAM_MODE_MESSAGES, PERMISSION_OPERATION_MESSAGES } from '../config';
32
+ import { IAM_EVENT_ACTIONS, IAM_EVENT_ENTITIES, IAM_EVENT_MODULE, IAM_MODE_MESSAGES, PERMISSION_OPERATION_MESSAGES } from '../config';
32
33
  import { AssignCompanyActionsDto, AssignRoleActionsDto, AssignUserActionsDto, AssignUserRolesDto, PermissionAction } from '../dtos/permission.dto';
33
34
  import { Action } from '../entities/action.entity';
34
35
  import { UserIamPermissionWithCompany } from '../entities/permission-with-company.entity';
@@ -145,7 +146,9 @@ export class PermissionService {
145
146
  await this.permissionCacheService.invalidateUser(dto.userId, enableCompanyFeature ? companyId : null, enableCompanyFeature ? [
146
147
  branchId
147
148
  ] : []);
148
- return this.buildOperationResult(dto.items.length, added, removed);
149
+ const result = this.buildOperationResult(dto.items.length, added, removed);
150
+ await this.publishAssignmentEvent(IAM_EVENT_ENTITIES.USER_ACTION, dto.userId, result);
151
+ return result;
149
152
  }
150
153
  async getUserActions(userId, companyId, branchId) {
151
154
  const permissionRepo = await this.getPermissionRepository();
@@ -259,7 +262,9 @@ export class PermissionService {
259
262
  removed = result.affected || 0;
260
263
  }
261
264
  await this.permissionCacheService.invalidateRoleMembersCache(dto.roleId);
262
- return this.buildOperationResult(dto.items.length, added, removed);
265
+ const result = this.buildOperationResult(dto.items.length, added, removed);
266
+ await this.publishAssignmentEvent(IAM_EVENT_ENTITIES.ROLE_ACTION, dto.roleId, result);
267
+ return result;
263
268
  }
264
269
  async getRoleActions(roleId) {
265
270
  const permissionRepo = await this.getPermissionRepository();
@@ -369,7 +374,9 @@ export class PermissionService {
369
374
  }
370
375
  });
371
376
  await this.permissionCacheService.invalidateCompanyMembersCache(dto.companyId);
372
- return this.buildOperationResult(dto.items.length, added, removed);
377
+ const result = this.buildOperationResult(dto.items.length, added, removed);
378
+ await this.publishAssignmentEvent(IAM_EVENT_ENTITIES.COMPANY_ACTION, dto.companyId, result);
379
+ return result;
373
380
  }
374
381
  /** Get all actions assigned to a company (whitelist) */ async getCompanyActions(companyId, isOnlyId = false) {
375
382
  const permissionRepo = await this.getPermissionRepository();
@@ -476,7 +483,9 @@ export class PermissionService {
476
483
  await this.permissionCacheService.invalidateUser(dto.userId, enableCompanyFeature ? companyId : null, [
477
484
  branchId
478
485
  ]);
479
- return this.buildOperationResult(dto.items.length, added, removed);
486
+ const result = this.buildOperationResult(dto.items.length, added, removed);
487
+ await this.publishAssignmentEvent(IAM_EVENT_ENTITIES.USER_ROLE, dto.userId, result);
488
+ return result;
480
489
  }
481
490
  /** Get user's roles (branch-scoped, filtered by companyId and branchId if provided) */ async getUserRoles(userId, branchId, companyId) {
482
491
  const permissionRepo = await this.getPermissionRepository();
@@ -630,6 +639,21 @@ export class PermissionService {
630
639
  toRemove: items.filter((item)=>item.action === PermissionAction.REMOVE)
631
640
  };
632
641
  }
642
+ async publishAssignmentEvent(entity, targetId, result) {
643
+ await publishDomainEvent({
644
+ module: IAM_EVENT_MODULE,
645
+ entity,
646
+ action: IAM_EVENT_ACTIONS.PERMISSIONS_ASSIGNED,
647
+ ids: [
648
+ targetId
649
+ ],
650
+ metadata: {
651
+ added: result.added,
652
+ removed: result.removed,
653
+ total: result.total
654
+ }
655
+ });
656
+ }
633
657
  /** Build standard operation result DTO */ buildOperationResult(_totalItems, added, removed) {
634
658
  return {
635
659
  added,
@@ -1,4 +1,5 @@
1
1
  import { Test } from '@nestjs/testing';
2
+ import { EventBusRegistry } from '@flusys/nestjs-shared/classes';
2
3
  import { createMockRepository } from '@test-utils/mocks/repository.mock';
3
4
  import { IAM_MODE_MESSAGES, PERMISSION_OPERATION_MESSAGES } from '../config';
4
5
  import { PermissionAction } from '../dtos/permission.dto';
@@ -830,4 +831,84 @@ describe('PermissionService', ()=>{
830
831
  ]);
831
832
  });
832
833
  });
834
+ describe('domain events', ()=>{
835
+ let publish;
836
+ beforeEach(()=>{
837
+ publish = jest.fn().mockResolvedValue(undefined);
838
+ EventBusRegistry.reset();
839
+ EventBusRegistry.setBus({
840
+ publish,
841
+ subscribe: jest.fn(),
842
+ close: jest.fn()
843
+ });
844
+ EventBusRegistry.configureModule('iam', {
845
+ enabled: true
846
+ });
847
+ mockIamConfigService.isDirectPermissionEnabled.mockReturnValue(true);
848
+ mockPermissionRepo.find.mockResolvedValue([]);
849
+ mockPermissionRepo.save.mockResolvedValue([
850
+ {}
851
+ ]);
852
+ mockPermissionRepo.delete.mockResolvedValue(buildDeleteResult(1));
853
+ });
854
+ afterEach(()=>{
855
+ EventBusRegistry.reset();
856
+ });
857
+ it('publishes iam.user_action.permissions-assigned with the operation counts', async ()=>{
858
+ await service.assignUserActions({
859
+ userId: 'user-1',
860
+ items: [
861
+ {
862
+ id: 'action-1',
863
+ action: PermissionAction.ADD
864
+ }
865
+ ]
866
+ });
867
+ expect(publish).toHaveBeenCalledWith(expect.objectContaining({
868
+ name: 'iam.user_action.permissions-assigned',
869
+ payload: expect.objectContaining({
870
+ ids: [
871
+ 'user-1'
872
+ ],
873
+ metadata: expect.objectContaining({
874
+ total: 1
875
+ })
876
+ })
877
+ }));
878
+ });
879
+ it('publishes iam.user_role.permissions-assigned when roles are assigned', async ()=>{
880
+ mockRoleRepo.find.mockResolvedValue([
881
+ {
882
+ id: 'role-1'
883
+ }
884
+ ]);
885
+ await service.assignUserRoles({
886
+ userId: 'user-1',
887
+ items: [
888
+ {
889
+ id: 'role-1',
890
+ action: PermissionAction.ADD
891
+ }
892
+ ]
893
+ });
894
+ expect(publish).toHaveBeenCalledWith(expect.objectContaining({
895
+ name: 'iam.user_role.permissions-assigned'
896
+ }));
897
+ });
898
+ it('does not publish when the iam module has events disabled', async ()=>{
899
+ EventBusRegistry.configureModule('iam', {
900
+ enabled: false
901
+ });
902
+ await service.assignUserActions({
903
+ userId: 'user-1',
904
+ items: [
905
+ {
906
+ id: 'action-1',
907
+ action: PermissionAction.ADD
908
+ }
909
+ ]
910
+ });
911
+ expect(publish).not.toHaveBeenCalled();
912
+ });
913
+ });
833
914
  });
@@ -28,8 +28,9 @@ function _ts_param(paramIndex, decorator) {
28
28
  import { ApiService, HybridCache } from '@flusys/nestjs-shared/classes';
29
29
  import { UtilsService } from '@flusys/nestjs-shared/modules';
30
30
  import { applyCompanyFilter } from '@flusys/nestjs-shared/utils';
31
- import { Inject, Injectable, Scope } from '@nestjs/common';
32
- import { In } from 'typeorm';
31
+ import { ForbiddenException, Inject, Injectable, Scope } from '@nestjs/common';
32
+ import { In, IsNull } from 'typeorm';
33
+ import { ROLE_MESSAGES } from '../config';
33
34
  import { UserIamPermissionWithCompany } from '../entities/permission-with-company.entity';
34
35
  import { RoleWithCompany } from '../entities/role-with-company.entity';
35
36
  import { Role } from '../entities/role.entity';
@@ -40,6 +41,30 @@ import { IAMConfigService } from './iam-config.service';
40
41
  import { IAMDataSourceService } from './iam-datasource.service';
41
42
  import { PermissionCacheService } from './permission-cache.service';
42
43
  export class RoleService extends ApiService {
44
+ async beforeUpdateOperation(dto, _user, _queryRunner) {
45
+ const dtos = Array.isArray(dto) ? dto : [
46
+ dto
47
+ ];
48
+ const ids = dtos.map((d)=>d.id);
49
+ await this.ensureDataSourceRepository();
50
+ const readonlyRoles = await this.repository.find({
51
+ where: {
52
+ id: In(ids),
53
+ readOnly: true,
54
+ deletedAt: IsNull()
55
+ }
56
+ });
57
+ if (readonlyRoles.length > 0) {
58
+ const names = readonlyRoles.map((r)=>r.name).join(', ');
59
+ throw new ForbiddenException({
60
+ message: `Cannot modify readonly role(s): ${names}`,
61
+ messageKey: ROLE_MESSAGES.READONLY_UPDATE_FORBIDDEN,
62
+ messageVariables: {
63
+ names
64
+ }
65
+ });
66
+ }
67
+ }
43
68
  async beforeDeleteOperation(dto, user, queryRunner) {
44
69
  await super.beforeDeleteOperation(dto, user, queryRunner);
45
70
  const roleIds = Array.isArray(dto.id) ? dto.id : [
@@ -48,6 +73,24 @@ export class RoleService extends ApiService {
48
73
  if (roleIds.length === 0) {
49
74
  return;
50
75
  }
76
+ await this.ensureDataSourceRepository();
77
+ const readonlyRoles = await this.repository.find({
78
+ where: {
79
+ id: In(roleIds),
80
+ readOnly: true,
81
+ deletedAt: IsNull()
82
+ }
83
+ });
84
+ if (readonlyRoles.length > 0) {
85
+ const names = readonlyRoles.map((r)=>r.name).join(', ');
86
+ throw new ForbiddenException({
87
+ message: `Cannot delete readonly role(s): ${names}`,
88
+ messageKey: ROLE_MESSAGES.READONLY_DELETE_FORBIDDEN,
89
+ messageVariables: {
90
+ names
91
+ }
92
+ });
93
+ }
51
94
  const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
52
95
  const permissionEntity = enableCompanyFeature ? UserIamPermissionWithCompany : UserIamPermission;
53
96
  const permissionRepo = queryRunner.manager.getRepository(permissionEntity);
@@ -101,6 +144,7 @@ export class RoleService extends ApiService {
101
144
  'name',
102
145
  'description',
103
146
  'isActive',
147
+ 'readOnly',
104
148
  'serial',
105
149
  'createdAt'
106
150
  ];
@@ -32,6 +32,7 @@ describe('RoleService', ()=>{
32
32
  let mockPermissionCacheService;
33
33
  beforeEach(async ()=>{
34
34
  mockRepo = createMockRepository();
35
+ mockRepo.find.mockResolvedValue([]);
35
36
  const mockDataSourceProvider = createMockDataSourceProvider(mockRepo);
36
37
  mockIamConfigService = {
37
38
  isCompanyFeatureEnabled: jest.fn().mockReturnValue(false)
@@ -301,5 +302,39 @@ describe('RoleService', ()=>{
301
302
  'user-1'
302
303
  ]);
303
304
  });
305
+ it('rejects deleting a readonly role', async ()=>{
306
+ mockRepo.find.mockResolvedValue([
307
+ buildRole({
308
+ readOnly: true
309
+ })
310
+ ]);
311
+ const permissionRepo = createMockRepository();
312
+ const queryRunner = buildQueryRunner(permissionRepo);
313
+ await expect(service.beforeDeleteOperation({
314
+ id: 'role-uuid-1',
315
+ type: 'delete'
316
+ }, buildMockUser(), queryRunner)).rejects.toThrow('Cannot delete readonly role');
317
+ expect(permissionRepo.find).not.toHaveBeenCalled();
318
+ });
319
+ });
320
+ describe('beforeUpdateOperation', ()=>{
321
+ it('rejects updating a readonly role', async ()=>{
322
+ mockRepo.find.mockResolvedValue([
323
+ buildRole({
324
+ readOnly: true
325
+ })
326
+ ]);
327
+ await expect(service.beforeUpdateOperation({
328
+ id: 'role-uuid-1',
329
+ name: 'Renamed'
330
+ }, buildMockUser(), {})).rejects.toThrow('Cannot modify readonly role');
331
+ });
332
+ it('allows updating a non-readonly role', async ()=>{
333
+ mockRepo.find.mockResolvedValue([]);
334
+ await expect(service.beforeUpdateOperation({
335
+ id: 'role-uuid-1',
336
+ name: 'Renamed'
337
+ }, buildMockUser(), {})).resolves.toBeUndefined();
338
+ });
304
339
  });
305
340
  });
@@ -1,6 +1,8 @@
1
1
  import { IBootstrapAppConfig, IDataSourceServiceOptions, IDynamicModuleConfig, IModuleOptionsFactory } from '@flusys/nestjs-core';
2
+ import { IModuleEventsConfig } from '@flusys/nestjs-shared/interfaces';
2
3
  import { ModuleMetadata, Type } from '@nestjs/common';
3
4
  export interface IIAMModuleConfig extends IDataSourceServiceOptions {
5
+ events?: IModuleEventsConfig;
4
6
  }
5
7
  export interface IAMModuleOptions extends IDynamicModuleConfig {
6
8
  bootstrapAppConfig?: IBootstrapAppConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flusys/nestjs-iam",
3
- "version": "6.4.1",
3
+ "version": "7.0.0-beta.1",
4
4
  "description": "Identity and Access Management (IAM) module for NestJS applications",
5
5
  "main": "cjs/index.js",
6
6
  "module": "fesm/index.js",
@@ -90,7 +90,7 @@
90
90
  "express": "^5.0.0"
91
91
  },
92
92
  "dependencies": {
93
- "@flusys/nestjs-core": "6.4.1",
94
- "@flusys/nestjs-shared": "6.4.1"
93
+ "@flusys/nestjs-core": "7.0.0-beta.1",
94
+ "@flusys/nestjs-shared": "7.0.0-beta.1"
95
95
  }
96
96
  }
@@ -17,6 +17,7 @@ export declare class ActionService extends ApiService<CreateActionDto, UpdateAct
17
17
  private readonly permissionService;
18
18
  private readonly permissionCacheService;
19
19
  constructor(cacheManager: HybridCache, utilsService: UtilsService, iamConfigService: IAMConfigService, dataSourceProvider: IAMDataSourceService, permissionService: PermissionService, permissionCacheService: PermissionCacheService);
20
+ protected beforeUpdateOperation(dto: UpdateActionDto | UpdateActionDto[], _user: ILoggedUserInfo | null, _queryRunner: QueryRunner): Promise<void>;
20
21
  protected beforeDeleteOperation(dto: DeleteDto, user: ILoggedUserInfo | null, queryRunner: QueryRunner): Promise<void>;
21
22
  getSelectQuery(query: SelectQueryBuilder<Action>, _user: ILoggedUserInfo | null, select?: string[]): Promise<{
22
23
  query: SelectQueryBuilder<Action>;
@@ -23,6 +23,7 @@ export declare class PermissionService {
23
23
  getUserEffectiveActions(userId: string, companyId?: string | null, branchId?: string | null): Promise<UserActionResponseDto[]>;
24
24
  getMyPermissions(userId: string, branchId: string | null, companyId?: string | null, parentCodes?: string[]): Promise<MyPermissionsResponseDto>;
25
25
  private splitItemsByAction;
26
+ private publishAssignmentEvent;
26
27
  private buildOperationResult;
27
28
  revokeCompanyPermissions(companyId: string): Promise<void>;
28
29
  revokeBranchPermissions(branchId: string, companyId: string): Promise<void>;
@@ -15,6 +15,7 @@ export declare class RoleService extends ApiService<CreateRoleDto, UpdateRoleDto
15
15
  private readonly iamConfigService;
16
16
  private readonly permissionCacheService;
17
17
  constructor(cacheManager: HybridCache, utilsService: UtilsService, iamConfigService: IAMConfigService, dataSourceProvider: IAMDataSourceService, permissionCacheService: PermissionCacheService);
18
+ protected beforeUpdateOperation(dto: UpdateRoleDto | UpdateRoleDto[], _user: ILoggedUserInfo | null, _queryRunner: QueryRunner): Promise<void>;
18
19
  protected beforeDeleteOperation(dto: DeleteDto, user: ILoggedUserInfo | null, queryRunner: QueryRunner): Promise<void>;
19
20
  protected resolveEntity(): EntityTarget<RoleBase>;
20
21
  convertSingleDtoToEntity(dto: CreateRoleDto | UpdateRoleDto, user: ILoggedUserInfo | null): Promise<RoleBase>;