@flusys/nestjs-auth 3.0.0 → 4.0.0-rc

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 (63) hide show
  1. package/README.md +1 -1
  2. package/cjs/config/index.js +1 -0
  3. package/cjs/config/message-keys.js +150 -0
  4. package/cjs/controllers/authentication.controller.js +9 -4
  5. package/cjs/controllers/branch.controller.js +1 -0
  6. package/cjs/controllers/company-selection.controller.js +3 -0
  7. package/cjs/controllers/company.controller.js +1 -0
  8. package/cjs/controllers/email-verification.controller.js +7 -2
  9. package/cjs/controllers/user-permission.controller.js +9 -1
  10. package/cjs/controllers/user.controller.js +16 -4
  11. package/cjs/dtos/authentication.dto.js +8 -0
  12. package/cjs/dtos/user-permission.dto.js +14 -0
  13. package/cjs/interceptors/set-token.interceptor.js +14 -5
  14. package/cjs/services/auth-config.service.js +9 -8
  15. package/cjs/services/auth-datasource.provider.js +7 -4
  16. package/cjs/services/auth-email.service.js +155 -45
  17. package/cjs/services/authentication.service.js +306 -71
  18. package/cjs/services/branch.service.js +1 -1
  19. package/cjs/services/company-selection-session.service.js +25 -6
  20. package/cjs/services/company.service.js +1 -1
  21. package/cjs/services/user-permission.service.js +52 -4
  22. package/cjs/services/user.service.js +122 -26
  23. package/cjs/strategies/jwt.strategy.js +18 -4
  24. package/config/index.d.ts +1 -0
  25. package/config/message-keys.d.ts +218 -0
  26. package/controllers/company-selection.controller.d.ts +2 -0
  27. package/controllers/user-permission.controller.d.ts +4 -0
  28. package/controllers/user.controller.d.ts +10 -0
  29. package/dtos/authentication.dto.d.ts +1 -0
  30. package/dtos/user-permission.dto.d.ts +2 -0
  31. package/entities/index.d.ts +10 -1
  32. package/fesm/config/index.js +1 -0
  33. package/fesm/config/message-keys.js +118 -0
  34. package/fesm/controllers/authentication.controller.js +9 -3
  35. package/fesm/controllers/branch.controller.js +1 -0
  36. package/fesm/controllers/company-selection.controller.js +3 -0
  37. package/fesm/controllers/company.controller.js +1 -0
  38. package/fesm/controllers/email-verification.controller.js +7 -2
  39. package/fesm/controllers/user-permission.controller.js +9 -1
  40. package/fesm/controllers/user.controller.js +16 -4
  41. package/fesm/dtos/authentication.dto.js +8 -0
  42. package/fesm/dtos/user-permission.dto.js +14 -0
  43. package/fesm/interceptors/set-token.interceptor.js +14 -4
  44. package/fesm/services/auth-config.service.js +10 -9
  45. package/fesm/services/auth-datasource.provider.js +7 -4
  46. package/fesm/services/auth-email.service.js +156 -45
  47. package/fesm/services/authentication.service.js +297 -61
  48. package/fesm/services/branch.service.js +1 -1
  49. package/fesm/services/company-selection-session.service.js +25 -6
  50. package/fesm/services/company.service.js +1 -1
  51. package/fesm/services/user-permission.service.js +52 -4
  52. package/fesm/services/user.service.js +122 -25
  53. package/fesm/strategies/jwt.strategy.js +18 -4
  54. package/interfaces/auth-module-options.interface.d.ts +2 -2
  55. package/interfaces/authentication.interface.d.ts +1 -0
  56. package/modules/auth.module.d.ts +2 -1
  57. package/package.json +3 -3
  58. package/services/auth-config.service.d.ts +0 -2
  59. package/services/auth-datasource.provider.d.ts +1 -3
  60. package/services/auth-email.service.d.ts +6 -3
  61. package/services/authentication.service.d.ts +5 -2
  62. package/services/branch.service.d.ts +2 -1
  63. package/services/user.service.d.ts +7 -7
@@ -26,14 +26,21 @@ function _ts_param(paramIndex, decorator) {
26
26
  };
