@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
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
const _common = require("@nestjs/common");
|
|
6
|
+
const _repositorymock = require("@test-utils/mocks/repository.mock");
|
|
7
|
+
const _userpermissionservice = require("./user-permission.service");
|
|
8
|
+
const _usercompanypermissionentity = require("../entities/user-company-permission.entity");
|
|
9
|
+
function buildPermission(overrides = {}) {
|
|
10
|
+
return {
|
|
11
|
+
id: 'perm-uuid-1',
|
|
12
|
+
userId: 'user-1',
|
|
13
|
+
permissionType: _usercompanypermissionentity.PermissionType.COMPANY,
|
|
14
|
+
targetId: 'company-1',
|
|
15
|
+
isActive: true,
|
|
16
|
+
createdAt: new Date(),
|
|
17
|
+
updatedAt: new Date(),
|
|
18
|
+
...overrides
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
describe('UserPermissionService', ()=>{
|
|
22
|
+
let service;
|
|
23
|
+
let mockRepo;
|
|
24
|
+
let mockPermissionSyncAdapter;
|
|
25
|
+
beforeEach(()=>{
|
|
26
|
+
mockRepo = (0, _repositorymock.createMockRepository)();
|
|
27
|
+
// The shared query builder mock does not include join helpers used by this service.
|
|
28
|
+
Object.assign(mockRepo.createQueryBuilder(), {
|
|
29
|
+
innerJoin: jest.fn().mockReturnThis(),
|
|
30
|
+
leftJoinAndSelect: jest.fn().mockReturnThis()
|
|
31
|
+
});
|
|
32
|
+
const mockDataSourceProvider = (0, _repositorymock.createMockDataSourceProvider)(mockRepo);
|
|
33
|
+
mockPermissionSyncAdapter = {
|
|
34
|
+
onCompanyDeleted: jest.fn(),
|
|
35
|
+
onBranchDeleted: jest.fn(),
|
|
36
|
+
onUserCompanyAccessRevoked: jest.fn(),
|
|
37
|
+
onUserBranchAccessRevoked: jest.fn()
|
|
38
|
+
};
|
|
39
|
+
service = new _userpermissionservice.UserPermissionService(mockDataSourceProvider, mockPermissionSyncAdapter);
|
|
40
|
+
});
|
|
41
|
+
describe('findUserPermissions', ()=>{
|
|
42
|
+
it('finds company permissions with the company relation', async ()=>{
|
|
43
|
+
mockRepo.find.mockResolvedValue([
|
|
44
|
+
buildPermission()
|
|
45
|
+
]);
|
|
46
|
+
const result = await service.findUserPermissions('user-1', _usercompanypermissionentity.PermissionType.COMPANY);
|
|
47
|
+
expect(result).toHaveLength(1);
|
|
48
|
+
expect(mockRepo.find).toHaveBeenCalledWith(expect.objectContaining({
|
|
49
|
+
where: {
|
|
50
|
+
userId: 'user-1',
|
|
51
|
+
permissionType: _usercompanypermissionentity.PermissionType.COMPANY
|
|
52
|
+
},
|
|
53
|
+
relations: [
|
|
54
|
+
'company'
|
|
55
|
+
]
|
|
56
|
+
}));
|
|
57
|
+
});
|
|
58
|
+
it('finds branch permissions with the branch relation', async ()=>{
|
|
59
|
+
mockRepo.find.mockResolvedValue([]);
|
|
60
|
+
await service.findUserPermissions('user-1', _usercompanypermissionentity.PermissionType.BRANCH);
|
|
61
|
+
expect(mockRepo.find).toHaveBeenCalledWith(expect.objectContaining({
|
|
62
|
+
relations: [
|
|
63
|
+
'branch'
|
|
64
|
+
]
|
|
65
|
+
}));
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
describe('assignPermission', ()=>{
|
|
69
|
+
it('creates and saves a new permission when none exists', async ()=>{
|
|
70
|
+
mockRepo.findOne.mockResolvedValue(null);
|
|
71
|
+
mockRepo.create.mockReturnValue(buildPermission());
|
|
72
|
+
mockRepo.save.mockResolvedValue(buildPermission());
|
|
73
|
+
await service.assignPermission('user-1', 'company-1', _usercompanypermissionentity.PermissionType.COMPANY);
|
|
74
|
+
expect(mockRepo.create).toHaveBeenCalledWith({
|
|
75
|
+
userId: 'user-1',
|
|
76
|
+
permissionType: _usercompanypermissionentity.PermissionType.COMPANY,
|
|
77
|
+
targetId: 'company-1'
|
|
78
|
+
});
|
|
79
|
+
expect(mockRepo.save).toHaveBeenCalled();
|
|
80
|
+
});
|
|
81
|
+
it('throws ConflictException when the permission already exists', async ()=>{
|
|
82
|
+
mockRepo.findOne.mockResolvedValue(buildPermission());
|
|
83
|
+
await expect(service.assignPermission('user-1', 'company-1', _usercompanypermissionentity.PermissionType.COMPANY)).rejects.toThrow(_common.ConflictException);
|
|
84
|
+
expect(mockRepo.save).not.toHaveBeenCalled();
|
|
85
|
+
});
|
|
86
|
+
it('translates a duplicate-entry database error into ConflictException', async ()=>{
|
|
87
|
+
mockRepo.findOne.mockResolvedValue(null);
|
|
88
|
+
mockRepo.create.mockReturnValue(buildPermission());
|
|
89
|
+
mockRepo.save.mockRejectedValue({
|
|
90
|
+
code: 'ER_DUP_ENTRY',
|
|
91
|
+
message: 'Duplicate entry'
|
|
92
|
+
});
|
|
93
|
+
await expect(service.assignPermission('user-1', 'company-1', _usercompanypermissionentity.PermissionType.COMPANY)).rejects.toThrow(_common.ConflictException);
|
|
94
|
+
});
|
|
95
|
+
it('rethrows unrelated database errors from save', async ()=>{
|
|
96
|
+
mockRepo.findOne.mockResolvedValue(null);
|
|
97
|
+
mockRepo.create.mockReturnValue(buildPermission());
|
|
98
|
+
const dbError = new Error('connection lost');
|
|
99
|
+
mockRepo.save.mockRejectedValue(dbError);
|
|
100
|
+
await expect(service.assignPermission('user-1', 'company-1', _usercompanypermissionentity.PermissionType.COMPANY)).rejects.toThrow('connection lost');
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
describe('revokePermission', ()=>{
|
|
104
|
+
it('throws NotFoundException when the permission does not exist', async ()=>{
|
|
105
|
+
mockRepo.findOne.mockResolvedValue(null);
|
|
106
|
+
await expect(service.revokePermission('user-1', 'company-1', _usercompanypermissionentity.PermissionType.COMPANY)).rejects.toThrow(_common.NotFoundException);
|
|
107
|
+
});
|
|
108
|
+
it('removes a company permission and cascades revocation to its branches', async ()=>{
|
|
109
|
+
const companyPermission = buildPermission({
|
|
110
|
+
permissionType: _usercompanypermissionentity.PermissionType.COMPANY
|
|
111
|
+
});
|
|
112
|
+
mockRepo.findOne.mockResolvedValue(companyPermission);
|
|
113
|
+
mockRepo.remove.mockResolvedValue(companyPermission);
|
|
114
|
+
const branchPermission = buildPermission({
|
|
115
|
+
id: 'perm-branch-1',
|
|
116
|
+
permissionType: _usercompanypermissionentity.PermissionType.BRANCH,
|
|
117
|
+
targetId: 'branch-1'
|
|
118
|
+
});
|
|
119
|
+
mockRepo.createQueryBuilder().getMany.mockResolvedValue([
|
|
120
|
+
branchPermission
|
|
121
|
+
]);
|
|
122
|
+
await service.revokePermission('user-1', 'company-1', _usercompanypermissionentity.PermissionType.COMPANY);
|
|
123
|
+
expect(mockRepo.remove).toHaveBeenCalledWith(companyPermission);
|
|
124
|
+
expect(mockRepo.remove).toHaveBeenCalledWith([
|
|
125
|
+
branchPermission
|
|
126
|
+
]);
|
|
127
|
+
expect(mockPermissionSyncAdapter.onUserCompanyAccessRevoked).toHaveBeenCalledWith('user-1', 'company-1');
|
|
128
|
+
expect(mockPermissionSyncAdapter.onUserBranchAccessRevoked).toHaveBeenCalledWith('user-1', 'branch-1', 'company-1');
|
|
129
|
+
});
|
|
130
|
+
it('removes a branch permission and notifies the sync adapter with its company id', async ()=>{
|
|
131
|
+
const branchPermission = buildPermission({
|
|
132
|
+
permissionType: _usercompanypermissionentity.PermissionType.BRANCH,
|
|
133
|
+
targetId: 'branch-1',
|
|
134
|
+
branch: {
|
|
135
|
+
companyId: 'company-1'
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
mockRepo.findOne.mockResolvedValue(branchPermission);
|
|
139
|
+
mockRepo.remove.mockResolvedValue(branchPermission);
|
|
140
|
+
await service.revokePermission('user-1', 'branch-1', _usercompanypermissionentity.PermissionType.BRANCH);
|
|
141
|
+
expect(mockRepo.remove).toHaveBeenCalledWith(branchPermission);
|
|
142
|
+
expect(mockPermissionSyncAdapter.onUserBranchAccessRevoked).toHaveBeenCalledWith('user-1', 'branch-1', 'company-1');
|
|
143
|
+
});
|
|
144
|
+
it('does not notify the sync adapter for a branch permission with no resolvable company', async ()=>{
|
|
145
|
+
const branchPermission = buildPermission({
|
|
146
|
+
permissionType: _usercompanypermissionentity.PermissionType.BRANCH,
|
|
147
|
+
targetId: 'branch-1',
|
|
148
|
+
branch: undefined
|
|
149
|
+
});
|
|
150
|
+
mockRepo.findOne.mockResolvedValue(branchPermission);
|
|
151
|
+
mockRepo.remove.mockResolvedValue(branchPermission);
|
|
152
|
+
await service.revokePermission('user-1', 'branch-1', _usercompanypermissionentity.PermissionType.BRANCH);
|
|
153
|
+
expect(mockPermissionSyncAdapter.onUserBranchAccessRevoked).not.toHaveBeenCalled();
|
|
154
|
+
});
|
|
155
|
+
it('works without a permission sync adapter configured', async ()=>{
|
|
156
|
+
service = new _userpermissionservice.UserPermissionService((0, _repositorymock.createMockDataSourceProvider)(mockRepo), undefined);
|
|
157
|
+
const companyPermission = buildPermission({
|
|
158
|
+
permissionType: _usercompanypermissionentity.PermissionType.COMPANY
|
|
159
|
+
});
|
|
160
|
+
mockRepo.findOne.mockResolvedValue(companyPermission);
|
|
161
|
+
mockRepo.remove.mockResolvedValue(companyPermission);
|
|
162
|
+
mockRepo.createQueryBuilder().getMany.mockResolvedValue([]);
|
|
163
|
+
await expect(service.revokePermission('user-1', 'company-1', _usercompanypermissionentity.PermissionType.COMPANY)).resolves.toBeUndefined();
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
describe('findUserCompanies', ()=>{
|
|
167
|
+
it('delegates to findUserPermissions with COMPANY type', async ()=>{
|
|
168
|
+
mockRepo.find.mockResolvedValue([
|
|
169
|
+
buildPermission()
|
|
170
|
+
]);
|
|
171
|
+
const result = await service.findUserCompanies('user-1');
|
|
172
|
+
expect(result).toHaveLength(1);
|
|
173
|
+
expect(mockRepo.find).toHaveBeenCalledWith(expect.objectContaining({
|
|
174
|
+
where: {
|
|
175
|
+
userId: 'user-1',
|
|
176
|
+
permissionType: _usercompanypermissionentity.PermissionType.COMPANY
|
|
177
|
+
}
|
|
178
|
+
}));
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
describe('findUserBranches', ()=>{
|
|
182
|
+
it('queries branch permissions for a user without a company filter', async ()=>{
|
|
183
|
+
mockRepo.createQueryBuilder().getMany.mockResolvedValue([]);
|
|
184
|
+
await service.findUserBranches('user-1');
|
|
185
|
+
const qb = mockRepo.createQueryBuilder();
|
|
186
|
+
expect(qb.where).toHaveBeenCalledWith('permission.userId = :userId', {
|
|
187
|
+
userId: 'user-1'
|
|
188
|
+
});
|
|
189
|
+
expect(qb.andWhere).toHaveBeenCalledWith('permission.permissionType = :type', {
|
|
190
|
+
type: _usercompanypermissionentity.PermissionType.BRANCH
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
it('applies an additional company filter when companyId is provided', async ()=>{
|
|
194
|
+
mockRepo.createQueryBuilder().getMany.mockResolvedValue([]);
|
|
195
|
+
await service.findUserBranches('user-1', 'company-1');
|
|
196
|
+
const qb = mockRepo.createQueryBuilder();
|
|
197
|
+
expect(qb.andWhere).toHaveBeenCalledWith('branch.companyId = :companyId', {
|
|
198
|
+
companyId: 'company-1'
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
});
|