@flusys/nestjs-auth 1.1.0-beta → 2.0.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +590 -473
  2. package/cjs/config/auth.constants.js +8 -0
  3. package/cjs/controllers/authentication.controller.js +218 -39
  4. package/cjs/controllers/branch.controller.js +44 -1
  5. package/cjs/controllers/company-selection.controller.js +27 -20
  6. package/cjs/controllers/company.controller.js +44 -1
  7. package/cjs/controllers/user-permission.controller.js +82 -154
  8. package/cjs/controllers/user.controller.js +68 -15
  9. package/cjs/docs/auth-swagger.config.js +14 -7
  10. package/cjs/dtos/authentication.dto.js +16 -43
  11. package/cjs/dtos/index.js +0 -1
  12. package/cjs/dtos/user-permission.dto.js +28 -0
  13. package/cjs/dtos/user.dto.js +19 -4
  14. package/cjs/entities/user-company-permission.entity.js +0 -6
  15. package/cjs/interceptors/clear-token.interceptor.js +10 -23
  16. package/cjs/interceptors/set-token.interceptor.js +15 -55
  17. package/cjs/interfaces/auth-email-provider.interface.js +1 -1
  18. package/cjs/modules/auth.module.js +63 -147
  19. package/cjs/services/auth-config.service.js +22 -28
  20. package/cjs/services/auth-datasource.provider.js +9 -21
  21. package/cjs/services/auth-email.service.js +89 -85
  22. package/cjs/services/authentication.service.js +225 -396
  23. package/cjs/services/branch.service.js +9 -4
  24. package/cjs/services/company-selection-session.service.js +1 -1
  25. package/cjs/services/user-permission.service.js +43 -73
  26. package/cjs/services/user.service.js +78 -64
  27. package/cjs/strategies/jwt.strategy.js +44 -44
  28. package/cjs/validators/password.validator.js +45 -0
  29. package/config/auth.constants.d.ts +2 -0
  30. package/controllers/authentication.controller.d.ts +5 -3
  31. package/controllers/company-selection.controller.d.ts +6 -2
  32. package/controllers/user-permission.controller.d.ts +5 -4
  33. package/controllers/user.controller.d.ts +6 -0
  34. package/dtos/authentication.dto.d.ts +0 -4
  35. package/dtos/company-branch.dto.d.ts +0 -1
  36. package/dtos/company.dto.d.ts +0 -1
  37. package/dtos/index.d.ts +0 -1
  38. package/dtos/user-permission.dto.d.ts +4 -0
  39. package/dtos/user.dto.d.ts +1 -3
  40. package/entities/index.d.ts +3 -3
  41. package/entities/user-company-permission.entity.d.ts +0 -2
  42. package/fesm/config/auth.constants.js +6 -13
  43. package/fesm/controllers/authentication.controller.js +221 -42
  44. package/fesm/controllers/branch.controller.js +45 -2
  45. package/fesm/controllers/company-selection.controller.js +28 -21
  46. package/fesm/controllers/company.controller.js +45 -2
  47. package/fesm/controllers/user-permission.controller.js +84 -156
  48. package/fesm/controllers/user.controller.js +71 -18
  49. package/fesm/docs/auth-swagger.config.js +14 -7
  50. package/fesm/dtos/authentication.dto.js +17 -44
  51. package/fesm/dtos/index.js +0 -1
  52. package/fesm/dtos/user-permission.dto.js +28 -0
  53. package/fesm/dtos/user.dto.js +20 -5
  54. package/fesm/entities/user-company-permission.entity.js +0 -6
  55. package/fesm/interceptors/clear-token.interceptor.js +9 -22
  56. package/fesm/interceptors/set-token.interceptor.js +14 -54
  57. package/fesm/interfaces/auth-email-provider.interface.js +1 -1
  58. package/fesm/modules/auth.module.js +64 -148
  59. package/fesm/services/auth-config.service.js +23 -29
  60. package/fesm/services/auth-datasource.provider.js +10 -22
  61. package/fesm/services/auth-email.service.js +90 -86
  62. package/fesm/services/authentication.service.js +225 -396
  63. package/fesm/services/branch.service.js +9 -4
  64. package/fesm/services/company-selection-session.service.js +1 -1
  65. package/fesm/services/user-permission.service.js +44 -74
  66. package/fesm/services/user.service.js +79 -65
  67. package/fesm/strategies/jwt.strategy.js +44 -44
  68. package/fesm/validators/password.validator.js +48 -0
  69. package/interceptors/clear-token.interceptor.d.ts +0 -1
  70. package/interceptors/set-token.interceptor.d.ts +1 -1
  71. package/interfaces/authentication.interface.d.ts +0 -4
  72. package/interfaces/user-enricher.interface.d.ts +1 -4
  73. package/modules/auth.module.d.ts +6 -3
  74. package/package.json +4 -4
  75. package/services/auth-config.service.d.ts +6 -8
  76. package/services/auth-datasource.provider.d.ts +2 -4
  77. package/services/auth-email.service.d.ts +8 -4
  78. package/services/authentication.service.d.ts +15 -16
  79. package/services/company-selection-session.service.d.ts +1 -1
  80. package/services/user-permission.service.d.ts +6 -9
  81. package/services/user.service.d.ts +3 -0
  82. package/strategies/jwt.strategy.d.ts +4 -1
  83. package/validators/password.validator.d.ts +10 -0
  84. package/cjs/dtos/user-company-permission.dto.js +0 -110
  85. package/dtos/user-company-permission.dto.d.ts +0 -10
  86. package/fesm/dtos/user-company-permission.dto.js +0 -98
