@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.
- package/cjs/controllers/action.controller.spec.js +79 -0
- package/cjs/controllers/company-action-permission.controller.js +1 -1
- package/cjs/controllers/company-action-permission.controller.spec.js +65 -0
- package/cjs/controllers/my-permission.controller.spec.js +56 -0
- package/cjs/controllers/role-permission.controller.spec.js +140 -0
- package/cjs/controllers/role.controller.spec.js +86 -0
- package/cjs/controllers/user-action-permission.controller.js +1 -1
- package/cjs/controllers/user-action-permission.controller.spec.js +68 -0
- package/cjs/docs/iam-swagger.config.spec.js +99 -0
- package/cjs/dtos/permission.dto.js +7 -0
- package/cjs/entities/index.spec.js +56 -0
- package/cjs/entities/permission-base.entity.js +1 -10
- package/cjs/entities/permission-with-company.entity.js +0 -3
- package/cjs/entities/user-iam-permission.entity.js +0 -3
- package/cjs/helpers/company-access.helper.spec.js +68 -0
- package/cjs/helpers/permission-mode.helper.spec.js +54 -0
- package/cjs/modules/iam.module.js +2 -2
- package/cjs/modules/iam.module.spec.js +293 -0
- package/cjs/services/action.service.js +4 -4
- package/cjs/services/action.service.spec.js +314 -0
- package/cjs/services/iam-config.service.spec.js +110 -0
- package/cjs/services/iam-datasource.service.spec.js +117 -0
- package/cjs/services/permission-cache.service.js +363 -52
- package/cjs/services/permission-cache.service.spec.js +699 -0
- package/cjs/services/permission.service.js +92 -396
- package/cjs/services/permission.service.spec.js +692 -0
- package/cjs/services/role.service.js +1 -1
- package/cjs/services/role.service.spec.js +309 -0
- package/dtos/permission.dto.d.ts +1 -0
- package/entities/permission-base.entity.d.ts +0 -1
- package/fesm/controllers/action.controller.spec.js +75 -0
- package/fesm/controllers/company-action-permission.controller.js +1 -1
- package/fesm/controllers/company-action-permission.controller.spec.js +61 -0
- package/fesm/controllers/my-permission.controller.spec.js +52 -0
- package/fesm/controllers/role-permission.controller.spec.js +136 -0
- package/fesm/controllers/role.controller.spec.js +82 -0
- package/fesm/controllers/user-action-permission.controller.js +1 -1
- package/fesm/controllers/user-action-permission.controller.spec.js +64 -0
- package/fesm/docs/iam-swagger.config.spec.js +95 -0
- package/fesm/dtos/permission.dto.js +7 -0
- package/fesm/entities/index.spec.js +52 -0
- package/fesm/entities/permission-base.entity.js +1 -10
- package/fesm/entities/permission-with-company.entity.js +0 -3
- package/fesm/entities/user-iam-permission.entity.js +0 -3
- package/fesm/helpers/company-access.helper.spec.js +64 -0
- package/fesm/helpers/permission-mode.helper.spec.js +50 -0
- package/fesm/modules/iam.module.js +4 -4
- package/fesm/modules/iam.module.spec.js +289 -0
- package/fesm/services/action.service.js +4 -4
- package/fesm/services/action.service.spec.js +310 -0
- package/fesm/services/iam-config.service.spec.js +106 -0
- package/fesm/services/iam-datasource.service.spec.js +113 -0
- package/fesm/services/permission-cache.service.js +365 -51
- package/fesm/services/permission-cache.service.spec.js +695 -0
- package/fesm/services/permission.service.js +92 -396
- package/fesm/services/permission.service.spec.js +688 -0
- package/fesm/services/role.service.js +1 -1
- package/fesm/services/role.service.spec.js +305 -0
- package/package.json +3 -3
- package/services/permission-cache.service.d.ts +20 -3
- package/services/permission.service.d.ts +2 -16
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
const _index = require("./index");
|
|
6
|
+
const _actionentity = require("./action.entity");
|
|
7
|
+
const _roleentity = require("./role.entity");
|
|
8
|
+
const _rolewithcompanyentity = require("./role-with-company.entity");
|
|
9
|
+
const _useriampermissionentity = require("./user-iam-permission.entity");
|
|
10
|
+
const _permissionwithcompanyentity = require("./permission-with-company.entity");
|
|
11
|
+
describe('getIAMEntitiesByConfig', ()=>{
|
|
12
|
+
it('should always include Action', ()=>{
|
|
13
|
+
expect((0, _index.getIAMEntitiesByConfig)(false)).toContain(_actionentity.Action);
|
|
14
|
+
expect((0, _index.getIAMEntitiesByConfig)(true)).toContain(_actionentity.Action);
|
|
15
|
+
});
|
|
16
|
+
it('should default permissionMode to FULL and include Role + UserIamPermission when company feature is off', ()=>{
|
|
17
|
+
const entities = (0, _index.getIAMEntitiesByConfig)(false);
|
|
18
|
+
expect(entities).toEqual(expect.arrayContaining([
|
|
19
|
+
_actionentity.Action,
|
|
20
|
+
_useriampermissionentity.UserIamPermission,
|
|
21
|
+
_roleentity.Role
|
|
22
|
+
]));
|
|
23
|
+
expect(entities).not.toContain(_rolewithcompanyentity.RoleWithCompany);
|
|
24
|
+
expect(entities).not.toContain(_permissionwithcompanyentity.UserIamPermissionWithCompany);
|
|
25
|
+
});
|
|
26
|
+
it('should use the company-scoped permission entity when enableCompanyFeature is true', ()=>{
|
|
27
|
+
const entities = (0, _index.getIAMEntitiesByConfig)(true, 'FULL');
|
|
28
|
+
expect(entities).toContain(_permissionwithcompanyentity.UserIamPermissionWithCompany);
|
|
29
|
+
expect(entities).not.toContain(_useriampermissionentity.UserIamPermission);
|
|
30
|
+
});
|
|
31
|
+
it('should use the company-scoped role entity when enableCompanyFeature is true', ()=>{
|
|
32
|
+
const entities = (0, _index.getIAMEntitiesByConfig)(true, 'RBAC');
|
|
33
|
+
expect(entities).toContain(_rolewithcompanyentity.RoleWithCompany);
|
|
34
|
+
expect(entities).not.toContain(_roleentity.Role);
|
|
35
|
+
});
|
|
36
|
+
it('should include Role for RBAC mode', ()=>{
|
|
37
|
+
expect((0, _index.getIAMEntitiesByConfig)(false, 'RBAC')).toContain(_roleentity.Role);
|
|
38
|
+
});
|
|
39
|
+
it('should include Role for FULL mode', ()=>{
|
|
40
|
+
expect((0, _index.getIAMEntitiesByConfig)(false, 'FULL')).toContain(_roleentity.Role);
|
|
41
|
+
});
|
|
42
|
+
it('should exclude Role entirely for DIRECT mode', ()=>{
|
|
43
|
+
const entities = (0, _index.getIAMEntitiesByConfig)(false, 'DIRECT');
|
|
44
|
+
expect(entities).not.toContain(_roleentity.Role);
|
|
45
|
+
expect(entities).not.toContain(_rolewithcompanyentity.RoleWithCompany);
|
|
46
|
+
expect(entities).toEqual(expect.arrayContaining([
|
|
47
|
+
_actionentity.Action,
|
|
48
|
+
_useriampermissionentity.UserIamPermission
|
|
49
|
+
]));
|
|
50
|
+
});
|
|
51
|
+
it('should exclude the company-scoped Role for DIRECT mode even with company feature enabled', ()=>{
|
|
52
|
+
const entities = (0, _index.getIAMEntitiesByConfig)(true, 'DIRECT');
|
|
53
|
+
expect(entities).not.toContain(_rolewithcompanyentity.RoleWithCompany);
|
|
54
|
+
expect(entities).toContain(_permissionwithcompanyentity.UserIamPermissionWithCompany);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -39,7 +39,7 @@ let PermissionBase = class PermissionBase extends _nestjsshared.Identity {
|
|
|
39
39
|
return true;
|
|
40
40
|
}
|
|
41
41
|
constructor(...args){
|
|
42
|
-
super(...args), _define_property(this, "permissionType", void 0), _define_property(this, "sourceType", void 0), _define_property(this, "sourceId", void 0), _define_property(this, "targetType", void 0), _define_property(this, "targetId", void 0), _define_property(this, "
|
|
42
|
+
super(...args), _define_property(this, "permissionType", void 0), _define_property(this, "sourceType", void 0), _define_property(this, "sourceId", void 0), _define_property(this, "targetType", void 0), _define_property(this, "targetId", void 0), _define_property(this, "validFrom", void 0), _define_property(this, "validUntil", void 0), _define_property(this, "reason", void 0);
|
|
43
43
|
}
|
|
44
44
|
};
|
|
45
45
|
_ts_decorate([
|
|
@@ -82,15 +82,6 @@ _ts_decorate([
|
|
|
82
82
|
}),
|
|
83
83
|
_ts_metadata("design:type", String)
|
|
84
84
|
], PermissionBase.prototype, "targetId", void 0);
|
|
85
|
-
_ts_decorate([
|
|
86
|
-
(0, _typeorm.Column)({
|
|
87
|
-
type: 'varchar',
|
|
88
|
-
length: 150,
|
|
89
|
-
nullable: true,
|
|
90
|
-
name: 'user_id'
|
|
91
|
-
}),
|
|
92
|
-
_ts_metadata("design:type", Object)
|
|
93
|
-
], PermissionBase.prototype, "userId", void 0);
|
|
94
85
|
_ts_decorate([
|
|
95
86
|
(0, _typeorm.Column)({
|
|
96
87
|
type: 'timestamp',
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
const _common = require("@nestjs/common");
|
|
6
|
+
const _constants = require("@flusys/nestjs-shared/constants");
|
|
7
|
+
const _loggedusermock = require("@test-utils/mocks/logged-user.mock");
|
|
8
|
+
const _companyaccesshelper = require("./company-access.helper");
|
|
9
|
+
describe('validateCompanyAccess', ()=>{
|
|
10
|
+
function buildConfig(isCompanyFeatureEnabled) {
|
|
11
|
+
return {
|
|
12
|
+
isCompanyFeatureEnabled: jest.fn().mockReturnValue(isCompanyFeatureEnabled)
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
it('does not throw when company feature is disabled', ()=>{
|
|
16
|
+
const config = buildConfig(false);
|
|
17
|
+
const user = (0, _loggedusermock.buildMockUser)({
|
|
18
|
+
companyId: 'company-uuid-1'
|
|
19
|
+
});
|
|
20
|
+
expect(()=>(0, _companyaccesshelper.validateCompanyAccess)(config, 'company-uuid-2', user)).not.toThrow();
|
|
21
|
+
});
|
|
22
|
+
it('does not throw when companyId is undefined', ()=>{
|
|
23
|
+
const config = buildConfig(true);
|
|
24
|
+
const user = (0, _loggedusermock.buildMockUser)({
|
|
25
|
+
companyId: 'company-uuid-1'
|
|
26
|
+
});
|
|
27
|
+
expect(()=>(0, _companyaccesshelper.validateCompanyAccess)(config, undefined, user)).not.toThrow();
|
|
28
|
+
});
|
|
29
|
+
it('does not throw when the companyId matches the user company', ()=>{
|
|
30
|
+
const config = buildConfig(true);
|
|
31
|
+
const user = (0, _loggedusermock.buildMockUser)({
|
|
32
|
+
companyId: 'company-uuid-1'
|
|
33
|
+
});
|
|
34
|
+
expect(()=>(0, _companyaccesshelper.validateCompanyAccess)(config, 'company-uuid-1', user)).not.toThrow();
|
|
35
|
+
});
|
|
36
|
+
it('throws ForbiddenException with default message/key when the company does not match', ()=>{
|
|
37
|
+
const config = buildConfig(true);
|
|
38
|
+
const user = (0, _loggedusermock.buildMockUser)({
|
|
39
|
+
companyId: 'company-uuid-1'
|
|
40
|
+
});
|
|
41
|
+
try {
|
|
42
|
+
(0, _companyaccesshelper.validateCompanyAccess)(config, 'company-uuid-2', user);
|
|
43
|
+
fail('expected validateCompanyAccess to throw');
|
|
44
|
+
} catch (error) {
|
|
45
|
+
expect(error).toBeInstanceOf(_common.ForbiddenException);
|
|
46
|
+
expect(error.getResponse()).toEqual(expect.objectContaining({
|
|
47
|
+
message: 'You do not have access to this company',
|
|
48
|
+
messageKey: _constants.AUTH_MESSAGES.COMPANY_NO_ACCESS
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
it('throws with a custom message and messageKey when provided', ()=>{
|
|
53
|
+
const config = buildConfig(true);
|
|
54
|
+
const user = (0, _loggedusermock.buildMockUser)({
|
|
55
|
+
companyId: 'company-uuid-1'
|
|
56
|
+
});
|
|
57
|
+
try {
|
|
58
|
+
(0, _companyaccesshelper.validateCompanyAccess)(config, 'company-uuid-2', user, 'Custom message', 'custom.message.key');
|
|
59
|
+
fail('expected validateCompanyAccess to throw');
|
|
60
|
+
} catch (error) {
|
|
61
|
+
expect(error).toBeInstanceOf(_common.ForbiddenException);
|
|
62
|
+
expect(error.getResponse()).toEqual(expect.objectContaining({
|
|
63
|
+
message: 'Custom message',
|
|
64
|
+
messageKey: 'custom.message.key'
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
const _permissiontypeenum = require("../enums/permission-type.enum");
|
|
6
|
+
const _permissionmodehelper = require("./permission-mode.helper");
|
|
7
|
+
describe('PermissionModeHelper', ()=>{
|
|
8
|
+
describe('fromString', ()=>{
|
|
9
|
+
it('converts "RBAC" to IAMPermissionMode.RBAC', ()=>{
|
|
10
|
+
expect(_permissionmodehelper.PermissionModeHelper.fromString('RBAC')).toBe(_permissiontypeenum.IAMPermissionMode.RBAC);
|
|
11
|
+
});
|
|
12
|
+
it('converts "DIRECT" to IAMPermissionMode.DIRECT', ()=>{
|
|
13
|
+
expect(_permissionmodehelper.PermissionModeHelper.fromString('DIRECT')).toBe(_permissiontypeenum.IAMPermissionMode.DIRECT);
|
|
14
|
+
});
|
|
15
|
+
it('converts "FULL" to IAMPermissionMode.FULL', ()=>{
|
|
16
|
+
expect(_permissionmodehelper.PermissionModeHelper.fromString('FULL')).toBe(_permissiontypeenum.IAMPermissionMode.FULL);
|
|
17
|
+
});
|
|
18
|
+
it('defaults to FULL when the string is undefined', ()=>{
|
|
19
|
+
expect(_permissionmodehelper.PermissionModeHelper.fromString(undefined)).toBe(_permissiontypeenum.IAMPermissionMode.FULL);
|
|
20
|
+
});
|
|
21
|
+
it('defaults to FULL when the string is empty', ()=>{
|
|
22
|
+
expect(_permissionmodehelper.PermissionModeHelper.fromString('')).toBe(_permissiontypeenum.IAMPermissionMode.FULL);
|
|
23
|
+
});
|
|
24
|
+
it('defaults to FULL when the string is not a known mode', ()=>{
|
|
25
|
+
expect(_permissionmodehelper.PermissionModeHelper.fromString('INVALID_MODE')).toBe(_permissiontypeenum.IAMPermissionMode.FULL);
|
|
26
|
+
});
|
|
27
|
+
it('is case-sensitive and falls back to FULL for lowercase input', ()=>{
|
|
28
|
+
expect(_permissionmodehelper.PermissionModeHelper.fromString('rbac')).toBe(_permissiontypeenum.IAMPermissionMode.FULL);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
describe('toString', ()=>{
|
|
32
|
+
it('converts IAMPermissionMode.RBAC to "RBAC"', ()=>{
|
|
33
|
+
expect(_permissionmodehelper.PermissionModeHelper.toString(_permissiontypeenum.IAMPermissionMode.RBAC)).toBe('RBAC');
|
|
34
|
+
});
|
|
35
|
+
it('converts IAMPermissionMode.DIRECT to "DIRECT"', ()=>{
|
|
36
|
+
expect(_permissionmodehelper.PermissionModeHelper.toString(_permissiontypeenum.IAMPermissionMode.DIRECT)).toBe('DIRECT');
|
|
37
|
+
});
|
|
38
|
+
it('converts IAMPermissionMode.FULL to "FULL"', ()=>{
|
|
39
|
+
expect(_permissionmodehelper.PermissionModeHelper.toString(_permissiontypeenum.IAMPermissionMode.FULL)).toBe('FULL');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
describe('round trip', ()=>{
|
|
43
|
+
it('fromString(toString(mode)) returns the original mode for every enum value', ()=>{
|
|
44
|
+
const modes = [
|
|
45
|
+
_permissiontypeenum.IAMPermissionMode.RBAC,
|
|
46
|
+
_permissiontypeenum.IAMPermissionMode.DIRECT,
|
|
47
|
+
_permissiontypeenum.IAMPermissionMode.FULL
|
|
48
|
+
];
|
|
49
|
+
for (const mode of modes){
|
|
50
|
+
expect(_permissionmodehelper.PermissionModeHelper.fromString(_permissionmodehelper.PermissionModeHelper.toString(mode))).toBe(mode);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -69,8 +69,8 @@ let IAMModule = class IAMModule {
|
|
|
69
69
|
provide: _nestjsshared.PERMISSION_GUARD_CONFIG,
|
|
70
70
|
useValue: {
|
|
71
71
|
enableCompanyFeature,
|
|
72
|
-
userPermissionKeyFormat: `${
|
|
73
|
-
companyPermissionKeyFormat: `${
|
|
72
|
+
userPermissionKeyFormat: `${_nestjsshared.PERMISSIONS_CACHE_PREFIX}:user:{userId}`,
|
|
73
|
+
companyPermissionKeyFormat: `${_nestjsshared.PERMISSIONS_CACHE_PREFIX}:company:{companyId}:branch:{branchId}:user:{userId}`
|
|
74
74
|
}
|
|
75
75
|
};
|
|
76
76
|
}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
const _nestjsshared = require("@flusys/nestjs-shared");
|
|
6
|
+
const _iammodule = require("./iam.module");
|
|
7
|
+
const _iamconstants = require("../config/iam.constants");
|
|
8
|
+
const _controllers = require("../controllers");
|
|
9
|
+
const _services = require("../services");
|
|
10
|
+
const _iamconfigservice = require("../services/iam-config.service");
|
|
11
|
+
const _iamdatasourceservice = require("../services/iam-datasource.service");
|
|
12
|
+
const _permissioncacheservice = require("../services/permission-cache.service");
|
|
13
|
+
function _define_property(obj, key, value) {
|
|
14
|
+
if (key in obj) {
|
|
15
|
+
Object.defineProperty(obj, key, {
|
|
16
|
+
value: value,
|
|
17
|
+
enumerable: true,
|
|
18
|
+
configurable: true,
|
|
19
|
+
writable: true
|
|
20
|
+
});
|
|
21
|
+
} else {
|
|
22
|
+
obj[key] = value;
|
|
23
|
+
}
|
|
24
|
+
return obj;
|
|
25
|
+
}
|
|
26
|
+
function providerTokens(providers) {
|
|
27
|
+
return providers.map((p)=>typeof p === 'function' ? p : p.provide);
|
|
28
|
+
}
|
|
29
|
+
describe('IAMModule', ()=>{
|
|
30
|
+
describe('forRoot - controller/service registration matrix', ()=>{
|
|
31
|
+
it('should register no controllers by default (includeController defaults to false)', ()=>{
|
|
32
|
+
const dynamicModule = _iammodule.IAMModule.forRoot();
|
|
33
|
+
expect(dynamicModule.controllers).toEqual([]);
|
|
34
|
+
});
|
|
35
|
+
it('DIRECT mode: registers only UserActionPermissionController beyond the base set', ()=>{
|
|
36
|
+
const dynamicModule = _iammodule.IAMModule.forRoot({
|
|
37
|
+
includeController: true,
|
|
38
|
+
bootstrapAppConfig: {
|
|
39
|
+
permissionMode: 'DIRECT'
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
expect(dynamicModule.controllers).toEqual([
|
|
43
|
+
_controllers.ActionController,
|
|
44
|
+
_controllers.MyPermissionController,
|
|
45
|
+
_controllers.UserActionPermissionController
|
|
46
|
+
]);
|
|
47
|
+
expect(providerTokens(dynamicModule.providers)).not.toContain(_services.RoleService);
|
|
48
|
+
});
|
|
49
|
+
it('RBAC mode: registers RoleController + RolePermissionController but not UserActionPermissionController', ()=>{
|
|
50
|
+
const dynamicModule = _iammodule.IAMModule.forRoot({
|
|
51
|
+
includeController: true,
|
|
52
|
+
bootstrapAppConfig: {
|
|
53
|
+
permissionMode: 'RBAC'
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
expect(dynamicModule.controllers).toEqual([
|
|
57
|
+
_controllers.ActionController,
|
|
58
|
+
_controllers.MyPermissionController,
|
|
59
|
+
_controllers.RoleController,
|
|
60
|
+
_controllers.RolePermissionController
|
|
61
|
+
]);
|
|
62
|
+
expect(providerTokens(dynamicModule.providers)).toContain(_services.RoleService);
|
|
63
|
+
});
|
|
64
|
+
it('FULL mode: registers every permission controller', ()=>{
|
|
65
|
+
const dynamicModule = _iammodule.IAMModule.forRoot({
|
|
66
|
+
includeController: true,
|
|
67
|
+
bootstrapAppConfig: {
|
|
68
|
+
permissionMode: 'FULL'
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
expect(dynamicModule.controllers).toEqual([
|
|
72
|
+
_controllers.ActionController,
|
|
73
|
+
_controllers.MyPermissionController,
|
|
74
|
+
_controllers.RoleController,
|
|
75
|
+
_controllers.UserActionPermissionController,
|
|
76
|
+
_controllers.RolePermissionController
|
|
77
|
+
]);
|
|
78
|
+
});
|
|
79
|
+
it('defaults to FULL mode when permissionMode is omitted', ()=>{
|
|
80
|
+
const dynamicModule = _iammodule.IAMModule.forRoot({
|
|
81
|
+
includeController: true
|
|
82
|
+
});
|
|
83
|
+
expect(dynamicModule.controllers).toEqual(expect.arrayContaining([
|
|
84
|
+
_controllers.RoleController,
|
|
85
|
+
_controllers.UserActionPermissionController,
|
|
86
|
+
_controllers.RolePermissionController
|
|
87
|
+
]));
|
|
88
|
+
});
|
|
89
|
+
it('defaults to FULL mode when permissionMode is an unrecognized string', ()=>{
|
|
90
|
+
const dynamicModule = _iammodule.IAMModule.forRoot({
|
|
91
|
+
includeController: true,
|
|
92
|
+
bootstrapAppConfig: {
|
|
93
|
+
permissionMode: 'BOGUS'
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
expect(dynamicModule.controllers).toEqual(expect.arrayContaining([
|
|
97
|
+
_controllers.RoleController,
|
|
98
|
+
_controllers.UserActionPermissionController,
|
|
99
|
+
_controllers.RolePermissionController
|
|
100
|
+
]));
|
|
101
|
+
});
|
|
102
|
+
it('adds CompanyActionPermissionController only when enableCompanyFeature is true', ()=>{
|
|
103
|
+
const withCompany = _iammodule.IAMModule.forRoot({
|
|
104
|
+
includeController: true,
|
|
105
|
+
bootstrapAppConfig: {
|
|
106
|
+
enableCompanyFeature: true
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
const withoutCompany = _iammodule.IAMModule.forRoot({
|
|
110
|
+
includeController: true,
|
|
111
|
+
bootstrapAppConfig: {
|
|
112
|
+
enableCompanyFeature: false
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
expect(withCompany.controllers).toContain(_controllers.CompanyActionPermissionController);
|
|
116
|
+
expect(withoutCompany.controllers).not.toContain(_controllers.CompanyActionPermissionController);
|
|
117
|
+
});
|
|
118
|
+
it('excludes RoleService for DIRECT mode but includes it for RBAC/FULL', ()=>{
|
|
119
|
+
const direct = _iammodule.IAMModule.forRoot({
|
|
120
|
+
bootstrapAppConfig: {
|
|
121
|
+
permissionMode: 'DIRECT'
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
const rbac = _iammodule.IAMModule.forRoot({
|
|
125
|
+
bootstrapAppConfig: {
|
|
126
|
+
permissionMode: 'RBAC'
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
const full = _iammodule.IAMModule.forRoot({
|
|
130
|
+
bootstrapAppConfig: {
|
|
131
|
+
permissionMode: 'FULL'
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
expect(providerTokens(direct.providers)).not.toContain(_services.RoleService);
|
|
135
|
+
expect(providerTokens(rbac.providers)).toContain(_services.RoleService);
|
|
136
|
+
expect(providerTokens(full.providers)).toContain(_services.RoleService);
|
|
137
|
+
expect(providerTokens(direct.exports)).not.toContain(_services.RoleService);
|
|
138
|
+
expect(providerTokens(rbac.exports)).toContain(_services.RoleService);
|
|
139
|
+
});
|
|
140
|
+
it('always registers the base providers/exports (ActionService, PermissionService, PermissionCacheService)', ()=>{
|
|
141
|
+
const dynamicModule = _iammodule.IAMModule.forRoot();
|
|
142
|
+
expect(providerTokens(dynamicModule.providers)).toEqual(expect.arrayContaining([
|
|
143
|
+
_iamconfigservice.IAMConfigService,
|
|
144
|
+
_iamdatasourceservice.IAMDataSourceService,
|
|
145
|
+
_services.ActionService,
|
|
146
|
+
_services.PermissionService,
|
|
147
|
+
_permissioncacheservice.PermissionCacheService
|
|
148
|
+
]));
|
|
149
|
+
expect(dynamicModule.exports).toEqual(expect.arrayContaining([
|
|
150
|
+
_iamconfigservice.IAMConfigService,
|
|
151
|
+
_iamdatasourceservice.IAMDataSourceService,
|
|
152
|
+
_services.ActionService,
|
|
153
|
+
_services.PermissionService,
|
|
154
|
+
_permissioncacheservice.PermissionCacheService,
|
|
155
|
+
_nestjsshared.PERMISSION_GUARD_CONFIG
|
|
156
|
+
]));
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
describe('forRoot - options/global handling', ()=>{
|
|
160
|
+
it('should not set `global` on the returned module when global is false/omitted', ()=>{
|
|
161
|
+
const dynamicModule = _iammodule.IAMModule.forRoot();
|
|
162
|
+
expect(dynamicModule.global).toBeUndefined();
|
|
163
|
+
});
|
|
164
|
+
it('should set global: true when requested', ()=>{
|
|
165
|
+
const dynamicModule = _iammodule.IAMModule.forRoot({
|
|
166
|
+
global: true
|
|
167
|
+
});
|
|
168
|
+
expect(dynamicModule.global).toBe(true);
|
|
169
|
+
});
|
|
170
|
+
it('should register IAM_MODULE_OPTIONS with the given options', ()=>{
|
|
171
|
+
const options = {
|
|
172
|
+
includeController: true
|
|
173
|
+
};
|
|
174
|
+
const dynamicModule = _iammodule.IAMModule.forRoot(options);
|
|
175
|
+
const provider = dynamicModule.providers.find((p)=>p.provide === _iamconstants.IAM_MODULE_OPTIONS);
|
|
176
|
+
expect(provider.useValue).toBe(options);
|
|
177
|
+
});
|
|
178
|
+
it('should build a company-aware PERMISSION_GUARD_CONFIG provider', ()=>{
|
|
179
|
+
const dynamicModule = _iammodule.IAMModule.forRoot({
|
|
180
|
+
bootstrapAppConfig: {
|
|
181
|
+
enableCompanyFeature: true
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
const provider = dynamicModule.providers.find((p)=>p.provide === _nestjsshared.PERMISSION_GUARD_CONFIG);
|
|
185
|
+
expect(provider.useValue.enableCompanyFeature).toBe(true);
|
|
186
|
+
expect(provider.useValue.userPermissionKeyFormat).toContain('{userId}');
|
|
187
|
+
expect(provider.useValue.companyPermissionKeyFormat).toContain('{companyId}');
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
describe('forRootAsync', ()=>{
|
|
191
|
+
it('mirrors the controller/service registration matrix of forRoot', ()=>{
|
|
192
|
+
const dynamicModule = _iammodule.IAMModule.forRootAsync({
|
|
193
|
+
includeController: true,
|
|
194
|
+
bootstrapAppConfig: {
|
|
195
|
+
permissionMode: 'DIRECT'
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
expect(dynamicModule.controllers).toEqual([
|
|
199
|
+
_controllers.ActionController,
|
|
200
|
+
_controllers.MyPermissionController,
|
|
201
|
+
_controllers.UserActionPermissionController
|
|
202
|
+
]);
|
|
203
|
+
});
|
|
204
|
+
it('should forward external imports alongside CacheModule/UtilsModule', ()=>{
|
|
205
|
+
let FakeImportedModule = class FakeImportedModule {
|
|
206
|
+
};
|
|
207
|
+
const dynamicModule = _iammodule.IAMModule.forRootAsync({
|
|
208
|
+
imports: [
|
|
209
|
+
FakeImportedModule
|
|
210
|
+
]
|
|
211
|
+
});
|
|
212
|
+
expect(dynamicModule.imports).toEqual(expect.arrayContaining([
|
|
213
|
+
FakeImportedModule
|
|
214
|
+
]));
|
|
215
|
+
});
|
|
216
|
+
it('useFactory: registers a single options provider using the given inject tokens', async ()=>{
|
|
217
|
+
const useFactory = jest.fn().mockResolvedValue({
|
|
218
|
+
foo: 'bar'
|
|
219
|
+
});
|
|
220
|
+
const dynamicModule = _iammodule.IAMModule.forRootAsync({
|
|
221
|
+
useFactory,
|
|
222
|
+
inject: [
|
|
223
|
+
'SOME_TOKEN'
|
|
224
|
+
]
|
|
225
|
+
});
|
|
226
|
+
const optionsProviders = dynamicModule.providers.filter((p)=>p.provide === _iamconstants.IAM_MODULE_OPTIONS);
|
|
227
|
+
expect(optionsProviders).toHaveLength(1);
|
|
228
|
+
const provider = optionsProviders[0];
|
|
229
|
+
expect(provider.useFactory).toBe(useFactory);
|
|
230
|
+
expect(provider.inject).toEqual([
|
|
231
|
+
'SOME_TOKEN'
|
|
232
|
+
]);
|
|
233
|
+
});
|
|
234
|
+
it('useClass: registers both the class provider and an options provider that calls createIAMOptions()', async ()=>{
|
|
235
|
+
let MyOptionsFactory = class MyOptionsFactory {
|
|
236
|
+
constructor(){
|
|
237
|
+
_define_property(this, "createIAMOptions", jest.fn().mockResolvedValue({
|
|
238
|
+
foo: 'from-class'
|
|
239
|
+
}));
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
const dynamicModule = _iammodule.IAMModule.forRootAsync({
|
|
243
|
+
useClass: MyOptionsFactory
|
|
244
|
+
});
|
|
245
|
+
const classProvider = dynamicModule.providers.find((p)=>p.provide === MyOptionsFactory);
|
|
246
|
+
expect(classProvider.useClass).toBe(MyOptionsFactory);
|
|
247
|
+
const optionsProvider = dynamicModule.providers.find((p)=>p.provide === _iamconstants.IAM_MODULE_OPTIONS);
|
|
248
|
+
expect(optionsProvider.inject).toEqual([
|
|
249
|
+
MyOptionsFactory
|
|
250
|
+
]);
|
|
251
|
+
const instance = new MyOptionsFactory();
|
|
252
|
+
const resolved = await optionsProvider.useFactory(instance);
|
|
253
|
+
expect(instance.createIAMOptions).toHaveBeenCalled();
|
|
254
|
+
expect(resolved).toEqual({
|
|
255
|
+
foo: 'from-class'
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
it('useExisting: registers a single options provider injecting the existing factory', async ()=>{
|
|
259
|
+
let MyOptionsFactory = class MyOptionsFactory {
|
|
260
|
+
constructor(){
|
|
261
|
+
_define_property(this, "createIAMOptions", jest.fn().mockResolvedValue({
|
|
262
|
+
foo: 'from-existing'
|
|
263
|
+
}));
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
const dynamicModule = _iammodule.IAMModule.forRootAsync({
|
|
267
|
+
useExisting: MyOptionsFactory
|
|
268
|
+
});
|
|
269
|
+
const optionsProviders = dynamicModule.providers.filter((p)=>p.provide === _iamconstants.IAM_MODULE_OPTIONS);
|
|
270
|
+
expect(optionsProviders).toHaveLength(1);
|
|
271
|
+
const provider = optionsProviders[0];
|
|
272
|
+
expect(provider.inject).toEqual([
|
|
273
|
+
MyOptionsFactory
|
|
274
|
+
]);
|
|
275
|
+
const instance = new MyOptionsFactory();
|
|
276
|
+
const resolved = await provider.useFactory(instance);
|
|
277
|
+
expect(resolved).toEqual({
|
|
278
|
+
foo: 'from-existing'
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
describe('forFeature', ()=>{
|
|
283
|
+
it('should behave identically to forRoot', ()=>{
|
|
284
|
+
const options = {
|
|
285
|
+
includeController: true,
|
|
286
|
+
bootstrapAppConfig: {
|
|
287
|
+
permissionMode: 'RBAC'
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
expect(_iammodule.IAMModule.forFeature(options)).toEqual(_iammodule.IAMModule.forRoot(options));
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
});
|
|
@@ -85,7 +85,7 @@ let ActionService = class ActionService extends _classes.ApiService {
|
|
|
85
85
|
}
|
|
86
86
|
}) : Promise.resolve([])
|
|
87
87
|
]);
|
|
88
|
-
const affectedUserIds = new Set(userActionRows.map((row)=>row.
|
|
88
|
+
const affectedUserIds = new Set(userActionRows.map((row)=>row.sourceId));
|
|
89
89
|
const affectedCompanyIds = [
|
|
90
90
|
...new Set(companyActionRows.map((row)=>row.sourceId).filter((id)=>!!id))
|
|
91
91
|
];
|
|
@@ -100,7 +100,7 @@ let ActionService = class ActionService extends _classes.ApiService {
|
|
|
100
100
|
targetId: (0, _typeorm.In)(affectedRoleIds)
|
|
101
101
|
}
|
|
102
102
|
});
|
|
103
|
-
userRoleRows.forEach((row)=>
|
|
103
|
+
userRoleRows.forEach((row)=>affectedUserIds.add(row.sourceId));
|
|
104
104
|
}
|
|
105
105
|
await permissionRepo.delete({
|
|
106
106
|
permissionType: _iampermissiontypeenum.IamPermissionType.ROLE_ACTION,
|
|
@@ -122,7 +122,7 @@ let ActionService = class ActionService extends _classes.ApiService {
|
|
|
122
122
|
affectedUserIds.size > 0 ? this.permissionCacheService.invalidateUsers([
|
|
123
123
|
...affectedUserIds
|
|
124
124
|
]) : Promise.resolve(),
|
|
125
|
-
...affectedCompanyIds.map((companyId)=>this.
|
|
125
|
+
...affectedCompanyIds.map((companyId)=>this.permissionCacheService.invalidateCompanyMembersCache(companyId))
|
|
126
126
|
]);
|
|
127
127
|
}
|
|
128
128
|
// Query Customization
|
|
@@ -194,7 +194,7 @@ let ActionService = class ActionService extends _classes.ApiService {
|
|
|
194
194
|
let whereClause = {};
|
|
195
195
|
const targetCompanyId = companyId || user.companyId;
|
|
196
196
|
if (this.iamConfigService.isCompanyFeatureEnabled() && targetCompanyId) {
|
|
197
|
-
const companyActionIds = await this.permissionService.
|
|
197
|
+
const companyActionIds = await this.permissionService.getCompanyActions(targetCompanyId, true);
|
|
198
198
|
if (companyActionIds.length === 0) {
|
|
199
199
|
return [];
|
|
200
200
|
}
|