@flusys/nestjs-auth 6.0.1 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs/controllers/authentication.controller.spec.js +219 -0
- package/cjs/controllers/branch.controller.spec.js +134 -0
- package/cjs/controllers/company-selection.controller.spec.js +84 -0
- package/cjs/controllers/company.controller.spec.js +131 -0
- package/cjs/controllers/email-verification.controller.spec.js +142 -0
- package/cjs/controllers/user-permission.controller.spec.js +138 -0
- package/cjs/controllers/user.controller.spec.js +204 -0
- package/cjs/docs/auth-swagger.config.spec.js +130 -0
- package/cjs/entities/company-branch.entity.js +2 -3
- package/cjs/entities/index.spec.js +73 -0
- package/cjs/helpers/auth-response.mapper.js +46 -0
- package/cjs/helpers/auth-response.mapper.spec.js +139 -0
- package/cjs/helpers/index.js +19 -0
- package/cjs/helpers/token.helper.js +58 -0
- package/cjs/helpers/token.helper.spec.js +86 -0
- package/cjs/index.js +1 -0
- package/cjs/interceptors/clear-token.interceptor.spec.js +107 -0
- package/cjs/interceptors/set-token.interceptor.spec.js +165 -0
- package/cjs/modules/auth.module.spec.js +243 -0
- package/cjs/services/auth-config.service.spec.js +97 -0
- package/cjs/services/auth-datasource.provider.spec.js +161 -0
- package/cjs/services/auth-email.service.spec.js +404 -0
- package/cjs/services/authentication.service.js +37 -109
- package/cjs/services/authentication.service.spec.js +904 -0
- package/cjs/services/branch.service.js +30 -3
- package/cjs/services/branch.service.spec.js +154 -0
- package/cjs/services/company-selection-session.service.spec.js +90 -0
- package/cjs/services/company.service.js +34 -3
- package/cjs/services/company.service.spec.js +113 -0
- package/cjs/services/user-permission.service.js +35 -4
- package/cjs/services/user-permission.service.spec.js +202 -0
- package/cjs/services/user.service.spec.js +704 -0
- package/cjs/strategies/jwt.strategy.js +1 -1
- package/cjs/strategies/jwt.strategy.spec.js +166 -0
- package/cjs/validators/password.validator.spec.js +161 -0
- package/entities/company-branch.entity.d.ts +1 -1
- package/entities/pending-registration.entity.d.ts +2 -7
- package/fesm/controllers/authentication.controller.spec.js +215 -0
- package/fesm/controllers/branch.controller.spec.js +130 -0
- package/fesm/controllers/company-selection.controller.spec.js +80 -0
- package/fesm/controllers/company.controller.spec.js +127 -0
- package/fesm/controllers/email-verification.controller.spec.js +138 -0
- package/fesm/controllers/user-permission.controller.spec.js +134 -0
- package/fesm/controllers/user.controller.spec.js +200 -0
- package/fesm/docs/auth-swagger.config.spec.js +126 -0
- package/fesm/entities/company-branch.entity.js +2 -3
- package/fesm/entities/index.spec.js +69 -0
- package/fesm/helpers/auth-response.mapper.js +25 -0
- package/fesm/helpers/auth-response.mapper.spec.js +135 -0
- package/fesm/helpers/index.js +2 -0
- package/fesm/helpers/token.helper.js +40 -0
- package/fesm/helpers/token.helper.spec.js +82 -0
- package/fesm/index.js +1 -0
- package/fesm/interceptors/clear-token.interceptor.spec.js +103 -0
- package/fesm/interceptors/set-token.interceptor.spec.js +161 -0
- package/fesm/interfaces/authentication.interface.js +1 -1
- package/fesm/modules/auth.module.spec.js +239 -0
- package/fesm/services/auth-config.service.spec.js +93 -0
- package/fesm/services/auth-datasource.provider.spec.js +157 -0
- package/fesm/services/auth-email.service.spec.js +359 -0
- package/fesm/services/authentication.service.js +37 -109
- package/fesm/services/authentication.service.spec.js +859 -0
- package/fesm/services/branch.service.js +32 -5
- package/fesm/services/branch.service.spec.js +150 -0
- package/fesm/services/company-selection-session.service.spec.js +86 -0
- package/fesm/services/company.service.js +36 -5
- package/fesm/services/company.service.spec.js +109 -0
- package/fesm/services/user-permission.service.js +36 -5
- package/fesm/services/user-permission.service.spec.js +198 -0
- package/fesm/services/user.service.spec.js +659 -0
- package/fesm/strategies/jwt.strategy.js +1 -1
- package/fesm/strategies/jwt.strategy.spec.js +162 -0
- package/fesm/validators/password.validator.spec.js +157 -0
- package/helpers/auth-response.mapper.d.ts +22 -0
- package/helpers/index.d.ts +2 -0
- package/helpers/token.helper.d.ts +8 -0
- package/index.d.ts +1 -0
- package/interfaces/authentication.interface.d.ts +7 -0
- package/package.json +8 -3
- package/services/authentication.service.d.ts +1 -8
- package/services/branch.service.d.ts +7 -5
- package/services/company.service.d.ts +7 -4
- package/services/user-permission.service.d.ts +4 -1
|
@@ -9,10 +9,13 @@ Object.defineProperty(exports, "BranchService", {
|
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
11
|
const _classes = require("@flusys/nestjs-shared/classes");
|
|
12
|
+
const _interfaces = require("@flusys/nestjs-shared/interfaces");
|
|
12
13
|
const _modules = require("@flusys/nestjs-shared/modules");
|
|
13
14
|
const _utils = require("@flusys/nestjs-shared/utils");
|
|
14
15
|
const _common = require("@nestjs/common");
|
|
16
|
+
const _typeorm = require("typeorm");
|
|
15
17
|
const _companybranchentity = require("../entities/company-branch.entity");
|
|
18
|
+
const _usercompanypermissionentity = require("../entities/user-company-permission.entity");
|
|
16
19
|
const _authconfigservice = require("./auth-config.service");
|
|
17
20
|
const _authdatasourceprovider = require("./auth-datasource.provider");
|
|
18
21
|
function _define_property(obj, key, value) {
|
|
@@ -43,6 +46,27 @@ function _ts_param(paramIndex, decorator) {
|
|
|
43
46
|
};
|
|
44
47
|
}
|
|
45
48
|
let BranchService = class BranchService extends _classes.ApiService {
|
|
49
|
+
async beforeDeleteOperation(dto, user, queryRunner) {
|
|
50
|
+
const branchIds = Array.isArray(dto.id) ? dto.id : [
|
|
51
|
+
dto.id
|
|
52
|
+
];
|
|
53
|
+
await queryRunner.manager.getRepository(_usercompanypermissionentity.UserCompanyPermission).delete({
|
|
54
|
+
targetId: (0, _typeorm.In)(branchIds)
|
|
55
|
+
});
|
|
56
|
+
if (!this.permissionSyncAdapter?.onBranchDeleted) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const rows = await queryRunner.manager.getRepository(_companybranchentity.CompanyBranch).find({
|
|
60
|
+
where: {
|
|
61
|
+
id: (0, _typeorm.In)(branchIds)
|
|
62
|
+
},
|
|
63
|
+
select: [
|
|
64
|
+
'id',
|
|
65
|
+
'companyId'
|
|
66
|
+
]
|
|
67
|
+
});
|
|
68
|
+
await Promise.all(rows.map((row)=>this.permissionSyncAdapter.onBranchDeleted(row.id, row.companyId)));
|
|
69
|
+
}
|
|
46
70
|
// Query Customization
|
|
47
71
|
async getSelectQuery(query, _user, select) {
|
|
48
72
|
if (!select?.length) {
|
|
@@ -84,8 +108,8 @@ let BranchService = class BranchService extends _classes.ApiService {
|
|
|
84
108
|
}
|
|
85
109
|
return result;
|
|
86
110
|
}
|
|
87
|
-
constructor(cacheManager, utilsService, dataSourceProvider, authConfig){
|
|
88
|
-
super('company_branch', cacheManager, utilsService, BranchService.name, true, 'auth', _companybranchentity.CompanyBranch, dataSourceProvider), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "authConfig", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.authConfig = authConfig;
|
|
111
|
+
constructor(cacheManager, utilsService, dataSourceProvider, authConfig, permissionSyncAdapter){
|
|
112
|
+
super('company_branch', cacheManager, utilsService, BranchService.name, true, 'auth', _companybranchentity.CompanyBranch, dataSourceProvider), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "authConfig", void 0), _define_property(this, "permissionSyncAdapter", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.authConfig = authConfig, this.permissionSyncAdapter = permissionSyncAdapter;
|
|
89
113
|
}
|
|
90
114
|
};
|
|
91
115
|
BranchService = _ts_decorate([
|
|
@@ -96,11 +120,14 @@ BranchService = _ts_decorate([
|
|
|
96
120
|
_ts_param(1, (0, _common.Inject)(_modules.UtilsService)),
|
|
97
121
|
_ts_param(2, (0, _common.Inject)(_authdatasourceprovider.AuthDataSourceProvider)),
|
|
98
122
|
_ts_param(3, (0, _common.Inject)(_authconfigservice.AuthConfigService)),
|
|
123
|
+
_ts_param(4, (0, _common.Optional)()),
|
|
124
|
+
_ts_param(4, (0, _common.Inject)(_interfaces.PERMISSION_SYNC_ADAPTER)),
|
|
99
125
|
_ts_metadata("design:type", Function),
|
|
100
126
|
_ts_metadata("design:paramtypes", [
|
|
101
127
|
typeof _classes.HybridCache === "undefined" ? Object : _classes.HybridCache,
|
|
102
128
|
typeof _modules.UtilsService === "undefined" ? Object : _modules.UtilsService,
|
|
103
129
|
typeof _authdatasourceprovider.AuthDataSourceProvider === "undefined" ? Object : _authdatasourceprovider.AuthDataSourceProvider,
|
|
104
|
-
typeof _authconfigservice.AuthConfigService === "undefined" ? Object : _authconfigservice.AuthConfigService
|
|
130
|
+
typeof _authconfigservice.AuthConfigService === "undefined" ? Object : _authconfigservice.AuthConfigService,
|
|
131
|
+
typeof _interfaces.IPermissionSyncAdapter === "undefined" ? Object : _interfaces.IPermissionSyncAdapter
|
|
105
132
|
])
|
|
106
133
|
], BranchService);
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
const _repositorymock = require("@test-utils/mocks/repository.mock");
|
|
6
|
+
const _branchservice = require("./branch.service");
|
|
7
|
+
const _companybranchentity = require("../entities/company-branch.entity");
|
|
8
|
+
const _usercompanypermissionentity = require("../entities/user-company-permission.entity");
|
|
9
|
+
describe('BranchService', ()=>{
|
|
10
|
+
let service;
|
|
11
|
+
let mockRepo;
|
|
12
|
+
let mockAuthConfig;
|
|
13
|
+
let mockPermissionSyncAdapter;
|
|
14
|
+
let mockQueryRunner;
|
|
15
|
+
beforeEach(()=>{
|
|
16
|
+
mockRepo = (0, _repositorymock.createMockRepository)();
|
|
17
|
+
Object.assign(mockRepo.createQueryBuilder(), {
|
|
18
|
+
leftJoinAndSelect: jest.fn().mockReturnThis(),
|
|
19
|
+
addOrderBy: jest.fn().mockReturnThis()
|
|
20
|
+
});
|
|
21
|
+
const mockDataSourceProvider = (0, _repositorymock.createMockDataSourceProvider)(mockRepo);
|
|
22
|
+
mockAuthConfig = {
|
|
23
|
+
isCompanyFeatureEnabled: jest.fn().mockReturnValue(false)
|
|
24
|
+
};
|
|
25
|
+
mockPermissionSyncAdapter = {
|
|
26
|
+
onBranchDeleted: jest.fn()
|
|
27
|
+
};
|
|
28
|
+
service = new _branchservice.BranchService({}, {}, mockDataSourceProvider, mockAuthConfig, mockPermissionSyncAdapter);
|
|
29
|
+
const permissionRepo = (0, _repositorymock.createMockRepository)();
|
|
30
|
+
const branchRepo = (0, _repositorymock.createMockRepository)();
|
|
31
|
+
branchRepo.find.mockResolvedValue([]);
|
|
32
|
+
mockQueryRunner = {
|
|
33
|
+
manager: {
|
|
34
|
+
getRepository: jest.fn().mockImplementation((entity)=>{
|
|
35
|
+
if (entity === _usercompanypermissionentity.UserCompanyPermission) return permissionRepo;
|
|
36
|
+
if (entity === _companybranchentity.CompanyBranch) return branchRepo;
|
|
37
|
+
throw new Error('Unexpected entity');
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
describe('beforeDeleteOperation', ()=>{
|
|
43
|
+
it('deletes user permissions targeting the deleted branch ids', async ()=>{
|
|
44
|
+
await service.beforeDeleteOperation({
|
|
45
|
+
id: [
|
|
46
|
+
'branch-1',
|
|
47
|
+
'branch-2'
|
|
48
|
+
]
|
|
49
|
+
}, null, mockQueryRunner);
|
|
50
|
+
const permissionRepo = mockQueryRunner.manager.getRepository(_usercompanypermissionentity.UserCompanyPermission);
|
|
51
|
+
expect(permissionRepo.delete).toHaveBeenCalledWith({
|
|
52
|
+
targetId: expect.anything()
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
it('handles a single (non-array) branch id', async ()=>{
|
|
56
|
+
await service.beforeDeleteOperation({
|
|
57
|
+
id: 'branch-1'
|
|
58
|
+
}, null, mockQueryRunner);
|
|
59
|
+
const permissionRepo = mockQueryRunner.manager.getRepository(_usercompanypermissionentity.UserCompanyPermission);
|
|
60
|
+
expect(permissionRepo.delete).toHaveBeenCalled();
|
|
61
|
+
});
|
|
62
|
+
it('does nothing further when no permission sync adapter is configured', async ()=>{
|
|
63
|
+
service = new _branchservice.BranchService({}, {}, (0, _repositorymock.createMockDataSourceProvider)(mockRepo), mockAuthConfig, undefined);
|
|
64
|
+
await expect(service.beforeDeleteOperation({
|
|
65
|
+
id: [
|
|
66
|
+
'branch-1'
|
|
67
|
+
]
|
|
68
|
+
}, null, mockQueryRunner)).resolves.toBeUndefined();
|
|
69
|
+
});
|
|
70
|
+
it('notifies the permission sync adapter for each deleted branch', async ()=>{
|
|
71
|
+
const branchRepo = mockQueryRunner.manager.getRepository(_companybranchentity.CompanyBranch);
|
|
72
|
+
branchRepo.find.mockResolvedValue([
|
|
73
|
+
{
|
|
74
|
+
id: 'branch-1',
|
|
75
|
+
companyId: 'company-1'
|
|
76
|
+
}
|
|
77
|
+
]);
|
|
78
|
+
await service.beforeDeleteOperation({
|
|
79
|
+
id: [
|
|
80
|
+
'branch-1'
|
|
81
|
+
]
|
|
82
|
+
}, null, mockQueryRunner);
|
|
83
|
+
expect(mockPermissionSyncAdapter.onBranchDeleted).toHaveBeenCalledWith('branch-1', 'company-1');
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
describe('getSelectQuery', ()=>{
|
|
87
|
+
it('applies the default selected fields when none are provided', async ()=>{
|
|
88
|
+
const qb = mockRepo.createQueryBuilder();
|
|
89
|
+
const { isRaw } = await service.getSelectQuery(qb, null);
|
|
90
|
+
expect(qb.select).toHaveBeenCalledWith(expect.arrayContaining([
|
|
91
|
+
'company_branch.id',
|
|
92
|
+
'company_branch.name',
|
|
93
|
+
'company_branch.slug'
|
|
94
|
+
]));
|
|
95
|
+
expect(isRaw).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
it('honors an explicit select list', async ()=>{
|
|
98
|
+
const qb = mockRepo.createQueryBuilder();
|
|
99
|
+
await service.getSelectQuery(qb, null, [
|
|
100
|
+
'id',
|
|
101
|
+
'name'
|
|
102
|
+
]);
|
|
103
|
+
expect(qb.select).toHaveBeenCalledWith([
|
|
104
|
+
'company_branch.id',
|
|
105
|
+
'company_branch.name'
|
|
106
|
+
]);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
describe('getGlobalSearchQuery', ()=>{
|
|
110
|
+
it('adds a LIKE search clause across name/slug/email', async ()=>{
|
|
111
|
+
const qb = mockRepo.createQueryBuilder();
|
|
112
|
+
await service.getGlobalSearchQuery(qb, 'acme');
|
|
113
|
+
expect(qb.andWhere).toHaveBeenCalledWith(expect.stringContaining('company_branch.name LIKE'), {
|
|
114
|
+
search: '%acme%'
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
describe('getExtraManipulateQuery', ()=>{
|
|
119
|
+
it('applies a company filter when company feature is enabled and no explicit companyId filter is present', async ()=>{
|
|
120
|
+
mockAuthConfig.isCompanyFeatureEnabled.mockReturnValue(true);
|
|
121
|
+
const qb = mockRepo.createQueryBuilder();
|
|
122
|
+
await service.getExtraManipulateQuery(qb, {
|
|
123
|
+
filter: {}
|
|
124
|
+
}, {
|
|
125
|
+
id: 'user-1',
|
|
126
|
+
companyId: 'company-1'
|
|
127
|
+
});
|
|
128
|
+
expect(qb.andWhere).toHaveBeenCalledWith('company_branch.company_id = :companyId', {
|
|
129
|
+
companyId: 'company-1'
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
it('does not apply a company filter when companyId is explicitly provided in the filter', async ()=>{
|
|
133
|
+
mockAuthConfig.isCompanyFeatureEnabled.mockReturnValue(true);
|
|
134
|
+
const qb = mockRepo.createQueryBuilder();
|
|
135
|
+
await service.getExtraManipulateQuery(qb, {
|
|
136
|
+
filter: {
|
|
137
|
+
companyId: 'explicit-company'
|
|
138
|
+
}
|
|
139
|
+
}, {
|
|
140
|
+
id: 'user-1',
|
|
141
|
+
companyId: 'company-1'
|
|
142
|
+
});
|
|
143
|
+
expect(qb.andWhere).not.toHaveBeenCalledWith('company_branch.company_id = :companyId', expect.anything());
|
|
144
|
+
});
|
|
145
|
+
it('does not apply a company filter when the company feature is disabled', async ()=>{
|
|
146
|
+
mockAuthConfig.isCompanyFeatureEnabled.mockReturnValue(false);
|
|
147
|
+
const qb = mockRepo.createQueryBuilder();
|
|
148
|
+
await service.getExtraManipulateQuery(qb, {
|
|
149
|
+
filter: {}
|
|
150
|
+
}, null);
|
|
151
|
+
expect(qb.andWhere).not.toHaveBeenCalledWith('company_branch.company_id = :companyId', expect.anything());
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
const _companyselectionsessionservice = require("./company-selection-session.service");
|
|
6
|
+
describe('CompanySelectionSessionService', ()=>{
|
|
7
|
+
let service;
|
|
8
|
+
let mockCacheManager;
|
|
9
|
+
beforeEach(()=>{
|
|
10
|
+
mockCacheManager = {
|
|
11
|
+
set: jest.fn(),
|
|
12
|
+
get: jest.fn(),
|
|
13
|
+
del: jest.fn()
|
|
14
|
+
};
|
|
15
|
+
service = new _companyselectionsessionservice.CompanySelectionSessionService(mockCacheManager);
|
|
16
|
+
});
|
|
17
|
+
describe('createSession', ()=>{
|
|
18
|
+
it('stores a session in the cache and returns a session id', async ()=>{
|
|
19
|
+
const sessionId = await service.createSession('user-1');
|
|
20
|
+
expect(typeof sessionId).toBe('string');
|
|
21
|
+
expect(sessionId.length).toBeGreaterThan(0);
|
|
22
|
+
expect(mockCacheManager.set).toHaveBeenCalledWith(`auth:session:${sessionId}`, expect.objectContaining({
|
|
23
|
+
sessionId,
|
|
24
|
+
userId: 'user-1'
|
|
25
|
+
}), 5 * 60 * 1000);
|
|
26
|
+
});
|
|
27
|
+
it('generates unique session ids on successive calls', async ()=>{
|
|
28
|
+
const first = await service.createSession('user-1');
|
|
29
|
+
const second = await service.createSession('user-1');
|
|
30
|
+
expect(first).not.toBe(second);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
describe('getSession', ()=>{
|
|
34
|
+
it('returns null when no session is cached', async ()=>{
|
|
35
|
+
mockCacheManager.get.mockResolvedValue(undefined);
|
|
36
|
+
const result = await service.getSession('missing-session');
|
|
37
|
+
expect(result).toBeNull();
|
|
38
|
+
});
|
|
39
|
+
it('returns the session when present and not expired', async ()=>{
|
|
40
|
+
const session = {
|
|
41
|
+
sessionId: 'sess-1',
|
|
42
|
+
userId: 'user-1',
|
|
43
|
+
createdAt: new Date(),
|
|
44
|
+
expiresAt: new Date(Date.now() + 60_000)
|
|
45
|
+
};
|
|
46
|
+
mockCacheManager.get.mockResolvedValue(session);
|
|
47
|
+
const result = await service.getSession('sess-1');
|
|
48
|
+
expect(result).toEqual(session);
|
|
49
|
+
expect(mockCacheManager.get).toHaveBeenCalledWith('auth:session:sess-1');
|
|
50
|
+
});
|
|
51
|
+
it('deletes and returns null when the cached session has expired', async ()=>{
|
|
52
|
+
const session = {
|
|
53
|
+
sessionId: 'sess-1',
|
|
54
|
+
userId: 'user-1',
|
|
55
|
+
createdAt: new Date(Date.now() - 10 * 60_000),
|
|
56
|
+
expiresAt: new Date(Date.now() - 60_000)
|
|
57
|
+
};
|
|
58
|
+
mockCacheManager.get.mockResolvedValue(session);
|
|
59
|
+
const result = await service.getSession('sess-1');
|
|
60
|
+
expect(result).toBeNull();
|
|
61
|
+
expect(mockCacheManager.del).toHaveBeenCalledWith('auth:session:sess-1');
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
describe('consumeSession', ()=>{
|
|
65
|
+
it('returns and deletes the session when present', async ()=>{
|
|
66
|
+
const session = {
|
|
67
|
+
sessionId: 'sess-1',
|
|
68
|
+
userId: 'user-1',
|
|
69
|
+
createdAt: new Date(),
|
|
70
|
+
expiresAt: new Date(Date.now() + 60_000)
|
|
71
|
+
};
|
|
72
|
+
mockCacheManager.get.mockResolvedValue(session);
|
|
73
|
+
const result = await service.consumeSession('sess-1');
|
|
74
|
+
expect(result).toEqual(session);
|
|
75
|
+
expect(mockCacheManager.del).toHaveBeenCalledWith('auth:session:sess-1');
|
|
76
|
+
});
|
|
77
|
+
it('returns null and does not delete when session is missing', async ()=>{
|
|
78
|
+
mockCacheManager.get.mockResolvedValue(undefined);
|
|
79
|
+
const result = await service.consumeSession('missing');
|
|
80
|
+
expect(result).toBeNull();
|
|
81
|
+
expect(mockCacheManager.del).not.toHaveBeenCalled();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
describe('deleteSession', ()=>{
|
|
85
|
+
it('deletes the session by key', async ()=>{
|
|
86
|
+
await service.deleteSession('sess-1');
|
|
87
|
+
expect(mockCacheManager.del).toHaveBeenCalledWith('auth:session:sess-1');
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -9,9 +9,13 @@ Object.defineProperty(exports, "CompanyService", {
|
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
11
|
const _classes = require("@flusys/nestjs-shared/classes");
|
|
12
|
+
const _interfaces = require("@flusys/nestjs-shared/interfaces");
|
|
12
13
|
const _modules = require("@flusys/nestjs-shared/modules");
|
|
13
14
|
const _common = require("@nestjs/common");
|
|
15
|
+
const _typeorm = require("typeorm");
|
|
16
|
+
const _companybranchentity = require("../entities/company-branch.entity");
|
|
14
17
|
const _companyentity = require("../entities/company.entity");
|
|
18
|
+
const _usercompanypermissionentity = require("../entities/user-company-permission.entity");
|
|
15
19
|
const _authdatasourceprovider = require("./auth-datasource.provider");
|
|
16
20
|
function _define_property(obj, key, value) {
|
|
17
21
|
if (key in obj) {
|
|
@@ -41,6 +45,30 @@ function _ts_param(paramIndex, decorator) {
|
|
|
41
45
|
};
|
|
42
46
|
}
|
|
43
47
|
let CompanyService = class CompanyService extends _classes.ApiService {
|
|
48
|
+
async beforeDeleteOperation(dto, user, queryRunner) {
|
|
49
|
+
const companyIds = Array.isArray(dto.id) ? dto.id : [
|
|
50
|
+
dto.id
|
|
51
|
+
];
|
|
52
|
+
const branchRows = await queryRunner.manager.getRepository(_companybranchentity.CompanyBranch).find({
|
|
53
|
+
where: {
|
|
54
|
+
companyId: (0, _typeorm.In)(companyIds)
|
|
55
|
+
},
|
|
56
|
+
select: [
|
|
57
|
+
'id'
|
|
58
|
+
]
|
|
59
|
+
});
|
|
60
|
+
const targetIds = [
|
|
61
|
+
...companyIds,
|
|
62
|
+
...branchRows.map((row)=>row.id)
|
|
63
|
+
];
|
|
64
|
+
await queryRunner.manager.getRepository(_usercompanypermissionentity.UserCompanyPermission).delete({
|
|
65
|
+
targetId: (0, _typeorm.In)(targetIds)
|
|
66
|
+
});
|
|
67
|
+
if (!this.permissionSyncAdapter?.onCompanyDeleted) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
await Promise.all(companyIds.map((companyId)=>this.permissionSyncAdapter.onCompanyDeleted(companyId)));
|
|
71
|
+
}
|
|
44
72
|
// Query Customization
|
|
45
73
|
async getSelectQuery(query, _user, select) {
|
|
46
74
|
if (!select?.length) {
|
|
@@ -69,8 +97,8 @@ let CompanyService = class CompanyService extends _classes.ApiService {
|
|
|
69
97
|
isRaw: false
|
|
70
98
|
};
|
|
71
99
|
}
|
|
72
|
-
constructor(cacheManager, utilsService, dataSourceProvider){
|
|
73
|
-
super('company', cacheManager, utilsService, CompanyService.name, true, 'auth', _companyentity.Company, dataSourceProvider), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService;
|
|
100
|
+
constructor(cacheManager, utilsService, dataSourceProvider, permissionSyncAdapter){
|
|
101
|
+
super('company', cacheManager, utilsService, CompanyService.name, true, 'auth', _companyentity.Company, dataSourceProvider), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "permissionSyncAdapter", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.permissionSyncAdapter = permissionSyncAdapter;
|
|
74
102
|
}
|
|
75
103
|
};
|
|
76
104
|
CompanyService = _ts_decorate([
|
|
@@ -80,10 +108,13 @@ CompanyService = _ts_decorate([
|
|
|
80
108
|
_ts_param(0, (0, _common.Inject)('CACHE_INSTANCE')),
|
|
81
109
|
_ts_param(1, (0, _common.Inject)(_modules.UtilsService)),
|
|
82
110
|
_ts_param(2, (0, _common.Inject)(_authdatasourceprovider.AuthDataSourceProvider)),
|
|
111
|
+
_ts_param(3, (0, _common.Optional)()),
|
|
112
|
+
_ts_param(3, (0, _common.Inject)(_interfaces.PERMISSION_SYNC_ADAPTER)),
|
|
83
113
|
_ts_metadata("design:type", Function),
|
|
84
114
|
_ts_metadata("design:paramtypes", [
|
|
85
115
|
typeof _classes.HybridCache === "undefined" ? Object : _classes.HybridCache,
|
|
86
116
|
typeof _modules.UtilsService === "undefined" ? Object : _modules.UtilsService,
|
|
87
|
-
typeof _authdatasourceprovider.AuthDataSourceProvider === "undefined" ? Object : _authdatasourceprovider.AuthDataSourceProvider
|
|
117
|
+
typeof _authdatasourceprovider.AuthDataSourceProvider === "undefined" ? Object : _authdatasourceprovider.AuthDataSourceProvider,
|
|
118
|
+
typeof _interfaces.IPermissionSyncAdapter === "undefined" ? Object : _interfaces.IPermissionSyncAdapter
|
|
88
119
|
])
|
|
89
120
|
], CompanyService);
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
const _repositorymock = require("@test-utils/mocks/repository.mock");
|
|
6
|
+
const _companyservice = require("./company.service");
|
|
7
|
+
const _companybranchentity = require("../entities/company-branch.entity");
|
|
8
|
+
const _usercompanypermissionentity = require("../entities/user-company-permission.entity");
|
|
9
|
+
describe('CompanyService', ()=>{
|
|
10
|
+
let service;
|
|
11
|
+
let mockRepo;
|
|
12
|
+
let mockPermissionSyncAdapter;
|
|
13
|
+
let mockQueryRunner;
|
|
14
|
+
let branchRepo;
|
|
15
|
+
let permissionRepo;
|
|
16
|
+
beforeEach(()=>{
|
|
17
|
+
mockRepo = (0, _repositorymock.createMockRepository)();
|
|
18
|
+
const mockDataSourceProvider = (0, _repositorymock.createMockDataSourceProvider)(mockRepo);
|
|
19
|
+
mockPermissionSyncAdapter = {
|
|
20
|
+
onCompanyDeleted: jest.fn()
|
|
21
|
+
};
|
|
22
|
+
service = new _companyservice.CompanyService({}, {}, mockDataSourceProvider, mockPermissionSyncAdapter);
|
|
23
|
+
branchRepo = (0, _repositorymock.createMockRepository)();
|
|
24
|
+
branchRepo.find.mockResolvedValue([]);
|
|
25
|
+
permissionRepo = (0, _repositorymock.createMockRepository)();
|
|
26
|
+
mockQueryRunner = {
|
|
27
|
+
manager: {
|
|
28
|
+
getRepository: jest.fn().mockImplementation((entity)=>{
|
|
29
|
+
if (entity === _companybranchentity.CompanyBranch) return branchRepo;
|
|
30
|
+
if (entity === _usercompanypermissionentity.UserCompanyPermission) return permissionRepo;
|
|
31
|
+
throw new Error('Unexpected entity');
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
describe('beforeDeleteOperation', ()=>{
|
|
37
|
+
it('deletes permissions for the companies and their branches', async ()=>{
|
|
38
|
+
branchRepo.find.mockResolvedValue([
|
|
39
|
+
{
|
|
40
|
+
id: 'branch-1'
|
|
41
|
+
}
|
|
42
|
+
]);
|
|
43
|
+
await service.beforeDeleteOperation({
|
|
44
|
+
id: [
|
|
45
|
+
'company-1'
|
|
46
|
+
]
|
|
47
|
+
}, null, mockQueryRunner);
|
|
48
|
+
const deleteArg = permissionRepo.delete.mock.calls[0][0];
|
|
49
|
+
expect(deleteArg.targetId._value).toEqual(expect.arrayContaining([
|
|
50
|
+
'company-1',
|
|
51
|
+
'branch-1'
|
|
52
|
+
]));
|
|
53
|
+
});
|
|
54
|
+
it('handles a single (non-array) company id', async ()=>{
|
|
55
|
+
await service.beforeDeleteOperation({
|
|
56
|
+
id: 'company-1'
|
|
57
|
+
}, null, mockQueryRunner);
|
|
58
|
+
expect(branchRepo.find).toHaveBeenCalledWith(expect.objectContaining({
|
|
59
|
+
where: {
|
|
60
|
+
companyId: expect.anything()
|
|
61
|
+
}
|
|
62
|
+
}));
|
|
63
|
+
});
|
|
64
|
+
it('does nothing further when no permission sync adapter is configured', async ()=>{
|
|
65
|
+
service = new _companyservice.CompanyService({}, {}, (0, _repositorymock.createMockDataSourceProvider)(mockRepo), undefined);
|
|
66
|
+
await expect(service.beforeDeleteOperation({
|
|
67
|
+
id: [
|
|
68
|
+
'company-1'
|
|
69
|
+
]
|
|
70
|
+
}, null, mockQueryRunner)).resolves.toBeUndefined();
|
|
71
|
+
});
|
|
72
|
+
it('notifies the permission sync adapter for each deleted company', async ()=>{
|
|
73
|
+
await service.beforeDeleteOperation({
|
|
74
|
+
id: [
|
|
75
|
+
'company-1',
|
|
76
|
+
'company-2'
|
|
77
|
+
]
|
|
78
|
+
}, null, mockQueryRunner);
|
|
79
|
+
expect(mockPermissionSyncAdapter.onCompanyDeleted).toHaveBeenCalledWith('company-1');
|
|
80
|
+
expect(mockPermissionSyncAdapter.onCompanyDeleted).toHaveBeenCalledWith('company-2');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
describe('getSelectQuery', ()=>{
|
|
84
|
+
it('applies the default selected fields when none are provided', async ()=>{
|
|
85
|
+
const qb = mockRepo.createQueryBuilder();
|
|
86
|
+
const { isRaw } = await service.getSelectQuery(qb, null);
|
|
87
|
+
expect(qb.select).toHaveBeenCalledWith(expect.arrayContaining([
|
|
88
|
+
'company.id',
|
|
89
|
+
'company.name',
|
|
90
|
+
'company.slug'
|
|
91
|
+
]));
|
|
92
|
+
expect(isRaw).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
it('honors an explicit select list', async ()=>{
|
|
95
|
+
const qb = mockRepo.createQueryBuilder();
|
|
96
|
+
await service.getSelectQuery(qb, null, [
|
|
97
|
+
'id'
|
|
98
|
+
]);
|
|
99
|
+
expect(qb.select).toHaveBeenCalledWith([
|
|
100
|
+
'company.id'
|
|
101
|
+
]);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
describe('getGlobalSearchQuery', ()=>{
|
|
105
|
+
it('adds a LIKE search clause across name/slug/email', async ()=>{
|
|
106
|
+
const qb = mockRepo.createQueryBuilder();
|
|
107
|
+
await service.getGlobalSearchQuery(qb, 'acme');
|
|
108
|
+
expect(qb.andWhere).toHaveBeenCalledWith(expect.stringContaining('company.name LIKE'), {
|
|
109
|
+
search: '%acme%'
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -9,6 +9,7 @@ Object.defineProperty(exports, "UserPermissionService", {
|
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
11
|
const _nestjsshared = require("@flusys/nestjs-shared");
|
|
12
|
+
const _interfaces = require("@flusys/nestjs-shared/interfaces");
|
|
12
13
|
const _common = require("@nestjs/common");
|
|
13
14
|
const _config = require("../config");
|
|
14
15
|
const _usercompanypermissionentity = require("../entities/user-company-permission.entity");
|
|
@@ -102,7 +103,7 @@ let UserPermissionService = class UserPermissionService {
|
|
|
102
103
|
throw error;
|
|
103
104
|
}
|
|
104
105
|
}
|
|
105
|
-
|
|
106
|
+
async revokePermission(userId, targetId, type) {
|
|
106
107
|
const repository = await this.getRepository();
|
|
107
108
|
const typeName = type === _usercompanypermissionentity.PermissionType.COMPANY ? 'company' : 'branch';
|
|
108
109
|
const permission = await repository.findOne({
|
|
@@ -110,7 +111,10 @@ let UserPermissionService = class UserPermissionService {
|
|
|
110
111
|
userId,
|
|
111
112
|
targetId,
|
|
112
113
|
permissionType: type
|
|
113
|
-
}
|
|
114
|
+
},
|
|
115
|
+
relations: [
|
|
116
|
+
type === _usercompanypermissionentity.PermissionType.BRANCH ? 'branch' : 'company'
|
|
117
|
+
]
|
|
114
118
|
});
|
|
115
119
|
if (!permission) {
|
|
116
120
|
throw new _common.NotFoundException({
|
|
@@ -121,7 +125,29 @@ let UserPermissionService = class UserPermissionService {
|
|
|
121
125
|
}
|
|
122
126
|
});
|
|
123
127
|
}
|
|
128
|
+
const companyId = type === _usercompanypermissionentity.PermissionType.COMPANY ? targetId : permission.branch?.companyId;
|
|
124
129
|
await repository.remove(permission);
|
|
130
|
+
if (type === _usercompanypermissionentity.PermissionType.COMPANY) {
|
|
131
|
+
const revokedBranchIds = await this.revokeBranchPermissionsForCompany(repository, userId, targetId);
|
|
132
|
+
await this.permissionSyncAdapter?.onUserCompanyAccessRevoked?.(userId, targetId);
|
|
133
|
+
await Promise.all(revokedBranchIds.map((branchId)=>this.permissionSyncAdapter?.onUserBranchAccessRevoked?.(userId, branchId, targetId)));
|
|
134
|
+
} else {
|
|
135
|
+
if (companyId) await this.permissionSyncAdapter?.onUserBranchAccessRevoked?.(userId, targetId, companyId);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async revokeBranchPermissionsForCompany(repository, userId, companyId) {
|
|
139
|
+
const branchPermissions = await repository.createQueryBuilder('permission').innerJoin('permission.branch', 'branch').where('permission.userId = :userId', {
|
|
140
|
+
userId
|
|
141
|
+
}).andWhere('permission.permissionType = :type', {
|
|
142
|
+
type: _usercompanypermissionentity.PermissionType.BRANCH
|
|
143
|
+
}).andWhere('branch.companyId = :companyId', {
|
|
144
|
+
companyId
|
|
145
|
+
}).getMany();
|
|
146
|
+
if (branchPermissions.length === 0) {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
await repository.remove(branchPermissions);
|
|
150
|
+
return branchPermissions.map((permission)=>permission.targetId);
|
|
125
151
|
}
|
|
126
152
|
/** Find user branch permissions with optional company filter */ async findUserBranches(userId, companyId) {
|
|
127
153
|
const repository = await this.getRepository();
|
|
@@ -137,12 +163,14 @@ let UserPermissionService = class UserPermissionService {
|
|
|
137
163
|
}
|
|
138
164
|
return queryBuilder.getMany();
|
|
139
165
|
}
|
|
140
|
-
constructor(dataSourceProvider){
|
|
166
|
+
constructor(dataSourceProvider, permissionSyncAdapter){
|
|
141
167
|
_define_property(this, "dataSourceProvider", void 0);
|
|
168
|
+
_define_property(this, "permissionSyncAdapter", void 0);
|
|
142
169
|
_define_property(this, "_repository", void 0);
|
|
143
170
|
// Convenience Methods
|
|
144
171
|
_define_property(this, "findUserCompanies", void 0);
|
|
145
172
|
this.dataSourceProvider = dataSourceProvider;
|
|
173
|
+
this.permissionSyncAdapter = permissionSyncAdapter;
|
|
146
174
|
this._repository = null;
|
|
147
175
|
this.findUserCompanies = (userId)=>this.findUserPermissions(userId, _usercompanypermissionentity.PermissionType.COMPANY);
|
|
148
176
|
}
|
|
@@ -178,8 +206,11 @@ UserPermissionService = _ts_decorate([
|
|
|
178
206
|
scope: _common.Scope.REQUEST
|
|
179
207
|
}),
|
|
180
208
|
_ts_param(0, (0, _common.Inject)(_authdatasourceprovider.AuthDataSourceProvider)),
|
|
209
|
+
_ts_param(1, (0, _common.Optional)()),
|
|
210
|
+
_ts_param(1, (0, _common.Inject)(_interfaces.PERMISSION_SYNC_ADAPTER)),
|
|
181
211
|
_ts_metadata("design:type", Function),
|
|
182
212
|
_ts_metadata("design:paramtypes", [
|
|
183
|
-
typeof _authdatasourceprovider.AuthDataSourceProvider === "undefined" ? Object : _authdatasourceprovider.AuthDataSourceProvider
|
|
213
|
+
typeof _authdatasourceprovider.AuthDataSourceProvider === "undefined" ? Object : _authdatasourceprovider.AuthDataSourceProvider,
|
|
214
|
+
typeof _interfaces.IPermissionSyncAdapter === "undefined" ? Object : _interfaces.IPermissionSyncAdapter
|
|
184
215
|
])
|
|
185
216
|
], UserPermissionService);
|