@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.
Files changed (83) hide show
  1. package/cjs/controllers/authentication.controller.spec.js +219 -0
  2. package/cjs/controllers/branch.controller.spec.js +134 -0
  3. package/cjs/controllers/company-selection.controller.spec.js +84 -0
  4. package/cjs/controllers/company.controller.spec.js +131 -0
  5. package/cjs/controllers/email-verification.controller.spec.js +142 -0
  6. package/cjs/controllers/user-permission.controller.spec.js +138 -0
  7. package/cjs/controllers/user.controller.spec.js +204 -0
  8. package/cjs/docs/auth-swagger.config.spec.js +130 -0
  9. package/cjs/entities/company-branch.entity.js +2 -3
  10. package/cjs/entities/index.spec.js +73 -0
  11. package/cjs/helpers/auth-response.mapper.js +46 -0
  12. package/cjs/helpers/auth-response.mapper.spec.js +139 -0
  13. package/cjs/helpers/index.js +19 -0
  14. package/cjs/helpers/token.helper.js +58 -0
  15. package/cjs/helpers/token.helper.spec.js +86 -0
  16. package/cjs/index.js +1 -0
  17. package/cjs/interceptors/clear-token.interceptor.spec.js +107 -0
  18. package/cjs/interceptors/set-token.interceptor.spec.js +165 -0
  19. package/cjs/modules/auth.module.spec.js +243 -0
  20. package/cjs/services/auth-config.service.spec.js +97 -0
  21. package/cjs/services/auth-datasource.provider.spec.js +161 -0
  22. package/cjs/services/auth-email.service.spec.js +404 -0
  23. package/cjs/services/authentication.service.js +37 -109
  24. package/cjs/services/authentication.service.spec.js +904 -0
  25. package/cjs/services/branch.service.js +30 -3
  26. package/cjs/services/branch.service.spec.js +154 -0
  27. package/cjs/services/company-selection-session.service.spec.js +90 -0
  28. package/cjs/services/company.service.js +34 -3
  29. package/cjs/services/company.service.spec.js +113 -0
  30. package/cjs/services/user-permission.service.js +35 -4
  31. package/cjs/services/user-permission.service.spec.js +202 -0
  32. package/cjs/services/user.service.spec.js +704 -0
  33. package/cjs/strategies/jwt.strategy.js +1 -1
  34. package/cjs/strategies/jwt.strategy.spec.js +166 -0
  35. package/cjs/validators/password.validator.spec.js +161 -0
  36. package/entities/company-branch.entity.d.ts +1 -1
  37. package/entities/pending-registration.entity.d.ts +2 -7
  38. package/fesm/controllers/authentication.controller.spec.js +215 -0
  39. package/fesm/controllers/branch.controller.spec.js +130 -0
  40. package/fesm/controllers/company-selection.controller.spec.js +80 -0
  41. package/fesm/controllers/company.controller.spec.js +127 -0
  42. package/fesm/controllers/email-verification.controller.spec.js +138 -0
  43. package/fesm/controllers/user-permission.controller.spec.js +134 -0
  44. package/fesm/controllers/user.controller.spec.js +200 -0
  45. package/fesm/docs/auth-swagger.config.spec.js +126 -0
  46. package/fesm/entities/company-branch.entity.js +2 -3
  47. package/fesm/entities/index.spec.js +69 -0
  48. package/fesm/helpers/auth-response.mapper.js +25 -0
  49. package/fesm/helpers/auth-response.mapper.spec.js +135 -0
  50. package/fesm/helpers/index.js +2 -0
  51. package/fesm/helpers/token.helper.js +40 -0
  52. package/fesm/helpers/token.helper.spec.js +82 -0
  53. package/fesm/index.js +1 -0
  54. package/fesm/interceptors/clear-token.interceptor.spec.js +103 -0
  55. package/fesm/interceptors/set-token.interceptor.spec.js +161 -0
  56. package/fesm/interfaces/authentication.interface.js +1 -1
  57. package/fesm/modules/auth.module.spec.js +239 -0
  58. package/fesm/services/auth-config.service.spec.js +93 -0
  59. package/fesm/services/auth-datasource.provider.spec.js +157 -0
  60. package/fesm/services/auth-email.service.spec.js +359 -0
  61. package/fesm/services/authentication.service.js +37 -109
  62. package/fesm/services/authentication.service.spec.js +859 -0
  63. package/fesm/services/branch.service.js +32 -5
  64. package/fesm/services/branch.service.spec.js +150 -0
  65. package/fesm/services/company-selection-session.service.spec.js +86 -0
  66. package/fesm/services/company.service.js +36 -5
  67. package/fesm/services/company.service.spec.js +109 -0
  68. package/fesm/services/user-permission.service.js +36 -5
  69. package/fesm/services/user-permission.service.spec.js +198 -0
  70. package/fesm/services/user.service.spec.js +659 -0
  71. package/fesm/strategies/jwt.strategy.js +1 -1
  72. package/fesm/strategies/jwt.strategy.spec.js +162 -0
  73. package/fesm/validators/password.validator.spec.js +157 -0
  74. package/helpers/auth-response.mapper.d.ts +22 -0
  75. package/helpers/index.d.ts +2 -0
  76. package/helpers/token.helper.d.ts +8 -0
  77. package/index.d.ts +1 -0
  78. package/interfaces/authentication.interface.d.ts +7 -0
  79. package/package.json +8 -3
  80. package/services/authentication.service.d.ts +1 -8
  81. package/services/branch.service.d.ts +7 -5
  82. package/services/company.service.d.ts +7 -4
  83. 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
+ });