@@ -12,12 +12,12 @@ const _common = require("@nestjs/common");
12
12
  const _bcrypt = /*#__PURE__*/ _interop_require_wildcard(require("bcrypt"));
13
13
  const _crypto = /*#__PURE__*/ _interop_require_wildcard(require("crypto"));
14
14
  const _typeorm = require("typeorm");
15
- const _authdatasourceprovider = require("./auth-datasource.provider");
15
+ const _config = require("../config");
16
16
  const _pendingregistrationentity = require("../entities/pending-registration.entity");
17
17
  const _usertokenentity = require("../entities/user-token.entity");
18
18
  const _userentity = require("../entities/user.entity");
19
19
  const _interfaces = require("../interfaces");
20
- const _config = require("../config");
20
+ const _authdatasourceprovider = require("./auth-datasource.provider");
21
21
  function _define_property(obj, key, value) {
22
22
  if (key in obj) {
23
23
  Object.defineProperty(obj, key, {
@@ -87,41 +87,66 @@ function _ts_param(paramIndex, decorator) {
87
87
  };
88
88
  }
89
89
  let AuthEmailService = class AuthEmailService {
90
- async getUserRepository() {
91
- if (this.dataSourceProvider) {
92
- return this.dataSourceProvider.getRepository(_userentity.User);
93
- }
94
- throw new Error('User repository not available');
90
+ isEmailEnabled() {
91
+ return !!this.emailProvider;
95
92
  }
96
- async getUserTokenRepository() {
97
- if (this.dataSourceProvider) {
98
- return this.dataSourceProvider.getRepository(_usertokenentity.UserToken);
93
+ // ============================================
94
+ // Private Helpers
95
+ // ============================================
96
+ async getRepository(entity) {
97
+ if (!this.dataSourceProvider) {
98
+ throw new _common.InternalServerErrorException(`${String(entity)} repository not available`);
99
99
  }
100
- throw new Error('UserToken repository not available');
100
+ return this.dataSourceProvider.getRepository(entity);
101
+ }
102
+ generateToken() {
103
+ return _crypto.randomBytes(32).toString('hex');
101
104
  }
102
- async getPendingRegistrationRepository() {
103
- if (this.dataSourceProvider) {
104
- return this.dataSourceProvider.getRepository(_pendingregistrationentity.PendingRegistration);
105
+ /** Hash token for secure storage - prevents token theft if database is compromised */ hashToken(token) {
106
+ return _crypto.createHash('sha256').update(token).digest('hex');
107
+ }
108
+ getErrorMessage(error) {
109
+ return error instanceof Error ? error.message : 'Unknown error';
110
+ }
111
+ assertEmailEnabled() {
112
+ if (!this.emailProvider) {
113
+ throw new _common.BadRequestException('Email service is not configured');
105
114
  }
106
- throw new Error('PendingRegistration repository not available');
107
115
  }
108
- isEmailEnabled() {
109
- return !!this.emailProvider;
116
+ async sendVerificationEmailSafe(email, token, verifyUrl, logContext) {
117
+ try {
118
+ await this.emailProvider.sendVerificationEmail(email, token, verifyUrl);
119
+ this.logger.log(`${logContext} verification email sent to: ${email}`);
120
+ } catch (error) {
121
+ this.logger.error(`Failed to send verification email: ${this.getErrorMessage(error)}`);
122
+ throw new _common.BadRequestException('Failed to send verification email. Please try again.');
123
+ }
110
124
  }
111
- generateToken() {
112
- return _crypto.randomBytes(32).toString('hex');
125
+ validateTokenNotExpiredOrUsed(token, expiredMessage, usedMessage) {
126
+ if (token.expiresAt < new Date()) {
127
+ throw new _common.BadRequestException(expiredMessage);
128
+ }
129
+ if (usedMessage && token.usedAt) {
130
+ throw new _common.BadRequestException(usedMessage);
131
+ }
113
132
  }
114
133
  // ============================================
115
134
  // Pending Registration (New Users)
116
135
  // ============================================
117
- /**
118
- * Create pending registration - stores data until email verified
119
- */ async createPendingRegistration(data) {
120
- if (!this.emailProvider) {
121
- throw new _common.BadRequestException('Email service is not configured');
122
- }
123
- const userRepository = await this.getUserRepository();
124
- const pendingRepository = await this.getPendingRegistrationRepository();
136
+ async hasPendingRegistration(email) {
137
+ if (!this.isEmailEnabled()) return false;
138
+ const pendingRepository = await this.getRepository(_pendingregistrationentity.PendingRegistration);
139
+ const pending = await pendingRepository.findOne({
140
+ where: {
141
+ email
142
+ }
143
+ });
144
+ return !!pending && pending.expiresAt > new Date();
145
+ }
146
+ async createPendingRegistration(data) {
147
+ this.assertEmailEnabled();
148
+ const userRepository = await this.getRepository(_userentity.User);
149
+ const pendingRepository = await this.getRepository(_pendingregistrationentity.PendingRegistration);
125
150
  // Check if user already exists
126
151
  const existingUser = await userRepository.findOne({
127
152
  where: {
@@ -135,8 +160,9 @@ let AuthEmailService = class AuthEmailService {
135
160
  await pendingRepository.delete({
136
161
  email: data.email
137
162
  });
138
- // Create pending registration
163
+ // Create pending registration with hashed token for secure storage
139
164
  const token = this.generateToken();
165
+ const tokenHash = this.hashToken(token);
140
166
  const passwordHash = await _bcrypt.hash(data.password, _config.BCRYPT_SALT_ROUNDS);
141
167
  const expiresAt = new Date(Date.now() + this.EMAIL_VERIFICATION_EXPIRY);
142
168
  const pending = pendingRepository.create({
@@ -144,20 +170,14 @@ let AuthEmailService = class AuthEmailService {
144
170
  name: data.name ?? null,
145
171
  phone: data.phone ?? null,
146
172
  passwordHash,
147
- token,
173
+ token: tokenHash,
148
174
  expiresAt,
149
175
  companyData: data.companyData ?? null
150
176
  });
151
177
  await pendingRepository.save(pending);
152
- // Send verification email
178
+ // Send verification email with raw token (user receives this)
153
179
  const verifyUrl = `${data.verifyUrlBase}?token=${token}`;
154
- try {
155
- await this.emailProvider.sendVerificationEmail(data.email, token, verifyUrl);
156
- this.logger.log(`Registration verification email sent to: ${data.email}`);
157
- } catch (error) {
158
- this.logger.error(`Failed to send verification email: ${error.message}`);
159
- throw new _common.BadRequestException('Failed to send verification email. Please try again.');
160
- }
180
+ await this.sendVerificationEmailSafe(data.email, token, verifyUrl, 'Registration');
161
181
  return {
162
182
  success: true,
163
183
  message: 'Please check your email to verify your registration.'
@@ -169,11 +189,9 @@ let AuthEmailService = class AuthEmailService {
169
189
  /**
170
190
  * Request password reset - sends reset email
171
191
  */ async forgotPassword(email, resetUrlBase) {
172
- if (!this.emailProvider) {
173
- throw new _common.BadRequestException('Email service is not configured');
174
- }
175
- const userRepository = await this.getUserRepository();
176
- const tokenRepository = await this.getUserTokenRepository();
192
+ this.assertEmailEnabled();
193
+ const userRepository = await this.getRepository(_userentity.User);
194
+ const tokenRepository = await this.getRepository(_usertokenentity.UserToken);
177
195
  const user = await userRepository.findOne({
178
196
  where: {
179
197
  email
@@ -192,22 +210,24 @@ let AuthEmailService = class AuthEmailService {
192
210
  userId: user.id,
193
211
  type: _usertokenentity.TokenType.PASSWORD_RESET
194
212
  });
195
- // Create new token
213
+ // Create new token with hashed storage
196
214
  const token = this.generateToken();
215
+ const tokenHash = this.hashToken(token);
197
216
  const userToken = tokenRepository.create({
198
217
  userId: user.id,
199
218
  type: _usertokenentity.TokenType.PASSWORD_RESET,
200
- token,
219
+ token: tokenHash,
201
220
  expiresAt: new Date(Date.now() + this.PASSWORD_RESET_EXPIRY)
202
221
  });
203
222
  await tokenRepository.save(userToken);
204
- // Send email
223
+ // Send email with raw token
205
224
  const resetUrl = `${resetUrlBase}?token=${token}`;
206
225
  try {
207
226
  await this.emailProvider.sendPasswordResetEmail(email, token, resetUrl);
208
227
  this.logger.log(`Password reset email sent to: ${email}`);
209
228
  } catch (error) {
210
- this.logger.error(`Failed to send password reset email: ${error.message}`);
229
+ this.logger.error(`Failed to send password reset email: ${this.getErrorMessage(error)}`);
230
+ throw new _common.BadRequestException('Failed to process request. Please try again later.');
211
231
  }
212
232
  return {
213
233
  success: true,
@@ -217,26 +237,20 @@ let AuthEmailService = class AuthEmailService {
217
237
  /**
218
238
  * Reset password using token
219
239
  */ async resetPassword(token, newPassword) {
220
- const userRepository = await this.getUserRepository();
221
- const tokenRepository = await this.getUserTokenRepository();
240
+ const userRepository = await this.getRepository(_userentity.User);
241
+ const tokenRepository = await this.getRepository(_usertokenentity.UserToken);
242
+ // Hash the incoming token to compare with stored hash
243
+ const tokenHash = this.hashToken(token);
222
244
  const userToken = await tokenRepository.findOne({
223
245
  where: {
224
- token,
246
+ token: tokenHash,
225
247
  type: _usertokenentity.TokenType.PASSWORD_RESET
226
248
  }
227
249
  });
228
250
  if (!userToken) {
229
251
  throw new _common.BadRequestException('Invalid or expired reset token');
230
252
  }
231
- if (userToken.expiresAt < new Date()) {
232
- await tokenRepository.delete({
233
- id: userToken.id
234
- });
235
- throw new _common.BadRequestException('Reset token has expired. Please request a new one.');
236
- }
237
- if (userToken.usedAt) {
238
- throw new _common.BadRequestException('This reset token has already been used.');
239
- }
253
+ this.validateTokenNotExpiredOrUsed(userToken, 'Reset token has expired. Please request a new one.', 'This reset token has already been used.');
240
254
  const user = await userRepository.findOne({
241
255
  where: {
242
256
  id: userToken.userId
@@ -264,13 +278,15 @@ let AuthEmailService = class AuthEmailService {
264
278
  * Verify email using token
265
279
  * Handles both pending registration and existing user verification
266
280
  */ async verifyEmail(token) {
267
- const pendingRepository = await this.getPendingRegistrationRepository();
268
- const userRepository = await this.getUserRepository();
269
- const tokenRepository = await this.getUserTokenRepository();
281
+ const pendingRepository = await this.getRepository(_pendingregistrationentity.PendingRegistration);
282
+ const userRepository = await this.getRepository(_userentity.User);
283
+ const tokenRepository = await this.getRepository(_usertokenentity.UserToken);
284
+ // Hash the incoming token to compare with stored hash
285
+ const tokenHash = this.hashToken(token);
270
286
  // Check for pending registration first
271
287
  const pending = await pendingRepository.findOne({
272
288
  where: {
273
- token
289
+ token: tokenHash
274
290
  }
275
291
  });
276
292
  if (pending) {
@@ -292,7 +308,7 @@ let AuthEmailService = class AuthEmailService {
292
308
  // Check for existing user email verification token
293
309
  const userToken = await tokenRepository.findOne({
294
310
  where: {
295
- token,
311
+ token: tokenHash,
296
312
  type: _usertokenentity.TokenType.EMAIL_VERIFICATION
297
313
  }
298
314
  });
@@ -334,12 +350,10 @@ let AuthEmailService = class AuthEmailService {
334
350
  /**
335
351
  * Resend verification email - handles both pending registrations and existing users
336
352
  */ async resendVerificationEmail(email, verifyUrlBase) {
337
- if (!this.emailProvider) {
338
- throw new _common.BadRequestException('Email service is not configured');
339
- }
340
- const pendingRepository = await this.getPendingRegistrationRepository();
341
- const userRepository = await this.getUserRepository();
342
- const tokenRepository = await this.getUserTokenRepository();
353
+ this.assertEmailEnabled();
354
+ const pendingRepository = await this.getRepository(_pendingregistrationentity.PendingRegistration);
355
+ const userRepository = await this.getRepository(_userentity.User);
356
+ const tokenRepository = await this.getRepository(_usertokenentity.UserToken);
343
357
  // Check pending registration first
344
358
  const pending = await pendingRepository.findOne({
345
359
  where: {
@@ -348,16 +362,11 @@ let AuthEmailService = class AuthEmailService {
348
362
  });
349
363
  if (pending) {
350
364
  const token = this.generateToken();
351
- pending.token = token;
365
+ pending.token = this.hashToken(token);
352
366
  pending.expiresAt = new Date(Date.now() + this.EMAIL_VERIFICATION_EXPIRY);
353
367
  await pendingRepository.save(pending);
354
368
  const verifyUrl = `${verifyUrlBase}?token=${token}`;
355
- try {
356
- await this.emailProvider.sendVerificationEmail(email, token, verifyUrl);
357
- this.logger.log(`Re-sent registration verification email to: ${email}`);
358
- } catch (error) {
359
- this.logger.error(`Failed to resend verification email: ${error.message}`);
360
- }
369
+ await this.sendVerificationEmailSafe(email, token, verifyUrl, 'Re-sent registration');
361
370
  return {
362
371
  success: true,
363
372
  message: 'If the email requires verification, a link has been sent.'
@@ -384,17 +393,12 @@ let AuthEmailService = class AuthEmailService {
384
393
  const userToken = tokenRepository.create({
385
394
  userId: user.id,
386
395
  type: _usertokenentity.TokenType.EMAIL_VERIFICATION,
387
- token,
396
+ token: this.hashToken(token),
388
397
  expiresAt: new Date(Date.now() + this.EMAIL_VERIFICATION_EXPIRY)
389
398
  });
390
399
  await tokenRepository.save(userToken);
391
400
  const verifyUrl = `${verifyUrlBase}?token=${token}`;
392
- try {
393
- await this.emailProvider.sendVerificationEmail(user.email, token, verifyUrl);
394
- this.logger.log(`Verification email sent to: ${user.email}`);
395
- } catch (error) {
396
- this.logger.error(`Failed to send verification email: ${error.message}`);
397
- }
401
+ await this.sendVerificationEmailSafe(user.email, token, verifyUrl, 'Verification');
398
402
  return {
399
403
  success: true,
400
404
  message: 'If the email requires verification, a link has been sent.'
@@ -406,8 +410,8 @@ let AuthEmailService = class AuthEmailService {
406
410
  /**
407
411
  * Clean up expired tokens and pending registrations
408
412
  */ async cleanupExpired() {
409
- const tokenRepository = await this.getUserTokenRepository();
410
- const pendingRepository = await this.getPendingRegistrationRepository();
413
+ const tokenRepository = await this.getRepository(_usertokenentity.UserToken);
414
+ const pendingRepository = await this.getRepository(_pendingregistrationentity.PendingRegistration);
411
415
  const now = new Date();
412
416
  const tokenResult = await tokenRepository.delete({
413
417
  expiresAt: (0, _typeorm.LessThan)(now)