@flusys/nestjs-auth 3.0.0 → 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.
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 +6 -4
  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,6 +26,7 @@ function _ts_param(paramIndex, decorator) {
26
26
  };
27
27
  }
28
28
  import { createApiController, USER_PERMISSIONS } from '@flusys/nestjs-shared/classes';
29
+ import { USER_MESSAGES } from '../config';
29
30
  import { CurrentUser, RequirePermission } from '@flusys/nestjs-shared/decorators';
30
31
  import { JwtAuthGuard } from '@flusys/nestjs-shared/guards';
31
32
  import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
@@ -34,6 +35,7 @@ import { ApiBearerAuth, ApiBody, ApiOperation, ApiParam, ApiTags } from '@nestjs
34
35
  import { CreateUserDto, UpdateUserDto, UserResponseDto } from '../dtos/user.dto';
35
36
  import { UserService } from '../services/user.service';
36
37
  export class UserController extends createApiController(CreateUserDto, UpdateUserDto, UserResponseDto, {
38
+ entityName: 'user',
37
39
  security: {
38
40
  insert: {
39
41
  level: 'permission',
@@ -83,6 +85,8 @@ export class UserController extends createApiController(CreateUserDto, UpdateUse
83
85
  const data = await this.service.getProfileWithExtras(id, user);
84
86
  return {
85
87
  success: true,
88
+ message: 'Profile extras retrieved successfully',
89
+ messageKey: USER_MESSAGES.PROFILE_EXTRAS_SUCCESS,
86
90
  data
87
91
  };
88
92
  }
@@ -90,21 +94,24 @@ export class UserController extends createApiController(CreateUserDto, UpdateUse
90
94
  await this.service.verifyEmail(id);
91
95
  return {
92
96
  success: true,
93
- message: 'Email verified successfully'
97
+ message: 'Email verified successfully',
98
+ messageKey: USER_MESSAGES.EMAIL_VERIFY_SUCCESS
94
99
  };
95
100
  }
96
101
  async verifyPhone(id) {
97
102
  await this.service.verifyPhone(id);
98
103
  return {
99
104
  success: true,
100
- message: 'Phone verified successfully'
105
+ message: 'Phone verified successfully',
106
+ messageKey: USER_MESSAGES.PHONE_VERIFY_SUCCESS
101
107
  };
102
108
  }
103
109
  async updateStatus(id, isActive, user) {
104
110
  await this.service.updateStatus(id, isActive, user);
105
111
  return {
106
112
  success: true,
107
- message: 'Status updated successfully'
113
+ message: 'Status updated successfully',
114
+ messageKey: USER_MESSAGES.STATUS_UPDATE_SUCCESS
108
115
  };
109
116
  }
110
117
  async profile(dto, user) {
@@ -112,6 +119,7 @@ export class UserController extends createApiController(CreateUserDto, UpdateUse
112
119
  return {
113
120
  success: true,
114
121
  message: 'Profile updated successfully',
122
+ messageKey: USER_MESSAGES.PROFILE_UPDATE_SUCCESS,
115
123
  data
116
124
  };
117
125
  }
@@ -120,6 +128,7 @@ export class UserController extends createApiController(CreateUserDto, UpdateUse
120
128
  return {
121
129
  success: true,
122
130
  message: 'Profile sections retrieved successfully',
131
+ messageKey: USER_MESSAGES.PROFILE_SECTIONS_SUCCESS,
123
132
  data: sections
124
133
  };
125
134
  }
@@ -128,6 +137,7 @@ export class UserController extends createApiController(CreateUserDto, UpdateUse
128
137
  return {
129
138
  success: true,
130
139
  message: 'Profile section data retrieved successfully',
140
+ messageKey: USER_MESSAGES.PROFILE_SECTION_DATA_SUCCESS,
131
141
  data
132
142
  };
133
143
  }
@@ -135,7 +145,8 @@ export class UserController extends createApiController(CreateUserDto, UpdateUse
135
145
  await this.service.updateProfileSection(id, sectionId, data, user);
136
146
  return {
137
147
  success: true,
138
- message: 'Section updated successfully'
148
+ message: 'Section updated successfully',
149
+ messageKey: USER_MESSAGES.PROFILE_SECTION_UPDATE_SUCCESS
139
150
  };
140
151
  }
141
152
  async getProfileCompletion(id, user) {
@@ -143,6 +154,7 @@ export class UserController extends createApiController(CreateUserDto, UpdateUse
143
154
  return {
144
155
  success: true,
145
156
  message: 'Profile completion retrieved successfully',
157
+ messageKey: USER_MESSAGES.PROFILE_COMPLETION_SUCCESS,
146
158
  data: {
147
159
  completionPercentage: percentage
148
160
  }
@@ -613,6 +613,7 @@ export class LoginResponseRequiresEmailVerificationDto {
613
613
  _define_property(this, "requiresEmailVerification", void 0);
614
614
  _define_property(this, "email", void 0);
615
615
  _define_property(this, "message", void 0);
616
+ _define_property(this, "messageKey", void 0);
616
617
  }
617
618
  }
618
619
  _ts_decorate([
@@ -633,6 +634,13 @@ _ts_decorate([
633
634
  }),
634
635
  _ts_metadata("design:type", String)
635
636
  ], LoginResponseRequiresEmailVerificationDto.prototype, "message", void 0);
637
+ _ts_decorate([
638
+ ApiProperty({
639
+ example: 'auth.login.email.not.verified',
640
+ description: 'Translation key for localization'
641
+ }),
642
+ _ts_metadata("design:type", String)
643
+ ], LoginResponseRequiresEmailVerificationDto.prototype, "messageKey", void 0);
636
644
  export class ForgotPasswordDto {
637
645
  constructor(){
638
646
  _define_property(this, "email", void 0);
@@ -235,6 +235,7 @@ _ts_decorate([
235
235
  constructor(){
236
236
  _define_property(this, "success", void 0);
237
237
  _define_property(this, "message", void 0);
238
+ _define_property(this, "messageKey", void 0);
238
239
  _define_property(this, "data", void 0);
239
240
  }
240
241
  }
@@ -250,6 +251,12 @@ _ts_decorate([
250
251
  }),
251
252
  _ts_metadata("design:type", String)
252
253
  ], UserCompaniesResponseDto.prototype, "message", void 0);
254
+ _ts_decorate([
255
+ ApiProperty({
256
+ example: 'userPermission.company.list.success'
257
+ }),
258
+ _ts_metadata("design:type", String)
259
+ ], UserCompaniesResponseDto.prototype, "messageKey", void 0);
253
260
  _ts_decorate([
254
261
  ApiProperty({
255
262
  description: 'List of companies the user has access to',
@@ -281,6 +288,7 @@ _ts_decorate([
281
288
  constructor(){
282
289
  _define_property(this, "success", void 0);
283
290
  _define_property(this, "message", void 0);
291
+ _define_property(this, "messageKey", void 0);
284
292
  _define_property(this, "data", void 0);
285
293
  }
286
294
  }
@@ -296,6 +304,12 @@ _ts_decorate([
296
304
  }),
297
305
  _ts_metadata("design:type", String)
298
306
  ], UserBranchesResponseDto.prototype, "message", void 0);
307
+ _ts_decorate([
308
+ ApiProperty({
309
+ example: 'userPermission.branch.list.success'
310
+ }),
311
+ _ts_metadata("design:type", String)
312
+ ], UserBranchesResponseDto.prototype, "messageKey", void 0);
299
313
  _ts_decorate([
300
314
  ApiProperty({
301
315
  description: 'List of branches the user has access to',
@@ -26,6 +26,7 @@ function _ts_param(paramIndex, decorator) {
26
26
  };
27
27
  }
28
28
  import { buildCookieOptions, isBrowserRequest, parseDurationToMs } from '@flusys/nestjs-shared/utils';
29
+ import { AUTH_MESSAGES } from '../config';
29
30
  import { Inject, Injectable, Optional, PayloadTooLargeException } from '@nestjs/common';
30
31
  import { Buffer } from 'buffer';
31
32
  import { catchError, map, throwError } from 'rxjs';
@@ -42,10 +43,13 @@ export class SetToken {
42
43
  const req = context.switchToHttp().getRequest();
43
44
  const res = context.switchToHttp().getResponse();
44
45
  return next.handle().pipe(map((value)=>{
45
- if (!value?.refreshToken) return value;
46
+ if (typeof value !== 'object' || value === null || !('refreshToken' in value)) {
47
+ return value;
48
+ }
49
+ const response = value;
46
50
  if (isBrowserRequest(req)) {
47
- this.setRefreshTokenCookie(req, res, value.refreshToken);
48
- const { refreshToken, ...responseWithoutToken } = value;
51
+ this.setRefreshTokenCookie(req, res, String(response.refreshToken));
52
+ const { refreshToken, ...responseWithoutToken } = response;
49
53
  return responseWithoutToken;
50
54
  }
51
55
  return value;
@@ -55,7 +59,13 @@ export class SetToken {
55
59
  const rawCookieString = `${this.cookieName}=${tokenValue}; HttpOnly`;
56
60
  const estimatedBytes = Buffer.byteLength(rawCookieString, 'utf8');
57
61
  if (estimatedBytes > MAX_COOKIE_BYTES) {
58
- throw new PayloadTooLargeException(`Refresh token too large (≈${estimatedBytes} bytes)`);
62
+ throw new PayloadTooLargeException({
63
+ message: `Refresh token too large (≈${estimatedBytes} bytes)`,
64
+ messageKey: AUTH_MESSAGES.TOKEN_TOO_LARGE,
65
+ messageParams: {
66
+ size: estimatedBytes
67
+ }
68
+ });
59
69
  }
60
70
  res.cookie(this.cookieName, tokenValue, {
61
71
  httpOnly: true,
@@ -26,7 +26,8 @@ function _ts_param(paramIndex, decorator) {
26
26
  };
27
27
  }
28
28
  import { envConfig } from '@flusys/nestjs-core/config';
29
- import { Inject, Injectable, InternalServerErrorException, Logger, Optional } from '@nestjs/common';
29
+ import { SYSTEM_MESSAGES } from '@flusys/nestjs-shared/constants';
30
+ import { Inject, Injectable, InternalServerErrorException, Optional } from '@nestjs/common';
30
31
  import { AUTH_MODULE_OPTIONS } from '../config/auth.constants';
31
32
  import { AuthModuleOptions } from '../interfaces/auth-module-options.interface';
32
33
  export class AuthConfigService {
@@ -47,7 +48,10 @@ export class AuthConfigService {
47
48
  // JWT Configuration
48
49
  getJwtSecret() {
49
50
  if (!this.config.jwtSecret) {
50
- throw new InternalServerErrorException('[AuthModule] JWT_SECRET is required - set via environment or module config');
51
+ throw new InternalServerErrorException({
52
+ message: '[AuthModule] JWT_SECRET is required - set via environment or module config',
53
+ messageKey: SYSTEM_MESSAGES.CONFIG_REQUIRED
54
+ });
51
55
  }
52
56
  return this.config.jwtSecret;
53
57
  }
@@ -59,11 +63,10 @@ export class AuthConfigService {
59
63
  return this.config.refreshTokenSecret;
60
64
  }
61
65
  if (envConfig.isProduction()) {
62
- throw new InternalServerErrorException('[AuthModule] REFRESH_TOKEN_SECRET is required in production - ' + 'using the same secret for access and refresh tokens is a security risk');
63
- }
64
- if (!this.refreshTokenSecretWarned) {
65
- this.refreshTokenSecretWarned = true;
66
- this.logger.warn('REFRESH_TOKEN_SECRET not configured - using JWT_SECRET as fallback. ' + 'For better security, configure a separate refresh token secret.');
66
+ throw new InternalServerErrorException({
67
+ message: '[AuthModule] REFRESH_TOKEN_SECRET is required in production - using the same secret for access and refresh tokens is a security risk',
68
+ messageKey: SYSTEM_MESSAGES.CONFIG_REQUIRED
69
+ });
67
70
  }
68
71
  return this.getJwtSecret();
69
72
  }
@@ -78,10 +81,8 @@ export class AuthConfigService {
78
81
  return this.config.frontendUrl;
79
82
  }
80
83
  constructor(injectedOptions){
81
- _define_property(this, "logger", new Logger(AuthConfigService.name));
82
84
  _define_property(this, "options", void 0);
83
85
  _define_property(this, "config", void 0);
84
- _define_property(this, "refreshTokenSecretWarned", false);
85
86
  this.options = injectedOptions ?? {
86
87
  global: false,
87
88
  includeController: true
@@ -25,8 +25,8 @@ function _ts_param(paramIndex, decorator) {
25
25
  decorator(target, key, paramIndex);
26
26
  };
27
27
  }
28
- import { MultiTenantDataSourceService } from '@flusys/nestjs-shared/modules';
29
- import { Inject, Injectable, InternalServerErrorException, Logger, Optional, Scope } from '@nestjs/common';
28
+ import { MultiTenantDataSourceService, SYSTEM_MESSAGES } from '@flusys/nestjs-shared';
29
+ import { Inject, Injectable, InternalServerErrorException, Optional, Scope } from '@nestjs/common';
30
30
  import { REQUEST } from '@nestjs/core';
31
31
  import { Request } from 'express';
32
32
  import { AuthModuleOptions } from '../interfaces/auth-module-options.interface';
@@ -40,7 +40,10 @@ export class AuthDataSourceProvider extends MultiTenantDataSourceService {
40
40
  const lockPromise = (async ()=>{
41
41
  const config = this.getDefaultDatabaseConfig();
42
42
  if (!config) {
43
- throw new InternalServerErrorException('Default database config is not available');
43
+ throw new InternalServerErrorException({
44
+ message: 'Default database config is not available',
45
+ messageKey: SYSTEM_MESSAGES.DATABASE_CONFIG_NOT_AVAILABLE
46
+ });
44
47
  }
45
48
  const ds = await this.createDataSourceFromConfig(config);
46
49
  AuthDataSourceProvider.singleDataSource = ds;
@@ -103,7 +106,7 @@ export class AuthDataSourceProvider extends MultiTenantDataSourceService {
103
106
  return super.createDataSourceFromConfig(config, await this.getAuthEntities());
104
107
  }
105
108
  constructor(authOptions, request){
106
- super(AuthDataSourceProvider.buildParentOptions(authOptions), request), _define_property(this, "authOptions", void 0), _define_property(this, "logger", void 0), this.authOptions = authOptions, this.logger = new Logger(AuthDataSourceProvider.name);
109
+ super(AuthDataSourceProvider.buildParentOptions(authOptions), request), _define_property(this, "authOptions", void 0), this.authOptions = authOptions;
107
110
  }
108
111
  }
109
112
  _define_property(AuthDataSourceProvider, "tenantConnections", new Map());
@@ -25,7 +25,10 @@ function _ts_param(paramIndex, decorator) {
25
25
  decorator(target, key, paramIndex);
26
26
  };
27
27
  }
28
- import { BadRequestException, Inject, Injectable, InternalServerErrorException, Logger, Optional, Scope } from '@nestjs/common';
28
+ import { SYSTEM_MESSAGES } from '@flusys/nestjs-shared/constants';
29
+ import { LogAction } from '@flusys/nestjs-shared';
30
+ import { AUTH_MESSAGES, EMAIL_VERIFICATION_MESSAGES, USER_MESSAGES } from '../config';
31
+ import { BadRequestException, Inject, Injectable, InternalServerErrorException, Optional, Scope } from '@nestjs/common';
29
32
  import * as bcrypt from 'bcrypt';
30
33
  import * as crypto from 'crypto';
31
34
  import { LessThan } from 'typeorm';
@@ -41,7 +44,13 @@ export class AuthEmailService {
41
44
  }
42
45
  async getRepository(entity) {
43
46
  if (!this.dataSourceProvider) {
44
- throw new InternalServerErrorException(`${String(entity)} repository not available`);
47
+ throw new InternalServerErrorException({
48
+ message: `${String(entity)} repository not available`,
49
+ messageKey: SYSTEM_MESSAGES.REPOSITORY_NOT_AVAILABLE,
50
+ messageParams: {
51
+ entity: String(entity)
52
+ }
53
+ });
45
54
  }
46
55
  return this.dataSourceProvider.getRepository(entity);
47
56
  }
@@ -51,29 +60,36 @@ export class AuthEmailService {
51
60
  hashToken(token) {
52
61
  return crypto.createHash('sha256').update(token).digest('hex');
53
62
  }
54
- getErrorMessage(error) {
55
- return error instanceof Error ? error.message : 'Unknown error';
56
- }
57
63
  assertEmailEnabled() {
58
64
  if (!this.emailProvider) {
59
- throw new BadRequestException('Email service is not configured');
65
+ throw new BadRequestException({
66
+ message: 'Email service is not configured',
67
+ messageKey: AUTH_MESSAGES.EMAIL_NOT_CONFIGURED
68
+ });
60
69
  }
61
70
  }
62
- async sendVerificationEmailSafe(email, token, verifyUrl, logContext) {
71
+ async sendVerificationEmailSafe(email, token, verifyUrl) {
63
72
  try {
64
73
  await this.emailProvider.sendVerificationEmail(email, token, verifyUrl);
65
- this.logger.log(`${logContext} verification email sent to: ${email}`);
66
- } catch (error) {
67
- this.logger.error(`Failed to send verification email: ${this.getErrorMessage(error)}`);
68
- throw new BadRequestException('Failed to send verification email. Please try again.');
74
+ } catch {
75
+ throw new BadRequestException({
76
+ message: 'Failed to send verification email. Please try again.',
77
+ messageKey: AUTH_MESSAGES.EMAIL_SEND_FAILED
78
+ });
69
79
  }
70
80
  }
71
- validateTokenNotExpiredOrUsed(token, expiredMessage, usedMessage) {
81
+ validateTokenNotExpiredOrUsed(token, expiredMessage, expiredMessageKey, usedMessage, usedMessageKey) {
72
82
  if (token.expiresAt < new Date()) {
73
- throw new BadRequestException(expiredMessage);
83
+ throw new BadRequestException({
84
+ message: expiredMessage,
85
+ messageKey: expiredMessageKey
86
+ });
74
87
  }
75
- if (usedMessage && token.usedAt) {
76
- throw new BadRequestException(usedMessage);
88
+ if (usedMessage && usedMessageKey && token.usedAt) {
89
+ throw new BadRequestException({
90
+ message: usedMessage,
91
+ messageKey: usedMessageKey
92
+ });
77
93
  }
78
94
  }
79
95
  async hasPendingRegistration(email) {
@@ -97,7 +113,10 @@ export class AuthEmailService {
97
113
  }
98
114
  });
99
115
  if (existingUser) {
100
- throw new BadRequestException('Registration failed. Please try again or contact support.');
116
+ throw new BadRequestException({
117
+ message: 'Registration failed. Please try again or contact support.',
118
+ messageKey: AUTH_MESSAGES.REGISTER_FAILED
119
+ });
101
120
  }
102
121
  // Delete any existing pending registration for this email
103
122
  await pendingRepository.delete({
@@ -121,10 +140,11 @@ export class AuthEmailService {
121
140
  await pendingRepository.save(pending);
122
141
  // Send verification email with raw token (user receives this)
123
142
  const verifyUrl = `${data.verifyUrlBase}?token=${token}`;
124
- await this.sendVerificationEmailSafe(data.email, token, verifyUrl, 'Registration');
143
+ await this.sendVerificationEmailSafe(data.email, token, verifyUrl);
125
144
  return {
126
145
  success: true,
127
- message: 'Please check your email to verify your registration.'
146
+ message: 'Please check your email to verify your registration.',
147
+ messageKey: EMAIL_VERIFICATION_MESSAGES.VERIFICATION_SENT
128
148
  };
129
149
  }
130
150
  async forgotPassword(email, resetUrlBase) {
@@ -138,10 +158,10 @@ export class AuthEmailService {
138
158
  });
139
159
  // Always return success to prevent email enumeration
140
160
  if (!user || !user.isActive) {
141
- this.logger.warn(`Password reset requested for non-existent/inactive email: ${email}`);
142
161
  return {
143
162
  success: true,
144
- message: 'If the email exists, a password reset link has been sent.'
163
+ message: 'If the email exists, a password reset link has been sent.',
164
+ messageKey: AUTH_MESSAGES.PASSWORD_RESET_EMAIL_SENT
145
165
  };
146
166
  }
147
167
  // Invalidate existing password reset tokens for this user
@@ -163,14 +183,16 @@ export class AuthEmailService {
163
183
  const resetUrl = `${resetUrlBase}?token=${token}`;
164
184
  try {
165
185
  await this.emailProvider.sendPasswordResetEmail(email, token, resetUrl);
166
- this.logger.log(`Password reset email sent to: ${email}`);
167
- } catch (error) {
168
- this.logger.error(`Failed to send password reset email: ${this.getErrorMessage(error)}`);
169
- throw new BadRequestException('Failed to process request. Please try again later.');
186
+ } catch {
187
+ throw new BadRequestException({
188
+ message: 'Failed to process request. Please try again later.',
189
+ messageKey: SYSTEM_MESSAGES.INTERNAL_ERROR
190
+ });
170
191
  }
171
192
  return {
172
193
  success: true,
173
- message: 'If the email exists, a password reset link has been sent.'
194
+ message: 'If the email exists, a password reset link has been sent.',
195
+ messageKey: AUTH_MESSAGES.PASSWORD_RESET_EMAIL_SENT
174
196
  };
175
197
  }
176
198
  async resetPassword(token, newPassword) {
@@ -185,16 +207,22 @@ export class AuthEmailService {
185
207
  }
186
208
  });
187
209
  if (!userToken) {
188
- throw new BadRequestException('Invalid or expired reset token');
210
+ throw new BadRequestException({
211
+ message: 'Invalid or expired reset token',
212
+ messageKey: EMAIL_VERIFICATION_MESSAGES.VERIFICATION_TOKEN_INVALID
213
+ });
189
214
  }
190
- this.validateTokenNotExpiredOrUsed(userToken, 'Reset token has expired. Please request a new one.', 'This reset token has already been used.');
215
+ this.validateTokenNotExpiredOrUsed(userToken, 'Reset token has expired. Please request a new one.', AUTH_MESSAGES.PASSWORD_RESET_TOKEN_EXPIRED, 'This reset token has already been used.', AUTH_MESSAGES.PASSWORD_RESET_TOKEN_USED);
191
216
  const user = await userRepository.findOne({
192
217
  where: {
193
218
  id: userToken.userId
194
219
  }
195
220
  });
196
221
  if (!user) {
197
- throw new BadRequestException('User not found');
222
+ throw new BadRequestException({
223
+ message: 'User not found',
224
+ messageKey: USER_MESSAGES.NOT_FOUND
225
+ });
198
226
  }
199
227
  // Update password
200
228
  user.password = await bcrypt.hash(newPassword, BCRYPT_SALT_ROUNDS);
@@ -202,10 +230,10 @@ export class AuthEmailService {
202
230
  // Mark token as used
203
231
  userToken.usedAt = new Date();
204
232
  await tokenRepository.save(userToken);
205
- this.logger.log(`Password reset successful for user: ${user.email}`);
206
233
  return {
207
234
  success: true,
208
- message: 'Password has been reset successfully.'
235
+ message: 'Password has been reset successfully.',
236
+ messageKey: AUTH_MESSAGES.PASSWORD_RESET_SUCCESS
209
237
  };
210
238
  }
211
239
  async verifyEmail(token) {
@@ -225,7 +253,10 @@ export class AuthEmailService {
225
253
  await pendingRepository.delete({
226
254
  id: pending.id
227
255
  });
228
- throw new BadRequestException('Verification token has expired. Please register again.');
256
+ throw new BadRequestException({
257
+ message: 'Verification token has expired. Please register again.',
258
+ messageKey: EMAIL_VERIFICATION_MESSAGES.VERIFICATION_TOKEN_EXPIRED
259
+ });
229
260
  }
230
261
  // Delete pending registration (will be consumed by caller to create user)
231
262
  await pendingRepository.delete({
@@ -244,18 +275,25 @@ export class AuthEmailService {
244
275
  }
245
276
  });
246
277
  if (!userToken) {
247
- throw new BadRequestException('Invalid or expired verification token');
278
+ throw new BadRequestException({
279
+ message: 'Invalid or expired verification token',
280
+ messageKey: EMAIL_VERIFICATION_MESSAGES.VERIFICATION_TOKEN_INVALID
281
+ });
248
282
  }
249
283
  if (userToken.expiresAt < new Date()) {
250
284
  await tokenRepository.delete({
251
285
  id: userToken.id
252
286
  });
253
- throw new BadRequestException('Verification token has expired. Please request a new one.');
287
+ throw new BadRequestException({
288
+ message: 'Verification token has expired. Please request a new one.',
289
+ messageKey: EMAIL_VERIFICATION_MESSAGES.VERIFICATION_TOKEN_EXPIRED
290
+ });
254
291
  }
255
292
  if (userToken.usedAt) {
256
293
  return {
257
294
  type: 'verified',
258
- message: 'Email has already been verified.'
295
+ message: 'Email has already been verified.',
296
+ messageKey: EMAIL_VERIFICATION_MESSAGES.VERIFICATION_SUCCESS
259
297
  };
260
298
  }
261
299
  const user = await userRepository.findOne({
@@ -264,7 +302,10 @@ export class AuthEmailService {
264
302
  }
265
303
  });
266
304
  if (!user) {
267
- throw new BadRequestException('User not found');
305
+ throw new BadRequestException({
306
+ message: 'User not found',
307
+ messageKey: USER_MESSAGES.NOT_FOUND
308
+ });
268
309
  }
269
310
  // Mark email as verified
270
311
  user.emailVerified = true;
@@ -272,10 +313,10 @@ export class AuthEmailService {
272
313
  // Mark token as used
273
314
  userToken.usedAt = new Date();
274
315
  await tokenRepository.save(userToken);
275
- this.logger.log(`Email verified for user: ${user.email}`);
276
316
  return {
277
317
  type: 'verified',
278
- message: 'Email has been verified successfully.'
318
+ message: 'Email has been verified successfully.',
319
+ messageKey: EMAIL_VERIFICATION_MESSAGES.VERIFICATION_SUCCESS
279
320
  };
280
321
  }
281
322
  async resendVerificationEmail(email, verifyUrlBase) {
@@ -295,10 +336,11 @@ export class AuthEmailService {
295
336
  pending.expiresAt = new Date(Date.now() + this.EMAIL_VERIFICATION_EXPIRY);
296
337
  await pendingRepository.save(pending);
297
338
  const verifyUrl = `${verifyUrlBase}?token=${token}`;
298
- await this.sendVerificationEmailSafe(email, token, verifyUrl, 'Re-sent registration');
339
+ await this.sendVerificationEmailSafe(email, token, verifyUrl);
299
340
  return {
300
341
  success: true,
301
- message: 'If the email requires verification, a link has been sent.'
342
+ message: 'If the email requires verification, a link has been sent.',
343
+ messageKey: EMAIL_VERIFICATION_MESSAGES.RESEND_SUCCESS
302
344
  };
303
345
  }
304
346
  // Check existing user
@@ -310,7 +352,8 @@ export class AuthEmailService {
310
352
  if (!user || user.emailVerified) {
311
353
  return {
312
354
  success: true,
313
- message: 'If the email requires verification, a link has been sent.'
355
+ message: 'If the email requires verification, a link has been sent.',
356
+ messageKey: EMAIL_VERIFICATION_MESSAGES.RESEND_SUCCESS
314
357
  };
315
358
  }
316
359
  // Invalidate existing tokens and create new one
@@ -327,10 +370,11 @@ export class AuthEmailService {
327
370
  });
328
371
  await tokenRepository.save(userToken);
329
372
  const verifyUrl = `${verifyUrlBase}?token=${token}`;
330
- await this.sendVerificationEmailSafe(user.email, token, verifyUrl, 'Verification');
373
+ await this.sendVerificationEmailSafe(user.email, token, verifyUrl);
331
374
  return {
332
375
  success: true,
333
- message: 'If the email requires verification, a link has been sent.'
376
+ message: 'If the email requires verification, a link has been sent.',
377
+ messageKey: EMAIL_VERIFICATION_MESSAGES.RESEND_SUCCESS
334
378
  };
335
379
  }
336
380
  async cleanupExpired() {
@@ -345,7 +389,6 @@ export class AuthEmailService {
345
389
  });
346
390
  const deletedTokens = tokenResult.affected ?? 0;
347
391
  const deletedPending = pendingResult.affected ?? 0;
348
- this.logger.log(`Cleaned up ${deletedTokens} expired tokens and ${deletedPending} expired pending registrations`);
349
392
  return {
350
393
  deletedTokens,
351
394
  deletedPending
@@ -354,16 +397,84 @@ export class AuthEmailService {
354
397
  constructor(dataSourceProvider, emailProvider){
355
398
  _define_property(this, "dataSourceProvider", void 0);
356
399
  _define_property(this, "emailProvider", void 0);
357
- _define_property(this, "logger", void 0);
358
400
  _define_property(this, "PASSWORD_RESET_EXPIRY", void 0); // 1 hour
359
401
  _define_property(this, "EMAIL_VERIFICATION_EXPIRY", void 0); // 24 hours
360
402
  this.dataSourceProvider = dataSourceProvider;
361
403
  this.emailProvider = emailProvider;
362
- this.logger = new Logger(AuthEmailService.name);
363
404
  this.PASSWORD_RESET_EXPIRY = 60 * 60 * 1000;
364
405
  this.EMAIL_VERIFICATION_EXPIRY = 24 * 60 * 60 * 1000;
365
406
  }
366
407
  }
408
+ _ts_decorate([
409
+ LogAction({
410
+ action: 'auth.createPendingRegistration',
411
+ module: 'auth',
412
+ includeParams: false
413
+ }),
414
+ _ts_metadata("design:type", Function),
415
+ _ts_metadata("design:paramtypes", [
416
+ Object
417
+ ]),
418
+ _ts_metadata("design:returntype", Promise)
419
+ ], AuthEmailService.prototype, "createPendingRegistration", null);
420
+ _ts_decorate([
421
+ LogAction({
422
+ action: 'auth.forgotPassword',
423
+ module: 'auth',
424
+ includeParams: false
425
+ }),
426
+ _ts_metadata("design:type", Function),
427
+ _ts_metadata("design:paramtypes", [
428
+ String,
429
+ String
430
+ ]),
431
+ _ts_metadata("design:returntype", Promise)
432
+ ], AuthEmailService.prototype, "forgotPassword", null);
433
+ _ts_decorate([
434
+ LogAction({
435
+ action: 'auth.resetPassword',
436
+ module: 'auth',
437
+ includeParams: false
438
+ }),
439
+ _ts_metadata("design:type", Function),
440
+ _ts_metadata("design:paramtypes", [
441
+ String,
442
+ String
443
+ ]),
444
+ _ts_metadata("design:returntype", Promise)
445
+ ], AuthEmailService.prototype, "resetPassword", null);
446
+ _ts_decorate([
447
+ LogAction({
448
+ action: 'auth.verifyEmailToken',
449
+ module: 'auth'
450
+ }),
451
+ _ts_metadata("design:type", Function),
452
+ _ts_metadata("design:paramtypes", [
453
+ String
454
+ ]),
455
+ _ts_metadata("design:returntype", Promise)
456
+ ], AuthEmailService.prototype, "verifyEmail", null);
457
+ _ts_decorate([
458
+ LogAction({
459
+ action: 'auth.sendVerificationEmail',
460
+ module: 'auth'
461
+ }),
462
+ _ts_metadata("design:type", Function),
463
+ _ts_metadata("design:paramtypes", [
464
+ String,
465
+ String
466
+ ]),
467
+ _ts_metadata("design:returntype", Promise)
468
+ ], AuthEmailService.prototype, "resendVerificationEmail", null);
469
+ _ts_decorate([
470
+ LogAction({
471
+ action: 'auth.cleanupExpiredTokens',
472
+ module: 'auth'
473
+ }),
474
+ _ts_metadata("design:type", Function),
475
+ _ts_metadata("design:paramtypes", []),
476
+ _ts_metadata("design:returntype", Promise)
477
+ ], AuthEmailService.prototype, "cleanupExpired", null);
367
478
  AuthEmailService = _ts_decorate([
368
479
  Injectable({
369
480
  scope: Scope.REQUEST