27
27
  }
28
28
  import { HybridCache } from '@flusys/nestjs-shared/classes';
29
- import { ErrorHandler, generateUniqueSlug } from '@flusys/nestjs-shared/utils';
30
- import { BadRequestException, Inject, Injectable, Logger, NotFoundException, Optional, Scope, UnauthorizedException } from '@nestjs/common';
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';
32
+ import { INotificationAdapter, NOTIFICATION_ADAPTER } from '@flusys/nestjs-shared/interfaces';
33
+ import { NotificationType } from '@flusys/nestjs-shared/enums';
34
+ import { generateUniqueSlug } from '@flusys/nestjs-shared/utils';
35
+ import { BadRequestException, Inject, Injectable, InternalServerErrorException, NotFoundException, Optional, Scope, UnauthorizedException } from '@nestjs/common';
31
36
  import { JwtService } from '@nestjs/jwt';
32
37
  import * as bcrypt from 'bcrypt';
33
38
  import { BCRYPT_SALT_ROUNDS, TOKEN_REVOKED_PREFIX, USER_ENRICHER } from '../config';
34
39
  import { IUserEnricher } from '../interfaces/user-enricher.interface';
40
+ import { RegistrationDto } from '../dtos/authentication.dto';
35
41
  import { CompanyBranch } from '../entities/company-branch.entity';
36
42
  import { Company } from '../entities/company.entity';
43
+ import { PendingRegistration } from '../entities/pending-registration.entity';
37
44
  import { UserCompanyPermission } from '../entities/user-company-permission.entity';
38
45
  import { User } from '../entities/user.entity';
39
46
  import { PermissionType } from '../enums';
@@ -52,7 +59,10 @@ export class AuthenticationService {
52
59
  }
53
60
  async getUserRepository() {
54
61
  if (!this.dataSourceProvider) {
55
- throw new Error('User repository not available');
62
+ throw new InternalServerErrorException({
63
+ message: 'User repository not available',
64
+ messageKey: SYSTEM_MESSAGES.REPOSITORY_NOT_AVAILABLE
65
+ });
56
66
  }
57
67
  return this.dataSourceProvider.getRepository(User);
58
68
  }
@@ -73,24 +83,29 @@ export class AuthenticationService {
73
83
  return {
74
84
  requiresEmailVerification: true,
75
85
  email,
76
- 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
77
88
  };
78
89
  }
79
90
  const user = await this.validateUser(email, password);
80
91
  if (!user.isActive) {
81
- throw new UnauthorizedException('User account is deactivated');
92
+ throw new UnauthorizedException({
93
+ message: 'User account is deactivated',
94
+ messageKey: AUTH_MESSAGES.LOGIN_ACCOUNT_DEACTIVATED
95
+ });
82
96
  }
83
97
  if (this.isEmailVerificationEnabled() && !user.emailVerified) {
84
98
  return {
85
99
  requiresEmailVerification: true,
86
100
  email: user.email,
87
- 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
88
103
  };
89
104
  }
90
105
  if (!this.isCompanyFeatureEnabled()) {
91
106
  return this.generateLoginResponse(user);
92
107
  }
93
- return this.handleCompanyBasedLogin(user);
108
+ return await this.handleCompanyBasedLogin(user);
94
109
  }
95
110
  async handleCompanyBasedLogin(user) {
96
111
  const permissionRepository = await this.getPermissionRepository();
@@ -142,10 +157,13 @@ export class AuthenticationService {
142
157
  }
143
158
  });
144
159
  if (existingUser && this.isCompanyFeatureEnabled()) {
145
- return this.handleExistingUserCompanyRegistration(existingUser, dto);
160
+ return await this.handleExistingUserCompanyRegistration(existingUser, dto);
146
161
  }
147
162
  if (existingUser) {
148
- throw new BadRequestException('Registration failed due to email exists. Please try again or contact support.');
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
+ });
149
167
  }
