@flusys/nestjs-auth 3.0.1 → 4.0.0-lts
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/config/index.js +1 -0
- package/cjs/config/message-keys.js +150 -0
- package/cjs/controllers/authentication.controller.js +9 -4
- package/cjs/controllers/branch.controller.js +1 -0
- package/cjs/controllers/company-selection.controller.js +3 -0
- package/cjs/controllers/company.controller.js +1 -0
- package/cjs/controllers/email-verification.controller.js +7 -2
- package/cjs/controllers/user-permission.controller.js +9 -1
- package/cjs/controllers/user.controller.js +16 -4
- package/cjs/dtos/authentication.dto.js +8 -0
- package/cjs/dtos/user-permission.dto.js +14 -0
- package/cjs/interceptors/set-token.interceptor.js +14 -5
- package/cjs/services/auth-config.service.js +9 -8
- package/cjs/services/auth-datasource.provider.js +7 -4
- package/cjs/services/auth-email.service.js +155 -45
- package/cjs/services/authentication.service.js +274 -60
- package/cjs/services/branch.service.js +1 -1
- package/cjs/services/company-selection-session.service.js +25 -6
- package/cjs/services/company.service.js +1 -1
- package/cjs/services/user-permission.service.js +52 -4
- package/cjs/services/user.service.js +122 -26
- package/cjs/strategies/jwt.strategy.js +18 -4
- package/config/index.d.ts +1 -0
- package/config/message-keys.d.ts +218 -0
- package/controllers/company-selection.controller.d.ts +2 -0
- package/controllers/user-permission.controller.d.ts +4 -0
- package/controllers/user.controller.d.ts +10 -0
- package/dtos/authentication.dto.d.ts +1 -0
- package/dtos/user-permission.dto.d.ts +2 -0
- package/entities/index.d.ts +10 -1
- package/fesm/config/index.js +1 -0
- package/fesm/config/message-keys.js +118 -0
- package/fesm/controllers/authentication.controller.js +9 -3
- package/fesm/controllers/branch.controller.js +1 -0
- package/fesm/controllers/company-selection.controller.js +3 -0
- package/fesm/controllers/company.controller.js +1 -0
- package/fesm/controllers/email-verification.controller.js +7 -2
- package/fesm/controllers/user-permission.controller.js +9 -1
- package/fesm/controllers/user.controller.js +16 -4
- package/fesm/dtos/authentication.dto.js +8 -0
- package/fesm/dtos/user-permission.dto.js +14 -0
- package/fesm/interceptors/set-token.interceptor.js +14 -4
- package/fesm/services/auth-config.service.js +10 -9
- package/fesm/services/auth-datasource.provider.js +7 -4
- package/fesm/services/auth-email.service.js +156 -45
- package/fesm/services/authentication.service.js +276 -61
- package/fesm/services/branch.service.js +1 -1
- package/fesm/services/company-selection-session.service.js +25 -6
- package/fesm/services/company.service.js +1 -1
- package/fesm/services/user-permission.service.js +52 -4
- package/fesm/services/user.service.js +122 -25
- package/fesm/strategies/jwt.strategy.js +18 -4
- package/interfaces/auth-module-options.interface.d.ts +2 -2
- package/interfaces/authentication.interface.d.ts +1 -0
- package/modules/auth.module.d.ts +2 -1
- package/package.json +6 -4
- package/services/auth-config.service.d.ts +0 -2
- package/services/auth-datasource.provider.d.ts +1 -3
- package/services/auth-email.service.d.ts +6 -3
- package/services/authentication.service.d.ts +1 -1
- package/services/branch.service.d.ts +2 -1
- package/services/user.service.d.ts +7 -7
|
@@ -26,16 +26,21 @@ function _ts_param(paramIndex, decorator) {
|
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
import { HybridCache } from '@flusys/nestjs-shared/classes';
|
|
29
|
+
import { SYSTEM_MESSAGES } from '@flusys/nestjs-shared/constants';
|
|
30
|
+
import { LogAction } from '@flusys/nestjs-shared';
|
|
31
|
+
import { AUTH_MESSAGES, EMAIL_VERIFICATION_MESSAGES, USER_MESSAGES } from '../config';
|
|
29
32
|
import { INotificationAdapter, NOTIFICATION_ADAPTER } from '@flusys/nestjs-shared/interfaces';
|
|
30
33
|
import { NotificationType } from '@flusys/nestjs-shared/enums';
|
|
31
|
-
import {
|
|
32
|
-
import { BadRequestException, Inject, Injectable,
|
|
34
|
+
import { generateUniqueSlug } from '@flusys/nestjs-shared/utils';
|
|
35
|
+
import { BadRequestException, Inject, Injectable, InternalServerErrorException, NotFoundException, Optional, Scope, UnauthorizedException } from '@nestjs/common';
|
|
33
36
|
import { JwtService } from '@nestjs/jwt';
|
|
34
37
|
import * as bcrypt from 'bcrypt';
|
|
35
38
|
import { BCRYPT_SALT_ROUNDS, TOKEN_REVOKED_PREFIX, USER_ENRICHER } from '../config';
|
|
36
39
|
import { IUserEnricher } from '../interfaces/user-enricher.interface';
|
|
40
|
+
import { RegistrationDto } from '../dtos/authentication.dto';
|
|
37
41
|
import { CompanyBranch } from '../entities/company-branch.entity';
|
|
38
42
|
import { Company } from '../entities/company.entity';
|
|
43
|
+
import { PendingRegistration } from '../entities/pending-registration.entity';
|
|
39
44
|
import { UserCompanyPermission } from '../entities/user-company-permission.entity';
|
|
40
45
|
import { User } from '../entities/user.entity';
|
|
41
46
|
import { PermissionType } from '../enums';
|
|
@@ -54,7 +59,10 @@ export class AuthenticationService {
|
|
|
54
59
|
}
|
|
55
60
|
async getUserRepository() {
|
|
56
61
|
if (!this.dataSourceProvider) {
|
|
57
|
-
throw new
|
|
62
|
+
throw new InternalServerErrorException({
|
|
63
|
+
message: 'User repository not available',
|
|
64
|
+
messageKey: SYSTEM_MESSAGES.REPOSITORY_NOT_AVAILABLE
|
|
65
|
+
});
|
|
58
66
|
}
|
|
59
67
|
return this.dataSourceProvider.getRepository(User);
|
|
60
68
|
}
|
|
@@ -75,24 +83,29 @@ export class AuthenticationService {
|
|
|
75
83
|
return {
|
|
76
84
|
requiresEmailVerification: true,
|
|
77
85
|
email,
|
|
78
|
-
message: 'Please verify your email address to complete registration.'
|
|
86
|
+
message: 'Please verify your email address to complete registration.',
|
|
87
|
+
messageKey: EMAIL_VERIFICATION_MESSAGES.VERIFICATION_SENT
|
|
79
88
|
};
|
|
80
89
|
}
|
|
81
90
|
const user = await this.validateUser(email, password);
|
|
82
91
|
if (!user.isActive) {
|
|
83
|
-
throw new UnauthorizedException(
|
|
92
|
+
throw new UnauthorizedException({
|
|
93
|
+
message: 'User account is deactivated',
|
|
94
|
+
messageKey: AUTH_MESSAGES.LOGIN_ACCOUNT_DEACTIVATED
|
|
95
|
+
});
|
|
84
96
|
}
|
|
85
97
|
if (this.isEmailVerificationEnabled() && !user.emailVerified) {
|
|
86
98
|
return {
|
|
87
99
|
requiresEmailVerification: true,
|
|
88
100
|
email: user.email,
|
|
89
|
-
message: 'Please verify your email address before logging in.'
|
|
101
|
+
message: 'Please verify your email address before logging in.',
|
|
102
|
+
messageKey: AUTH_MESSAGES.LOGIN_EMAIL_NOT_VERIFIED
|
|
90
103
|
};
|
|
91
104
|
}
|
|
92
105
|
if (!this.isCompanyFeatureEnabled()) {
|
|
93
106
|
return this.generateLoginResponse(user);
|
|
94
107
|
}
|
|
95
|
-
return this.handleCompanyBasedLogin(user);
|
|
108
|
+
return await this.handleCompanyBasedLogin(user);
|
|
96
109
|
}
|
|
97
110
|
async handleCompanyBasedLogin(user) {
|
|
98
111
|
const permissionRepository = await this.getPermissionRepository();
|
|
@@ -144,10 +157,13 @@ export class AuthenticationService {
|
|
|
144
157
|
}
|
|
145
158
|
});
|
|
146
159
|
if (existingUser && this.isCompanyFeatureEnabled()) {
|
|
147
|
-
return this.handleExistingUserCompanyRegistration(existingUser, dto);
|
|
160
|
+
return await this.handleExistingUserCompanyRegistration(existingUser, dto);
|
|
148
161
|
}
|
|
149
162
|
if (existingUser) {
|
|
150
|
-
throw new BadRequestException(
|
|
163
|
+
throw new BadRequestException({
|
|
164
|
+
message: 'Registration failed due to email exists. Please try again or contact support.',
|
|
165
|
+
messageKey: AUTH_MESSAGES.REGISTER_EMAIL_EXISTS
|
|
166
|
+
});
|
|
151
167
|
}
|
|
152
168
|
if (this.isEmailVerificationEnabled() && verifyUrlBase) {
|
|
153
169
|
const companyData = this.isCompanyFeatureEnabled() ? {
|
|
@@ -168,19 +184,26 @@ export class AuthenticationService {
|
|
|
168
184
|
});
|
|
169
185
|
return {
|
|
170
186
|
requiresEmailVerification: true,
|
|
171
|
-
message: result.message
|
|
187
|
+
message: result.message,
|
|
188
|
+
messageKey: EMAIL_VERIFICATION_MESSAGES.VERIFICATION_SENT
|
|
172
189
|
};
|
|
173
190
|
}
|
|
174
|
-
return this.createUserDirectly(dto);
|
|
191
|
+
return await this.createUserDirectly(dto);
|
|
175
192
|
}
|
|
176
193
|
async handleExistingUserCompanyRegistration(existingUser, dto) {
|
|
177
194
|
if (!dto.companySlug && !dto.newCompanyName) {
|
|
178
|
-
throw new BadRequestException(
|
|
195
|
+
throw new BadRequestException({
|
|
196
|
+
message: 'Email already registered. Please login or provide a company to join/create.',
|
|
197
|
+
messageKey: AUTH_MESSAGES.REGISTER_EMAIL_EXISTS
|
|
198
|
+
});
|
|
179
199
|
}
|
|
180
200
|
const permissionRepository = await this.getPermissionRepository();
|
|
181
201
|
const companyRepository = await this.getCompanyRepository();
|
|
182
202
|
if (!permissionRepository || !companyRepository) {
|
|
183
|
-
throw new BadRequestException(
|
|
203
|
+
throw new BadRequestException({
|
|
204
|
+
message: 'Company feature repositories not available',
|
|
205
|
+
messageKey: SYSTEM_MESSAGES.REPOSITORY_NOT_AVAILABLE
|
|
206
|
+
});
|
|
184
207
|
}
|
|
185
208
|
if (dto.companySlug) {
|
|
186
209
|
const company = await companyRepository.findOne({
|
|
@@ -198,15 +221,21 @@ export class AuthenticationService {
|
|
|
198
221
|
}
|
|
199
222
|
});
|
|
200
223
|
if (existingPermission) {
|
|
201
|
-
throw new BadRequestException(
|
|
224
|
+
throw new BadRequestException({
|
|
225
|
+
message: 'You are already assigned to this company. Please login.',
|
|
226
|
+
messageKey: AUTH_MESSAGES.REGISTER_COMPANY_ALREADY_ASSIGNED
|
|
227
|
+
});
|
|
202
228
|
}
|
|
203
229
|
}
|
|
204
230
|
}
|
|
205
|
-
return this.processCompanyRegistration(existingUser, dto, true);
|
|
231
|
+
return await this.processCompanyRegistration(existingUser, dto, true);
|
|
206
232
|
}
|
|
207
233
|
async createUserDirectly(dto) {
|
|
208
234
|
if (!this.dataSourceProvider) {
|
|
209
|
-
throw new BadRequestException(
|
|
235
|
+
throw new BadRequestException({
|
|
236
|
+
message: 'Data source not available',
|
|
237
|
+
messageKey: SYSTEM_MESSAGES.DATASOURCE_NOT_AVAILABLE
|
|
238
|
+
});
|
|
210
239
|
}
|
|
211
240
|
const dataSource = await this.dataSourceProvider.getDataSource();
|
|
212
241
|
const queryRunner = dataSource.createQueryRunner();
|
|
@@ -231,21 +260,26 @@ export class AuthenticationService {
|
|
|
231
260
|
if (!this.isCompanyFeatureEnabled()) {
|
|
232
261
|
return this.generateRegistrationResponse(user);
|
|
233
262
|
}
|
|
234
|
-
return this.processCompanyRegistration(user, dto);
|
|
263
|
+
return await this.processCompanyRegistration(user, dto);
|
|
235
264
|
} catch (error) {
|
|
236
265
|
await queryRunner.rollbackTransaction();
|
|
237
266
|
if (error instanceof BadRequestException) {
|
|
238
267
|
throw error;
|
|
239
268
|
}
|
|
240
|
-
|
|
241
|
-
|
|
269
|
+
throw new BadRequestException({
|
|
270
|
+
message: 'Registration failed. Please try again.',
|
|
271
|
+
messageKey: AUTH_MESSAGES.REGISTER_FAILED
|
|
272
|
+
});
|
|
242
273
|
} finally{
|
|
243
274
|
await queryRunner.release();
|
|
244
275
|
}
|
|
245
276
|
}
|
|
246
277
|
async createUserFromPendingRegistration(pending) {
|
|
247
278
|
if (!this.dataSourceProvider) {
|
|
248
|
-
throw new BadRequestException(
|
|
279
|
+
throw new BadRequestException({
|
|
280
|
+
message: 'Data source not available',
|
|
281
|
+
messageKey: SYSTEM_MESSAGES.DATASOURCE_NOT_AVAILABLE
|
|
282
|
+
});
|
|
249
283
|
}
|
|
250
284
|
const dataSource = await this.dataSourceProvider.getDataSource();
|
|
251
285
|
const queryRunner = dataSource.createQueryRunner();
|
|
@@ -258,7 +292,10 @@ export class AuthenticationService {
|
|
|
258
292
|
}
|
|
259
293
|
});
|
|
260
294
|
if (existingUser) {
|
|
261
|
-
throw new BadRequestException(
|
|
295
|
+
throw new BadRequestException({
|
|
296
|
+
message: 'This email has already been registered.',
|
|
297
|
+
messageKey: AUTH_MESSAGES.REGISTER_EMAIL_EXISTS
|
|
298
|
+
});
|
|
262
299
|
}
|
|
263
300
|
const user = queryRunner.manager.create(User, {
|
|
264
301
|
name: pending.name,
|
|
@@ -275,7 +312,7 @@ export class AuthenticationService {
|
|
|
275
312
|
}
|
|
276
313
|
await queryRunner.commitTransaction();
|
|
277
314
|
if (this.isCompanyFeatureEnabled() && pending.companyData) {
|
|
278
|
-
return this.processCompanyRegistration(user, pending.companyData);
|
|
315
|
+
return await this.processCompanyRegistration(user, pending.companyData);
|
|
279
316
|
}
|
|
280
317
|
return this.generateRegistrationResponse(user);
|
|
281
318
|
} catch (error) {
|
|
@@ -283,8 +320,10 @@ export class AuthenticationService {
|
|
|
283
320
|
if (error instanceof BadRequestException) {
|
|
284
321
|
throw error;
|
|
285
322
|
}
|
|
286
|
-
|
|
287
|
-
|
|
323
|
+
throw new BadRequestException({
|
|
324
|
+
message: 'Registration failed. Please try again.',
|
|
325
|
+
messageKey: AUTH_MESSAGES.REGISTER_FAILED
|
|
326
|
+
});
|
|
288
327
|
} finally{
|
|
289
328
|
await queryRunner.release();
|
|
290
329
|
}
|
|
@@ -294,7 +333,10 @@ export class AuthenticationService {
|
|
|
294
333
|
const branchRepository = await this.getBranchRepository();
|
|
295
334
|
const permissionRepository = await this.getPermissionRepository();
|
|
296
335
|
if (!companyRepository || !branchRepository || !permissionRepository) {
|
|
297
|
-
if (throwOnUnavailable) throw new BadRequestException(
|
|
336
|
+
if (throwOnUnavailable) throw new BadRequestException({
|
|
337
|
+
message: 'Company feature repositories not available',
|
|
338
|
+
messageKey: SYSTEM_MESSAGES.REPOSITORY_NOT_AVAILABLE
|
|
339
|
+
});
|
|
298
340
|
return this.generateRegistrationResponse(user);
|
|
299
341
|
}
|
|
300
342
|
const { company, branch } = await this.findOrCreateCompanyAndBranch(data, companyRepository, branchRepository);
|
|
@@ -312,7 +354,10 @@ export class AuthenticationService {
|
|
|
312
354
|
isActive: true
|
|
313
355
|
}
|
|
314
356
|
});
|
|
315
|
-
if (!company) throw new BadRequestException(
|
|
357
|
+
if (!company) throw new BadRequestException({
|
|
358
|
+
message: 'Company not found or inactive',
|
|
359
|
+
messageKey: AUTH_MESSAGES.COMPANY_NOT_FOUND
|
|
360
|
+
});
|
|
316
361
|
const branch = data.branchSlug ? await branchRepo.findOne({
|
|
317
362
|
where: {
|
|
318
363
|
slug: data.branchSlug,
|
|
@@ -329,7 +374,10 @@ export class AuthenticationService {
|
|
|
329
374
|
createdAt: 'ASC'
|
|
330
375
|
}
|
|
331
376
|
});
|
|
332
|
-
if (data.branchSlug && !branch) throw new BadRequestException(
|
|
377
|
+
if (data.branchSlug && !branch) throw new BadRequestException({
|
|
378
|
+
message: 'Branch not found or inactive',
|
|
379
|
+
messageKey: AUTH_MESSAGES.BRANCH_NOT_FOUND
|
|
380
|
+
});
|
|
333
381
|
return {
|
|
334
382
|
company,
|
|
335
383
|
branch
|
|
@@ -385,17 +433,23 @@ export class AuthenticationService {
|
|
|
385
433
|
async select(sessionId, companyId, branchId) {
|
|
386
434
|
this.ensureCompanyFeatureEnabled();
|
|
387
435
|
if (!this.sessionService) {
|
|
388
|
-
throw new BadRequestException(
|
|
436
|
+
throw new BadRequestException({
|
|
437
|
+
message: 'Session service not available',
|
|
438
|
+
messageKey: AUTH_MESSAGES.SESSION_NOT_AVAILABLE
|
|
439
|
+
});
|
|
389
440
|
}
|
|
390
441
|
const session = await this.sessionService.consumeSession(sessionId);
|
|
391
442
|
if (!session) {
|
|
392
|
-
throw new UnauthorizedException(
|
|
443
|
+
throw new UnauthorizedException({
|
|
444
|
+
message: 'Invalid or expired session',
|
|
445
|
+
messageKey: AUTH_MESSAGES.SESSION_INVALID
|
|
446
|
+
});
|
|
393
447
|
}
|
|
394
|
-
return this.validateAndLoginWithCompany(session.userId, companyId, branchId);
|
|
448
|
+
return await this.validateAndLoginWithCompany(session.userId, companyId, branchId);
|
|
395
449
|
}
|
|
396
450
|
async switchCompany(userId, companyId, branchId) {
|
|
397
451
|
this.ensureCompanyFeatureEnabled();
|
|
398
|
-
return this.validateAndLoginWithCompany(userId, companyId, branchId);
|
|
452
|
+
return await this.validateAndLoginWithCompany(userId, companyId, branchId);
|
|
399
453
|
}
|
|
400
454
|
async validateAndLoginWithCompany(userId, companyId, branchId) {
|
|
401
455
|
const userRepository = await this.getUserRepository();
|
|
@@ -405,13 +459,19 @@ export class AuthenticationService {
|
|
|
405
459
|
}
|
|
406
460
|
});
|
|
407
461
|
if (!user) {
|
|
408
|
-
throw new NotFoundException(
|
|
462
|
+
throw new NotFoundException({
|
|
463
|
+
message: 'User not found',
|
|
464
|
+
messageKey: USER_MESSAGES.NOT_FOUND
|
|
465
|
+
});
|
|
409
466
|
}
|
|
410
467
|
const permissionRepository = await this.getPermissionRepository();
|
|
411
468
|
const companyRepository = await this.getCompanyRepository();
|
|
412
469
|
const branchRepository = await this.getBranchRepository();
|
|
413
470
|
if (!permissionRepository || !companyRepository || !branchRepository) {
|
|
414
|
-
throw new BadRequestException(
|
|
471
|
+
throw new BadRequestException({
|
|
472
|
+
message: 'Repositories not available',
|
|
473
|
+
messageKey: SYSTEM_MESSAGES.REPOSITORY_NOT_AVAILABLE
|
|
474
|
+
});
|
|
415
475
|
}
|
|
416
476
|
const hasCompanyAccess = await permissionRepository.findOne({
|
|
417
477
|
where: {
|
|
@@ -421,7 +481,10 @@ export class AuthenticationService {
|
|
|
421
481
|
}
|
|
422
482
|
});
|
|
423
483
|
if (!hasCompanyAccess) {
|
|
424
|
-
throw new UnauthorizedException(
|
|
484
|
+
throw new UnauthorizedException({
|
|
485
|
+
message: 'No access to this company',
|
|
486
|
+
messageKey: AUTH_MESSAGES.COMPANY_NO_ACCESS
|
|
487
|
+
});
|
|
425
488
|
}
|
|
426
489
|
const company = await companyRepository.findOne({
|
|
427
490
|
where: {
|
|
@@ -430,7 +493,10 @@ export class AuthenticationService {
|
|
|
430
493
|
}
|
|
431
494
|
});
|
|
432
495
|
if (!company) {
|
|
433
|
-
throw new UnauthorizedException(
|
|
496
|
+
throw new UnauthorizedException({
|
|
497
|
+
message: 'Company not found or inactive',
|
|
498
|
+
messageKey: AUTH_MESSAGES.COMPANY_NOT_FOUND
|
|
499
|
+
});
|
|
434
500
|
}
|
|
435
501
|
const hasBranchAccess = await permissionRepository.findOne({
|
|
436
502
|
where: {
|
|
@@ -440,7 +506,10 @@ export class AuthenticationService {
|
|
|
440
506
|
}
|
|
441
507
|
});
|
|
442
508
|
if (!hasBranchAccess) {
|
|
443
|
-
throw new UnauthorizedException(
|
|
509
|
+
throw new UnauthorizedException({
|
|
510
|
+
message: 'No access to this branch',
|
|
511
|
+
messageKey: AUTH_MESSAGES.BRANCH_NO_ACCESS
|
|
512
|
+
});
|
|
444
513
|
}
|
|
445
514
|
const branch = await branchRepository.findOne({
|
|
446
515
|
where: {
|
|
@@ -450,7 +519,10 @@ export class AuthenticationService {
|
|
|
450
519
|
}
|
|
451
520
|
});
|
|
452
521
|
if (!branch) {
|
|
453
|
-
throw new UnauthorizedException(
|
|
522
|
+
throw new UnauthorizedException({
|
|
523
|
+
message: 'Branch not found or inactive',
|
|
524
|
+
messageKey: AUTH_MESSAGES.BRANCH_NOT_FOUND
|
|
525
|
+
});
|
|
454
526
|
}
|
|
455
527
|
return this.generateLoginResponseWithCompany(user, company, branch);
|
|
456
528
|
}
|
|
@@ -460,7 +532,10 @@ export class AuthenticationService {
|
|
|
460
532
|
secret: this.authConfig.getRefreshTokenSecret()
|
|
461
533
|
});
|
|
462
534
|
if (await this.isTokenRevoked(payload.id, payload.iat)) {
|
|
463
|
-
throw new UnauthorizedException(
|
|
535
|
+
throw new UnauthorizedException({
|
|
536
|
+
message: 'Token has been revoked',
|
|
537
|
+
messageKey: AUTH_MESSAGES.TOKEN_REVOKED
|
|
538
|
+
});
|
|
464
539
|
}
|
|
465
540
|
const userRepository = await this.getUserRepository();
|
|
466
541
|
const user = await userRepository.findOne({
|
|
@@ -469,7 +544,10 @@ export class AuthenticationService {
|
|
|
469
544
|
}
|
|
470
545
|
});
|
|
471
546
|
if (!user || !user.isActive) {
|
|
472
|
-
throw new UnauthorizedException(
|
|
547
|
+
throw new UnauthorizedException({
|
|
548
|
+
message: 'Invalid refresh token',
|
|
549
|
+
messageKey: AUTH_MESSAGES.TOKEN_INVALID
|
|
550
|
+
});
|
|
473
551
|
}
|
|
474
552
|
return this.generateTokens({
|
|
475
553
|
id: user.id,
|
|
@@ -483,9 +561,11 @@ export class AuthenticationService {
|
|
|
483
561
|
branchId: payload.branchId
|
|
484
562
|
}
|
|
485
563
|
});
|
|
486
|
-
} catch
|
|
487
|
-
|
|
488
|
-
|
|
564
|
+
} catch {
|
|
565
|
+
throw new UnauthorizedException({
|
|
566
|
+
message: 'Invalid or expired refresh token',
|
|
567
|
+
messageKey: AUTH_MESSAGES.TOKEN_EXPIRED
|
|
568
|
+
});
|
|
489
569
|
}
|
|
490
570
|
}
|
|
491
571
|
async changePassword(userId, currentPassword, newPassword) {
|
|
@@ -495,24 +575,31 @@ export class AuthenticationService {
|
|
|
495
575
|
id: userId
|
|
496
576
|
}
|
|
497
577
|
});
|
|
498
|
-
if (!user) throw new UnauthorizedException(
|
|
578
|
+
if (!user) throw new UnauthorizedException({
|
|
579
|
+
message: 'User not found',
|
|
580
|
+
messageKey: USER_MESSAGES.NOT_FOUND
|
|
581
|
+
});
|
|
499
582
|
if (!await bcrypt.compare(currentPassword, user.password || '')) {
|
|
500
|
-
throw new UnauthorizedException(
|
|
583
|
+
throw new UnauthorizedException({
|
|
584
|
+
message: 'Current password is incorrect',
|
|
585
|
+
messageKey: AUTH_MESSAGES.PASSWORD_CHANGE_INVALID_CURRENT
|
|
586
|
+
});
|
|
501
587
|
}
|
|
502
588
|
user.password = await bcrypt.hash(newPassword, BCRYPT_SALT_ROUNDS);
|
|
503
589
|
await userRepository.save(user);
|
|
504
590
|
}
|
|
505
591
|
async logout(userId) {
|
|
506
592
|
if (!this.cacheManager) {
|
|
507
|
-
|
|
508
|
-
|
|
593
|
+
throw new BadRequestException({
|
|
594
|
+
message: 'Logout service temporarily unavailable. Please try again later.',
|
|
595
|
+
messageKey: AUTH_MESSAGES.LOGOUT_SERVICE_UNAVAILABLE
|
|
596
|
+
});
|
|
509
597
|
}
|
|
510
598
|
const revokedAt = Math.floor(Date.now() / 1000);
|
|
511
599
|
const cacheKey = `${TOKEN_REVOKED_PREFIX}:${userId}`;
|
|
512
600
|
const refreshTokenExpiry = this.authConfig.getRefreshTokenExpiration();
|
|
513
601
|
const ttlMs = this.parseExpiryToMs(refreshTokenExpiry);
|
|
514
602
|
await this.cacheManager.set(cacheKey, revokedAt, ttlMs);
|
|
515
|
-
this.logger.log(`User ${userId} logged out - all tokens revoked`);
|
|
516
603
|
}
|
|
517
604
|
async isTokenRevoked(userId, tokenIssuedAt) {
|
|
518
605
|
if (!this.cacheManager || !tokenIssuedAt) {
|
|
@@ -548,7 +635,10 @@ export class AuthenticationService {
|
|
|
548
635
|
const permissionRepository = await this.getPermissionRepository();
|
|
549
636
|
const companyRepository = await this.getCompanyRepository();
|
|
550
637
|
if (!permissionRepository || !companyRepository) {
|
|
551
|
-
throw new BadRequestException(
|
|
638
|
+
throw new BadRequestException({
|
|
639
|
+
message: 'Repositories not available',
|
|
640
|
+
messageKey: SYSTEM_MESSAGES.REPOSITORY_NOT_AVAILABLE
|
|
641
|
+
});
|
|
552
642
|
}
|
|
553
643
|
const permissions = await permissionRepository.find({
|
|
554
644
|
where: {
|
|
@@ -569,7 +659,10 @@ export class AuthenticationService {
|
|
|
569
659
|
const permissionRepository = await this.getPermissionRepository();
|
|
570
660
|
const branchRepository = await this.getBranchRepository();
|
|
571
661
|
if (!permissionRepository || !branchRepository) {
|
|
572
|
-
throw new BadRequestException(
|
|
662
|
+
throw new BadRequestException({
|
|
663
|
+
message: 'Repositories not available',
|
|
664
|
+
messageKey: SYSTEM_MESSAGES.REPOSITORY_NOT_AVAILABLE
|
|
665
|
+
});
|
|
573
666
|
}
|
|
574
667
|
const companyPermission = await permissionRepository.findOne({
|
|
575
668
|
where: {
|
|
@@ -579,7 +672,10 @@ export class AuthenticationService {
|
|
|
579
672
|
}
|
|
580
673
|
});
|
|
581
674
|
if (!companyPermission) {
|
|
582
|
-
throw new UnauthorizedException(
|
|
675
|
+
throw new UnauthorizedException({
|
|
676
|
+
message: 'No access to this company',
|
|
677
|
+
messageKey: AUTH_MESSAGES.COMPANY_NO_ACCESS
|
|
678
|
+
});
|
|
583
679
|
}
|
|
584
680
|
const branchPermissions = await permissionRepository.find({
|
|
585
681
|
where: {
|
|
@@ -626,20 +722,32 @@ export class AuthenticationService {
|
|
|
626
722
|
id: userId
|
|
627
723
|
}
|
|
628
724
|
});
|
|
629
|
-
if (!user) throw new UnauthorizedException(
|
|
725
|
+
if (!user) throw new UnauthorizedException({
|
|
726
|
+
message: 'User not found',
|
|
727
|
+
messageKey: USER_MESSAGES.NOT_FOUND
|
|
728
|
+
});
|
|
630
729
|
const response = {
|
|
631
730
|
user: this.toUserInfo(user)
|
|
632
731
|
};
|
|
633
732
|
if (this.isCompanyFeatureEnabled()) {
|
|
634
733
|
if (!companyId) {
|
|
635
|
-
throw new UnauthorizedException(
|
|
734
|
+
throw new UnauthorizedException({
|
|
735
|
+
message: 'Company feature is enabled but user has no company assigned.',
|
|
736
|
+
messageKey: AUTH_MESSAGES.COMPANY_REQUIRED
|
|
737
|
+
});
|
|
636
738
|
}
|
|
637
739
|
const company = await this.getCompanyById(companyId);
|
|
638
|
-
if (!company) throw new UnauthorizedException(
|
|
740
|
+
if (!company) throw new UnauthorizedException({
|
|
741
|
+
message: 'Company not found.',
|
|
742
|
+
messageKey: AUTH_MESSAGES.COMPANY_NOT_FOUND
|
|
743
|
+
});
|
|
639
744
|
response.company = this.toCompanyInfo(company);
|
|
640
745
|
if (branchId) {
|
|
641
746
|
const branch = await this.getBranchById(branchId);
|
|
642
|
-
if (!branch) throw new UnauthorizedException(
|
|
747
|
+
if (!branch) throw new UnauthorizedException({
|
|
748
|
+
message: 'Branch not found.',
|
|
749
|
+
messageKey: AUTH_MESSAGES.BRANCH_NOT_FOUND
|
|
750
|
+
});
|
|
643
751
|
response.branch = this.toBranchInfo(branch);
|
|
644
752
|
}
|
|
645
753
|
}
|
|
@@ -647,7 +755,10 @@ export class AuthenticationService {
|
|
|
647
755
|
}
|
|
648
756
|
ensureCompanyFeatureEnabled() {
|
|
649
757
|
if (!this.isCompanyFeatureEnabled()) {
|
|
650
|
-
throw new BadRequestException(
|
|
758
|
+
throw new BadRequestException({
|
|
759
|
+
message: 'Company feature is not enabled',
|
|
760
|
+
messageKey: AUTH_MESSAGES.COMPANY_FEATURE_NOT_ENABLED
|
|
761
|
+
});
|
|
651
762
|
}
|
|
652
763
|
}
|
|
653
764
|
async validateUser(email, password) {
|
|
@@ -658,7 +769,10 @@ export class AuthenticationService {
|
|
|
658
769
|
}
|
|
659
770
|
});
|
|
660
771
|
if (!user || !await bcrypt.compare(password, user.password || '')) {
|
|
661
|
-
throw new UnauthorizedException(
|
|
772
|
+
throw new UnauthorizedException({
|
|
773
|
+
message: 'Invalid credentials',
|
|
774
|
+
messageKey: AUTH_MESSAGES.LOGIN_INVALID_CREDENTIALS
|
|
775
|
+
});
|
|
662
776
|
}
|
|
663
777
|
user.lastLoginAt = new Date();
|
|
664
778
|
await userRepository.save(user);
|
|
@@ -747,13 +861,16 @@ export class AuthenticationService {
|
|
|
747
861
|
message: 'You have successfully logged in to your account.',
|
|
748
862
|
type: NotificationType.INFO,
|
|
749
863
|
companyId
|
|
750
|
-
}).catch((
|
|
751
|
-
|
|
864
|
+
}).catch(()=>{
|
|
865
|
+
// Silent failure - notification is non-critical
|
|
752
866
|
});
|
|
753
867
|
}
|
|
754
868
|
async generateCompanySelectionResponse(user, companies, branches) {
|
|
755
869
|
if (!this.sessionService) {
|
|
756
|
-
throw new BadRequestException(
|
|
870
|
+
throw new BadRequestException({
|
|
871
|
+
message: 'Session service not available',
|
|
872
|
+
messageKey: AUTH_MESSAGES.SESSION_NOT_AVAILABLE
|
|
873
|
+
});
|
|
757
874
|
}
|
|
758
875
|
const sessionId = await this.sessionService.createSession(user.id);
|
|
759
876
|
const companiesWithBranches = companies.map((company)=>({
|
|
@@ -794,7 +911,6 @@ export class AuthenticationService {
|
|
|
794
911
|
_define_property(this, "authEmailService", void 0);
|
|
795
912
|
_define_property(this, "userEnricher", void 0);
|
|
796
913
|
_define_property(this, "notificationAdapter", void 0);
|
|
797
|
-
_define_property(this, "logger", void 0);
|
|
798
914
|
this.jwtService = jwtService;
|
|
799
915
|
this.authConfig = authConfig;
|
|
800
916
|
this.cacheManager = cacheManager;
|
|
@@ -803,9 +919,108 @@ export class AuthenticationService {
|
|
|
803
919
|
this.authEmailService = authEmailService;
|
|
804
920
|
this.userEnricher = userEnricher;
|
|
805
921
|
this.notificationAdapter = notificationAdapter;
|
|
806
|
-
this.logger = new Logger(AuthenticationService.name);
|
|
807
922
|
}
|
|
808
923
|
}
|
|
924
|
+
_ts_decorate([
|
|
925
|
+
LogAction({
|
|
926
|
+
action: 'auth.login',
|
|
927
|
+
module: 'auth',
|
|
928
|
+
includeParams: false
|
|
929
|
+
}),
|
|
930
|
+
_ts_metadata("design:type", Function),
|
|
931
|
+
_ts_metadata("design:paramtypes", [
|
|
932
|
+
String,
|
|
933
|
+
String
|
|
934
|
+
]),
|
|
935
|
+
_ts_metadata("design:returntype", Promise)
|
|
936
|
+
], AuthenticationService.prototype, "login", null);
|
|
937
|
+
_ts_decorate([
|
|
938
|
+
LogAction({
|
|
939
|
+
action: 'auth.register',
|
|
940
|
+
module: 'auth',
|
|
941
|
+
includeParams: false
|
|
942
|
+
}),
|
|
943
|
+
_ts_metadata("design:type", Function),
|
|
944
|
+
_ts_metadata("design:paramtypes", [
|
|
945
|
+
typeof RegistrationDto === "undefined" ? Object : RegistrationDto,
|
|
946
|
+
String
|
|
947
|
+
]),
|
|
948
|
+
_ts_metadata("design:returntype", Promise)
|
|
949
|
+
], AuthenticationService.prototype, "register", null);
|
|
950
|
+
_ts_decorate([
|
|
951
|
+
LogAction({
|
|
952
|
+
action: 'auth.createUser',
|
|
953
|
+
module: 'auth',
|
|
954
|
+
includeParams: false
|
|
955
|
+
}),
|
|
956
|
+
_ts_metadata("design:type", Function),
|
|
957
|
+
_ts_metadata("design:paramtypes", [
|
|
958
|
+
typeof RegistrationDto === "undefined" ? Object : RegistrationDto
|
|
959
|
+
]),
|
|
960
|
+
_ts_metadata("design:returntype", Promise)
|
|
961
|
+
], AuthenticationService.prototype, "createUserDirectly", null);
|
|
962
|
+
_ts_decorate([
|
|
963
|
+
LogAction({
|
|
964
|
+
action: 'auth.createUserFromPending',
|
|
965
|
+
module: 'auth',
|
|
966
|
+
includeParams: false
|
|
967
|
+
}),
|
|
968
|
+
_ts_metadata("design:type", Function),
|
|
969
|
+
_ts_metadata("design:paramtypes", [
|
|
970
|
+
typeof PendingRegistration === "undefined" ? Object : PendingRegistration
|
|
971
|
+
]),
|
|
972
|
+
_ts_metadata("design:returntype", Promise)
|
|
973
|
+
], AuthenticationService.prototype, "createUserFromPendingRegistration", null);
|
|
974
|
+
_ts_decorate([
|
|
975
|
+
LogAction({
|
|
976
|
+
action: 'auth.companySwitch',
|
|
977
|
+
module: 'auth'
|
|
978
|
+
}),
|
|
979
|
+
_ts_metadata("design:type", Function),
|
|
980
|
+
_ts_metadata("design:paramtypes", [
|
|
981
|
+
String,
|
|
982
|
+
String,
|
|
983
|
+
String
|
|
984
|
+
]),
|
|
985
|
+
_ts_metadata("design:returntype", Promise)
|
|
986
|
+
], AuthenticationService.prototype, "validateAndLoginWithCompany", null);
|
|
987
|
+
_ts_decorate([
|
|
988
|
+
LogAction({
|
|
989
|
+
action: 'auth.refreshToken',
|
|
990
|
+
module: 'auth',
|
|
991
|
+
includeParams: false
|
|
992
|
+
}),
|
|
993
|
+
_ts_metadata("design:type", Function),
|
|
994
|
+
_ts_metadata("design:paramtypes", [
|
|
995
|
+
String
|
|
996
|
+
]),
|
|
997
|
+
_ts_metadata("design:returntype", Promise)
|
|
998
|
+
], AuthenticationService.prototype, "refreshToken", null);
|
|
999
|
+
_ts_decorate([
|
|
1000
|
+
LogAction({
|
|
1001
|
+
action: 'auth.changePassword',
|
|
1002
|
+
module: 'auth',
|
|
1003
|
+
includeParams: false
|
|
1004
|
+
}),
|
|
1005
|
+
_ts_metadata("design:type", Function),
|
|
1006
|
+
_ts_metadata("design:paramtypes", [
|
|
1007
|
+
String,
|
|
1008
|
+
String,
|
|
1009
|
+
String
|
|
1010
|
+
]),
|
|
1011
|
+
_ts_metadata("design:returntype", Promise)
|
|
1012
|
+
], AuthenticationService.prototype, "changePassword", null);
|
|
1013
|
+
_ts_decorate([
|
|
1014
|
+
LogAction({
|
|
1015
|
+
action: 'auth.logout',
|
|
1016
|
+
module: 'auth'
|
|
1017
|
+
}),
|
|
1018
|
+
_ts_metadata("design:type", Function),
|
|
1019
|
+
_ts_metadata("design:paramtypes", [
|
|
1020
|
+
String
|
|
1021
|
+
]),
|
|
1022
|
+
_ts_metadata("design:returntype", Promise)
|
|
1023
|
+
], AuthenticationService.prototype, "logout", null);
|
|
809
1024
|
AuthenticationService = _ts_decorate([
|
|
810
1025
|
Injectable({
|
|
811
1026
|
scope: Scope.REQUEST
|
|
@@ -79,7 +79,7 @@ export class BranchService extends RequestScopedApiService {
|
|
|
79
79
|
return result;
|
|
80
80
|
}
|
|
81
81
|
constructor(cacheManager, utilsService, dataSourceProvider, authConfig){
|
|
82
|
-
super('company_branch', null, cacheManager, utilsService, BranchService.name, true), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "dataSourceProvider", void 0), _define_property(this, "authConfig", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.dataSourceProvider = dataSourceProvider, this.authConfig = authConfig;
|
|
82
|
+
super('company_branch', null, cacheManager, utilsService, BranchService.name, true, 'auth'), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "dataSourceProvider", void 0), _define_property(this, "authConfig", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.dataSourceProvider = dataSourceProvider, this.authConfig = authConfig;
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
BranchService = _ts_decorate([
|