150
168
  if (this.isEmailVerificationEnabled() && verifyUrlBase) {
151
169
  const companyData = this.isCompanyFeatureEnabled() ? {
@@ -166,19 +184,26 @@ export class AuthenticationService {
166
184
  });
167
185
  return {
168
186
  requiresEmailVerification: true,
169
- message: result.message
187
+ message: result.message,
188
+ messageKey: EMAIL_VERIFICATION_MESSAGES.VERIFICATION_SENT
170
189
  };
171
190
  }
172
- return this.createUserDirectly(dto);
191
+ return await this.createUserDirectly(dto);
173
192
  }
174
193
  async handleExistingUserCompanyRegistration(existingUser, dto) {
175
194
  if (!dto.companySlug && !dto.newCompanyName) {
176
- throw new BadRequestException('Email already registered. Please login or provide a company to join/create.');
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
+ });
177
199
  }
178
200
  const permissionRepository = await this.getPermissionRepository();
179
201
  const companyRepository = await this.getCompanyRepository();
180
202
  if (!permissionRepository || !companyRepository) {
181
- throw new BadRequestException('Company feature repositories not available');
203
+ throw new BadRequestException({
204
+ message: 'Company feature repositories not available',
205
+ messageKey: SYSTEM_MESSAGES.REPOSITORY_NOT_AVAILABLE
206
+ });
182
207
  }
183
208
  if (dto.companySlug) {
184
209
  const company = await companyRepository.findOne({
@@ -196,15 +221,21 @@ export class AuthenticationService {
196
221
  }
197
222
  });
198
223
  if (existingPermission) {
199
- throw new BadRequestException('You are already assigned to this company. Please login.');
224
+ throw new BadRequestException({
225
+ message: 'You are already assigned to this company. Please login.',
226
+ messageKey: AUTH_MESSAGES.REGISTER_COMPANY_ALREADY_ASSIGNED
227
+ });
200
228
  }
201
229
  }
202
230
  }
203
- return this.processCompanyRegistration(existingUser, dto, true);
231
+ return await this.processCompanyRegistration(existingUser, dto, true);
204
232
  }
205
233
  async createUserDirectly(dto) {
206
234
  if (!this.dataSourceProvider) {
207
- throw new BadRequestException('Data source not available');
235
+ throw new BadRequestException({
236
+ message: 'Data source not available',
237
+ messageKey: SYSTEM_MESSAGES.DATASOURCE_NOT_AVAILABLE
238
+ });
208
239
  }
209
240
  const dataSource = await this.dataSourceProvider.getDataSource();
210
241
  const queryRunner = dataSource.createQueryRunner();
@@ -229,21 +260,26 @@ export class AuthenticationService {
229
260
  if (!this.isCompanyFeatureEnabled()) {
230
261
  return this.generateRegistrationResponse(user);
231
262
  }
232
- return this.processCompanyRegistration(user, dto);
263
+ return await this.processCompanyRegistration(user, dto);
233
264
  } catch (error) {
234
265
  await queryRunner.rollbackTransaction();
235
266
  if (error instanceof BadRequestException) {
236
267
  throw error;
237
268
  }
238
- ErrorHandler.logError(this.logger, error, 'createUserDirectly');
239
- throw new BadRequestException('Registration failed. Please try again.');
269
+ throw new BadRequestException({
270
+ message: 'Registration failed. Please try again.',
271
+ messageKey: AUTH_MESSAGES.REGISTER_FAILED
272
+ });
240
273
  } finally{
241
274
  await queryRunner.release();
242
275
  }
243
276
  }
244
277
  async createUserFromPendingRegistration(pending) {
245
278
  if (!this.dataSourceProvider) {
246
- throw new BadRequestException('Data source not available');
279
+ throw new BadRequestException({
280
+ message: 'Data source not available',
281
+ messageKey: SYSTEM_MESSAGES.DATASOURCE_NOT_AVAILABLE
282
+ });
247
283
  }
248
284
  const dataSource = await this.dataSourceProvider.getDataSource();
249
285
  const queryRunner = dataSource.createQueryRunner();
@@ -256,7 +292,10 @@ export class AuthenticationService {
256
292
  }
257
293
  });
258
294
  if (existingUser) {
259
- throw new BadRequestException('This email has already been registered.');
295
+ throw new BadRequestException({
296
+ message: 'This email has already been registered.',
297
+ messageKey: AUTH_MESSAGES.REGISTER_EMAIL_EXISTS
298
+ });
260
299
  }
261
300
  const user = queryRunner.manager.create(User, {
262
301
  name: pending.name,
@@ -273,7 +312,7 @@ export class AuthenticationService {
273
312
  }
274
313
  await queryRunner.commitTransaction();
275
314
  if (this.isCompanyFeatureEnabled() && pending.companyData) {
276
- return this.processCompanyRegistration(user, pending.companyData);
315
+ return await this.processCompanyRegistration(user, pending.companyData);
277
316
  }
278
317
  return this.generateRegistrationResponse(user);
279
318
  } catch (error) {
@@ -281,8 +320,10 @@ export class AuthenticationService {
281
320
  if (error instanceof BadRequestException) {
282
321
  throw error;
283
322
  }
284
- ErrorHandler.logError(this.logger, error, 'createUserFromPendingRegistration');
285
- throw new BadRequestException('Registration failed. Please try again.');
323
+ throw new BadRequestException({
324
+ message: 'Registration failed. Please try again.',
325
+ messageKey: AUTH_MESSAGES.REGISTER_FAILED
326
+ });
286
327
  } finally{
287
328
  await queryRunner.release();
288
329
  }
@@ -292,7 +333,10 @@ export class AuthenticationService {
292
333
  const branchRepository = await this.getBranchRepository();
293
334
  const permissionRepository = await this.getPermissionRepository();
294
335
  if (!companyRepository || !branchRepository || !permissionRepository) {
295
- if (throwOnUnavailable) throw new BadRequestException('Company feature repositories not available');
336
+ if (throwOnUnavailable) throw new BadRequestException({
337
+ message: 'Company feature repositories not available',
338
+ messageKey: SYSTEM_MESSAGES.REPOSITORY_NOT_AVAILABLE
339
+ });
296
340
  return this.generateRegistrationResponse(user);
297
341
  }
298
342
  const { company, branch } = await this.findOrCreateCompanyAndBranch(data, companyRepository, branchRepository);
@@ -310,7 +354,10 @@ export class AuthenticationService {
310
354
  isActive: true
311
355
  }
312
356
  });
313
- if (!company) throw new BadRequestException('Company not found or inactive');
357
+ if (!company) throw new BadRequestException({
358
+ message: 'Company not found or inactive',
359
+ messageKey: AUTH_MESSAGES.COMPANY_NOT_FOUND
360
+ });
314
361
  const branch = data.branchSlug ? await branchRepo.findOne({
315
362
  where: {
316
363
  slug: data.branchSlug,
@@ -327,7 +374,10 @@ export class AuthenticationService {
327
374
  createdAt: 'ASC'
328
375
  }
329
376
  });
330
- if (data.branchSlug && !branch) throw new BadRequestException('Branch not found or inactive');
377
+ if (data.branchSlug && !branch) throw new BadRequestException({
378
+ message: 'Branch not found or inactive',
379
+ messageKey: AUTH_MESSAGES.BRANCH_NOT_FOUND
380
+ });
331
381
  return {
332
382
  company,
333
383
  branch
@@ -383,17 +433,23 @@ export class AuthenticationService {
383
433
  async select(sessionId, companyId, branchId) {
384
434
  this.ensureCompanyFeatureEnabled();
385
435
  if (!this.sessionService) {
386
- throw new BadRequestException('Session service not available');
436
+ throw new BadRequestException({
437
+ message: 'Session service not available',
438
+ messageKey: AUTH_MESSAGES.SESSION_NOT_AVAILABLE
439
+ });
387
440
  }
388
441
  const session = await this.sessionService.consumeSession(sessionId);
389
442
  if (!session) {
390
- throw new UnauthorizedException('Invalid or expired session');
443
+ throw new UnauthorizedException({
444
+ message: 'Invalid or expired session',
445
+ messageKey: AUTH_MESSAGES.SESSION_INVALID
446
+ });
391
447
  }
392
- return this.validateAndLoginWithCompany(session.userId, companyId, branchId);
448
+ return await this.validateAndLoginWithCompany(session.userId, companyId, branchId);
393
449
  }
394
450
  async switchCompany(userId, companyId, branchId) {
395
451
  this.ensureCompanyFeatureEnabled();
396
- return this.validateAndLoginWithCompany(userId, companyId, branchId);
452
+ return await this.validateAndLoginWithCompany(userId, companyId, branchId);
397
453
  }
398
454
  async validateAndLoginWithCompany(userId, companyId, branchId) {
399
455
  const userRepository = await this.getUserRepository();
@@ -403,13 +459,19 @@ export class AuthenticationService {
403
459
  }
404
460
  });
405
461
  if (!user) {
406
- throw new NotFoundException('User not found');
462
+ throw new NotFoundException({
463
+ message: 'User not found',
464
+ messageKey: USER_MESSAGES.NOT_FOUND
465
+ });
407
466
  }
408
467
  const permissionRepository = await this.getPermissionRepository();
409
468
  const companyRepository = await this.getCompanyRepository();
410
469
  const branchRepository = await this.getBranchRepository();
411
470
  if (!permissionRepository || !companyRepository || !branchRepository) {
412
- throw new BadRequestException('Repositories not available');
471
+ throw new BadRequestException({
472
+ message: 'Repositories not available',
473
+ messageKey: SYSTEM_MESSAGES.REPOSITORY_NOT_AVAILABLE
474
+ });
413
475
  }
414
476
  const hasCompanyAccess = await permissionRepository.findOne({
415
477
  where: {
@@ -419,7 +481,10 @@ export class AuthenticationService {
419
481
  }
420
482
  });
421
483
  if (!hasCompanyAccess) {
422
- throw new UnauthorizedException('No access to this company');
484
+ throw new UnauthorizedException({
485
+ message: 'No access to this company',
486
+ messageKey: AUTH_MESSAGES.COMPANY_NO_ACCESS
487
+ });
423
488
  }
424
489
  const company = await companyRepository.findOne({
425
490
  where: {
@@ -428,7 +493,10 @@ export class AuthenticationService {
428
493
  }
429
494
  });
430
495
  if (!company) {
431
- throw new UnauthorizedException('Company not found or inactive');
496
+ throw new UnauthorizedException({
497
+ message: 'Company not found or inactive',
498
+ messageKey: AUTH_MESSAGES.COMPANY_NOT_FOUND
499
+ });
432
500
  }
433
501
  const hasBranchAccess = await permissionRepository.findOne({
434
502
  where: {
@@ -438,7 +506,10 @@ export class AuthenticationService {
438
506
  }
439
507
  });
440
508
  if (!hasBranchAccess) {
441
- throw new UnauthorizedException('No access to this branch');
509
+ throw new UnauthorizedException({
510
+ message: 'No access to this branch',
511
+ messageKey: AUTH_MESSAGES.BRANCH_NO_ACCESS
512
+ });
442
513
  }
443
514
  const branch = await branchRepository.findOne({
444
515
  where: {
@@ -448,7 +519,10 @@ export class AuthenticationService {
448
519
  }
449
520
  });
450
521
  if (!branch) {
451
- throw new UnauthorizedException('Branch not found or inactive');
522
+ throw new UnauthorizedException({
523
+ message: 'Branch not found or inactive',
524
+ messageKey: AUTH_MESSAGES.BRANCH_NOT_FOUND
525
+ });
452
526
  }
453
527
  return this.generateLoginResponseWithCompany(user, company, branch);
454
528
  }
@@ -458,7 +532,10 @@ export class AuthenticationService {
458
532
  secret: this.authConfig.getRefreshTokenSecret()
459
533
  });
460
534
  if (await this.isTokenRevoked(payload.id, payload.iat)) {
461
- throw new UnauthorizedException('Token has been revoked');
535
+ throw new UnauthorizedException({
536
+ message: 'Token has been revoked',
537
+ messageKey: AUTH_MESSAGES.TOKEN_REVOKED
538
+ });
462
539
  }
463
540
  const userRepository = await this.getUserRepository();
464
541
  const user = await userRepository.findOne({
@@ -467,7 +544,10 @@ export class AuthenticationService {
467
544
  }
468
545
  });
469
546
  if (!user || !user.isActive) {
470
- throw new UnauthorizedException('Invalid refresh token');
547
+ throw new UnauthorizedException({
548
+ message: 'Invalid refresh token',
549
+ messageKey: AUTH_MESSAGES.TOKEN_INVALID
550
+ });
471
551
  }
472
552
  return this.generateTokens({
473
553
  id: user.id,
@@ -481,9 +561,11 @@ export class AuthenticationService {
481
561
  branchId: payload.branchId
482
562
  }
483
563
  });
484
- } catch (error) {
485
- ErrorHandler.logError(this.logger, error, 'refreshToken');
486
- throw new UnauthorizedException('Invalid or expired refresh token');
564
+ } catch {
565
+ throw new UnauthorizedException({
566
+ message: 'Invalid or expired refresh token',
567
+ messageKey: AUTH_MESSAGES.TOKEN_EXPIRED
568
+ });
487
569
  }
488
570
  }
489
571
  async changePassword(userId, currentPassword, newPassword) {
@@ -493,24 +575,31 @@ export class AuthenticationService {
493
575
  id: userId
494
576
  }
495
577
  });
496
- if (!user) throw new UnauthorizedException('User not found');
578
+ if (!user) throw new UnauthorizedException({
579
+ message: 'User not found',
580
+ messageKey: USER_MESSAGES.NOT_FOUND
581
+ });
497
582
  if (!await bcrypt.compare(currentPassword, user.password || '')) {
498
- throw new UnauthorizedException('Current password is incorrect');
583
+ throw new UnauthorizedException({
584
+ message: 'Current password is incorrect',
585
+ messageKey: AUTH_MESSAGES.PASSWORD_CHANGE_INVALID_CURRENT
586
+ });
499
587
  }
500
588
  user.password = await bcrypt.hash(newPassword, BCRYPT_SALT_ROUNDS);
501
589
  await userRepository.save(user);
502
590
  }
503
591
  async logout(userId) {
504
592
  if (!this.cacheManager) {
505
- this.logger.error(`SECURITY: Cache not available for logout (userId: ${userId}). ` + 'Token revocation failed - tokens will remain valid until expiry. ' + 'Configure cache (Redis) to enable proper token revocation.');
506
- throw new BadRequestException('Logout service temporarily unavailable. Please try again later.');
593
+ throw new BadRequestException({
594
+ message: 'Logout service temporarily unavailable. Please try again later.',
595
+ messageKey: AUTH_MESSAGES.LOGOUT_SERVICE_UNAVAILABLE
596
+ });
507
597
  }
508
598
  const revokedAt = Math.floor(Date.now() / 1000);
509
599
  const cacheKey = `${TOKEN_REVOKED_PREFIX}:${userId}`;
510
600
  const refreshTokenExpiry = this.authConfig.getRefreshTokenExpiration();
511
601
  const ttlMs = this.parseExpiryToMs(refreshTokenExpiry);
512
602
  await this.cacheManager.set(cacheKey, revokedAt, ttlMs);
513
- this.logger.log(`User ${userId} logged out - all tokens revoked`);
514
603
  }
515
604
  async isTokenRevoked(userId, tokenIssuedAt) {
516
605
  if (!this.cacheManager || !tokenIssuedAt) {
@@ -546,7 +635,10 @@ export class AuthenticationService {
546
635
  const permissionRepository = await this.getPermissionRepository();
547
636
  const companyRepository = await this.getCompanyRepository();
548
637
  if (!permissionRepository || !companyRepository) {
549
- throw new BadRequestException('Repositories not available');
638
+ throw new BadRequestException({
639
+ message: 'Repositories not available',
640
+ messageKey: SYSTEM_MESSAGES.REPOSITORY_NOT_AVAILABLE
641
+ });
550
642
  }
551
643
  const permissions = await permissionRepository.find({
552
644
  where: {
@@ -567,7 +659,10 @@ export class AuthenticationService {
567
659
  const permissionRepository = await this.getPermissionRepository();
568
660
  const branchRepository = await this.getBranchRepository();
569
661
  if (!permissionRepository || !branchRepository) {
570
- throw new BadRequestException('Repositories not available');
662
+ throw new BadRequestException({
663
+ message: 'Repositories not available',
664
+ messageKey: SYSTEM_MESSAGES.REPOSITORY_NOT_AVAILABLE
665
+ });
571
666
  }
572
667
  const companyPermission = await permissionRepository.findOne({
573
668
  where: {
@@ -577,7 +672,10 @@ export class AuthenticationService {
577
672
  }
578
673
  });
579
674
  if (!companyPermission) {
580
- throw new UnauthorizedException('No access to this company');
675
+ throw new UnauthorizedException({
676
+ message: 'No access to this company',
677
+ messageKey: AUTH_MESSAGES.COMPANY_NO_ACCESS
678
+ });
581
679
  }
582
680
  const branchPermissions = await permissionRepository.find({
583
681
  where: {
@@ -624,20 +722,32 @@ export class AuthenticationService {
624
722
  id: userId
625
723
  }
626
724
  });
627
- if (!user) throw new UnauthorizedException('User not found');
725
+ if (!user) throw new UnauthorizedException({
726
+ message: 'User not found',
727
+ messageKey: USER_MESSAGES.NOT_FOUND
728
+ });
628
729
  const response = {
629
730
  user: this.toUserInfo(user)
630
731
  };
631
732
  if (this.isCompanyFeatureEnabled()) {
632
733
  if (!companyId) {
633
- throw new UnauthorizedException('Company feature is enabled but user has no company assigned.');
734
+ throw new UnauthorizedException({
735
+ message: 'Company feature is enabled but user has no company assigned.',
736
+ messageKey: AUTH_MESSAGES.COMPANY_REQUIRED
737
+ });
634
738
  }
635
739
  const company = await this.getCompanyById(companyId);
636
- if (!company) throw new UnauthorizedException('Company not found.');
740
+ if (!company) throw new UnauthorizedException({
741
+ message: 'Company not found.',
742
+ messageKey: AUTH_MESSAGES.COMPANY_NOT_FOUND
743
+ });
637
744
  response.company = this.toCompanyInfo(company);
638
745
  if (branchId) {
639
746
  const branch = await this.getBranchById(branchId);
640
- if (!branch) throw new UnauthorizedException('Branch not found.');
747
+ if (!branch) throw new UnauthorizedException({
748
+ message: 'Branch not found.',
749
+ messageKey: AUTH_MESSAGES.BRANCH_NOT_FOUND
750
+ });
641
751
  response.branch = this.toBranchInfo(branch);
642
752
  }
643
753
  }
@@ -645,7 +755,10 @@ export class AuthenticationService {
645
755
  }
646
756
  ensureCompanyFeatureEnabled() {
647
757
  if (!this.isCompanyFeatureEnabled()) {
648
- throw new BadRequestException('Company feature is not enabled');
758
+ throw new BadRequestException({
759
+ message: 'Company feature is not enabled',
760
+ messageKey: AUTH_MESSAGES.COMPANY_FEATURE_NOT_ENABLED
761
+ });
649
762
  }
650
763
  }
651
764
  async validateUser(email, password) {
@@ -656,7 +769,10 @@ export class AuthenticationService {
656
769
  }
657
770
  });
658
771
  if (!user || !await bcrypt.compare(password, user.password || '')) {
659
- throw new UnauthorizedException('Invalid credentials');
772
+ throw new UnauthorizedException({
773
+ message: 'Invalid credentials',
774
+ messageKey: AUTH_MESSAGES.LOGIN_INVALID_CREDENTIALS
775
+ });
660
776
  }
661
777
  user.lastLoginAt = new Date();
662
778
  await userRepository.save(user);
@@ -713,6 +829,7 @@ export class AuthenticationService {
713
829
  email: user.email,
714
830
  type: 'access'
715
831
  });
832
+ this.sendLoginNotification(user.id);
716
833
  return {
717
834
  accessToken,
718
835
  refreshToken,
@@ -727,6 +844,7 @@ export class AuthenticationService {
727
844
  companyId: company.id,
728
845
  branchId: branch?.id
729
846
  });
847
+ this.sendLoginNotification(user.id, company.id);
730
848
  return {
731
849
  accessToken,
732
850
  refreshToken,
@@ -735,9 +853,24 @@ export class AuthenticationService {
735
853
  branch: branch ? this.toBranchInfo(branch) : undefined
736
854
  };
737
855
  }
856
+ sendLoginNotification(userId, companyId) {
857
+ if (!this.notificationAdapter) return;
858
+ this.notificationAdapter.send({
859
+ userId,
860
+ title: 'Login successful',
861
+ message: 'You have successfully logged in to your account.',
862
+ type: NotificationType.INFO,
863
+ companyId
864
+ }).catch(()=>{
865
+ // Silent failure - notification is non-critical
866
+ });
867
+ }
738
868
  async generateCompanySelectionResponse(user, companies, branches) {
739
869
  if (!this.sessionService) {
740
- throw new BadRequestException('Session service not available');
870
+ throw new BadRequestException({
871
+ message: 'Session service not available',
872
+ messageKey: AUTH_MESSAGES.SESSION_NOT_AVAILABLE
873
+ });
741
874
  }
742
875
  const sessionId = await this.sessionService.createSession(user.id);
743
876
  const companiesWithBranches = companies.map((company)=>({
@@ -769,7 +902,7 @@ export class AuthenticationService {
769
902
  };
770
903
  }
771
904
  // NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
772
- constructor(jwtService, authConfig, cacheManager, dataSourceProvider, sessionService, authEmailService, userEnricher){
905
+ constructor(jwtService, authConfig, cacheManager, dataSourceProvider, sessionService, authEmailService, userEnricher, notificationAdapter){
773
906
  _define_property(this, "jwtService", void 0);
774
907
  _define_property(this, "authConfig", void 0);
775
908
  _define_property(this, "cacheManager", void 0);
@@ -777,7 +910,7 @@ export class AuthenticationService {
777
910
  _define_property(this, "sessionService", void 0);
778
911
  _define_property(this, "authEmailService", void 0);
779
912
  _define_property(this, "userEnricher", void 0);
780
- _define_property(this, "logger", void 0);
913
+ _define_property(this, "notificationAdapter", void 0);
781
914
  this.jwtService = jwtService;
782
915
  this.authConfig = authConfig;
783
916
  this.cacheManager = cacheManager;
@@ -785,9 +918,109 @@ export class AuthenticationService {
785
918
  this.sessionService = sessionService;
786
919
  this.authEmailService = authEmailService;
787
920
  this.userEnricher = userEnricher;
788
- this.logger = new Logger(AuthenticationService.name);
921
+ this.notificationAdapter = notificationAdapter;
789
922
  }
790
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);
791
1024
  AuthenticationService = _ts_decorate([
792
1025
  Injectable({
793
1026
  scope: Scope.REQUEST
@@ -804,6 +1037,8 @@ AuthenticationService = _ts_decorate([
804
1037
  _ts_param(5, Inject(AuthEmailService)),
805
1038
  _ts_param(6, Optional()),
806
1039
  _ts_param(6, Inject(USER_ENRICHER)),
1040
+ _ts_param(7, Optional()),
1041
+ _ts_param(7, Inject(NOTIFICATION_ADAPTER)),
807
1042
  _ts_metadata("design:type", Function),
808
1043
  _ts_metadata("design:paramtypes", [
809
1044
  typeof JwtService === "undefined" ? Object : JwtService,
@@ -812,6 +1047,7 @@ AuthenticationService = _ts_decorate([
812
1047
  typeof AuthDataSourceProvider === "undefined" ? Object : AuthDataSourceProvider,
813
1048
  typeof CompanySelectionSessionService === "undefined" ? Object : CompanySelectionSessionService,
814
1049
  typeof AuthEmailService === "undefined" ? Object : AuthEmailService,
815
- typeof IUserEnricher === "undefined" ? Object : IUserEnricher
1050
+ typeof IUserEnricher === "undefined" ? Object : IUserEnricher,
1051
+ typeof INotificationAdapter === "undefined" ? Object : INotificationAdapter
816
1052
  ])
817
1053
  ], AuthenticationService);