@flusys/nestjs-auth 1.0.0-rc → 1.0.1

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 (61) hide show
  1. package/README.md +530 -396
  2. package/cjs/controllers/authentication.controller.js +160 -28
  3. package/cjs/controllers/user-permission.controller.js +49 -136
  4. package/cjs/docs/auth-swagger.config.js +14 -7
  5. package/cjs/dtos/authentication.dto.js +0 -30
  6. package/cjs/dtos/index.js +0 -1
  7. package/cjs/entities/user-company-permission.entity.js +3 -3
  8. package/cjs/interceptors/clear-token.interceptor.js +8 -18
  9. package/cjs/interceptors/set-token.interceptor.js +8 -55
  10. package/cjs/interfaces/auth-email-provider.interface.js +1 -1
  11. package/cjs/modules/auth.module.js +63 -129
  12. package/cjs/services/auth-config.service.js +6 -29
  13. package/cjs/services/auth-datasource.provider.js +8 -20
  14. package/cjs/services/auth-email.service.js +72 -87
  15. package/cjs/services/authentication.service.js +211 -355
  16. package/cjs/services/branch.service.js +9 -5
  17. package/cjs/services/user-permission.service.js +43 -71
  18. package/cjs/services/user.service.js +18 -47
  19. package/cjs/strategies/jwt.strategy.js +44 -44
  20. package/controllers/authentication.controller.d.ts +5 -3
  21. package/controllers/user-permission.controller.d.ts +5 -4
  22. package/dtos/authentication.dto.d.ts +0 -4
  23. package/dtos/index.d.ts +0 -1
  24. package/entities/index.d.ts +3 -3
  25. package/entities/user-company-permission.entity.d.ts +1 -1
  26. package/fesm/controllers/authentication.controller.js +162 -30
  27. package/fesm/controllers/user-permission.controller.js +50 -137
  28. package/fesm/docs/auth-swagger.config.js +14 -7
  29. package/fesm/dtos/authentication.dto.js +0 -30
  30. package/fesm/dtos/index.js +0 -1
  31. package/fesm/entities/user-company-permission.entity.js +3 -3
  32. package/fesm/interceptors/clear-token.interceptor.js +8 -18
  33. package/fesm/interceptors/set-token.interceptor.js +8 -55
  34. package/fesm/interfaces/auth-email-provider.interface.js +1 -1
  35. package/fesm/modules/auth.module.js +63 -129
  36. package/fesm/services/auth-config.service.js +6 -29
  37. package/fesm/services/auth-datasource.provider.js +8 -20
  38. package/fesm/services/auth-email.service.js +72 -87
  39. package/fesm/services/authentication.service.js +211 -355
  40. package/fesm/services/branch.service.js +9 -5
  41. package/fesm/services/user-permission.service.js +44 -72
  42. package/fesm/services/user.service.js +18 -47
  43. package/fesm/strategies/jwt.strategy.js +44 -44
  44. package/interceptors/clear-token.interceptor.d.ts +0 -1
  45. package/interceptors/set-token.interceptor.d.ts +1 -2
  46. package/interfaces/authentication.interface.d.ts +0 -4
  47. package/modules/auth.module.d.ts +6 -2
  48. package/package.json +3 -3
  49. package/services/auth-config.service.d.ts +2 -7
  50. package/services/auth-datasource.provider.d.ts +2 -4
  51. package/services/auth-email.service.d.ts +7 -4
  52. package/services/authentication.service.d.ts +14 -8
  53. package/services/user-permission.service.d.ts +6 -8
  54. package/services/user.service.d.ts +1 -1
  55. package/strategies/jwt.strategy.d.ts +4 -1
  56. package/cjs/dtos/user-company-permission.dto.js +0 -110
  57. package/cjs/guards/auth-throttler.guard.js +0 -53
  58. package/dtos/user-company-permission.dto.d.ts +0 -10
  59. package/fesm/dtos/user-company-permission.dto.js +0 -98
  60. package/fesm/guards/auth-throttler.guard.js +0 -43
  61. package/guards/auth-throttler.guard.d.ts +0 -7
@@ -29,51 +29,73 @@ import { BadRequestException, Inject, Injectable, InternalServerErrorException,
29
29
  import * as bcrypt from 'bcrypt';
30
30
  import * as crypto from 'crypto';
31
31
  import { LessThan } from 'typeorm';
32
- import { AuthDataSourceProvider } from './auth-datasource.provider';
32
+ import { BCRYPT_SALT_ROUNDS } from '../config';
33
33
  import { PendingRegistration } from '../entities/pending-registration.entity';
34
34
  import { TokenType, UserToken } from '../entities/user-token.entity';
35
35
  import { User } from '../entities/user.entity';
36
36
  import { AUTH_EMAIL_PROVIDER, IAuthEmailProvider } from '../interfaces';
37
- import { BCRYPT_SALT_ROUNDS } from '../config';
37
+ import { AuthDataSourceProvider } from './auth-datasource.provider';
38
38
  export class AuthEmailService {
39
- async getUserRepository() {
40
- if (this.dataSourceProvider) {
41
- return this.dataSourceProvider.getRepository(User);
42
- }
43
- throw new InternalServerErrorException('User repository not available');
44
- }
45
- async getUserTokenRepository() {
46
- if (this.dataSourceProvider) {
47
- return this.dataSourceProvider.getRepository(UserToken);
48
- }
49
- throw new InternalServerErrorException('UserToken repository not available');
50
- }
51
- async getPendingRegistrationRepository() {
52
- if (this.dataSourceProvider) {
53
- return this.dataSourceProvider.getRepository(PendingRegistration);
54
- }
55
- throw new InternalServerErrorException('PendingRegistration repository not available');
56
- }
57
39
  isEmailEnabled() {
58
40
  return !!this.emailProvider;
59
41
  }
42
+ // ============================================
43
+ // Private Helpers
44
+ // ============================================
45
+ async getRepository(entity) {
46
+ if (!this.dataSourceProvider) {
47
+ throw new InternalServerErrorException(`${String(entity)} repository not available`);
48
+ }
49
+ return this.dataSourceProvider.getRepository(entity);
50
+ }
60
51
  generateToken() {
61
52
  return crypto.randomBytes(32).toString('hex');
62
53
  }
63
54
  /** Hash token for secure storage - prevents token theft if database is compromised */ hashToken(token) {
64
55
  return crypto.createHash('sha256').update(token).digest('hex');
65
56
  }
66
- // ============================================
67
- // Pending Registration (New Users)
68
- // ============================================
69
- /**
70
- * Create pending registration - stores data until email verified
71
- */ async createPendingRegistration(data) {
57
+ getErrorMessage(error) {
58
+ return error instanceof Error ? error.message : 'Unknown error';
59
+ }
60
+ assertEmailEnabled() {
72
61
  if (!this.emailProvider) {
73
62
  throw new BadRequestException('Email service is not configured');
74
63
  }
75
- const userRepository = await this.getUserRepository();
76
- const pendingRepository = await this.getPendingRegistrationRepository();
64
+ }
65
+ async sendVerificationEmailSafe(email, token, verifyUrl, logContext) {
66
+ try {
67
+ await this.emailProvider.sendVerificationEmail(email, token, verifyUrl);
68
+ this.logger.log(`${logContext} verification email sent to: ${email}`);
69
+ } catch (error) {
70
+ this.logger.error(`Failed to send verification email: ${this.getErrorMessage(error)}`);
71
+ throw new BadRequestException('Failed to send verification email. Please try again.');
72
+ }
73
+ }
74
+ validateTokenNotExpiredOrUsed(token, expiredMessage, usedMessage) {
75
+ if (token.expiresAt < new Date()) {
76
+ throw new BadRequestException(expiredMessage);
77
+ }
78
+ if (usedMessage && token.usedAt) {
79
+ throw new BadRequestException(usedMessage);
80
+ }
81
+ }
82
+ // ============================================
83
+ // Pending Registration (New Users)
84
+ // ============================================
85
+ async hasPendingRegistration(email) {
86
+ if (!this.isEmailEnabled()) return false;
87
+ const pendingRepository = await this.getRepository(PendingRegistration);
88
+ const pending = await pendingRepository.findOne({
89
+ where: {
90
+ email
91
+ }
92
+ });
93
+ return !!pending && pending.expiresAt > new Date();
94
+ }
95
+ async createPendingRegistration(data) {
96
+ this.assertEmailEnabled();
97
+ const userRepository = await this.getRepository(User);
98
+ const pendingRepository = await this.getRepository(PendingRegistration);
77
99
  // Check if user already exists
78
100
  const existingUser = await userRepository.findOne({
79
101
  where: {
@@ -104,14 +126,7 @@ export class AuthEmailService {
104
126
  await pendingRepository.save(pending);
105
127
  // Send verification email with raw token (user receives this)
106
128
  const verifyUrl = `${data.verifyUrlBase}?token=${token}`;
107
- try {
108
- await this.emailProvider.sendVerificationEmail(data.email, token, verifyUrl);
109
- this.logger.log(`Registration verification email sent to: ${data.email}`);
110
- } catch (error) {
111
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
112
- this.logger.error(`Failed to send verification email: ${errorMessage}`);
113
- throw new BadRequestException('Failed to send verification email. Please try again.');
114
- }
129
+ await this.sendVerificationEmailSafe(data.email, token, verifyUrl, 'Registration');
115
130
  return {
116
131
  success: true,
117
132
  message: 'Please check your email to verify your registration.'
@@ -123,11 +138,9 @@ export class AuthEmailService {
123
138
  /**
124
139
  * Request password reset - sends reset email
125
140
  */ async forgotPassword(email, resetUrlBase) {
126
- if (!this.emailProvider) {
127
- throw new BadRequestException('Email service is not configured');
128
- }
129
- const userRepository = await this.getUserRepository();
130
- const tokenRepository = await this.getUserTokenRepository();
141
+ this.assertEmailEnabled();
142
+ const userRepository = await this.getRepository(User);
143
+ const tokenRepository = await this.getRepository(UserToken);
131
144
  const user = await userRepository.findOne({
132
145
  where: {
133
146
  email
@@ -162,9 +175,7 @@ export class AuthEmailService {
162
175
  await this.emailProvider.sendPasswordResetEmail(email, token, resetUrl);
163
176
  this.logger.log(`Password reset email sent to: ${email}`);
164
177
  } catch (error) {
165
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
166
- this.logger.error(`Failed to send password reset email: ${errorMessage}`);
167
- // Don't silently fail - but still use same message to prevent enumeration
178
+ this.logger.error(`Failed to send password reset email: ${this.getErrorMessage(error)}`);
168
179
  throw new BadRequestException('Failed to process request. Please try again later.');
169
180
  }
170
181
  return {
@@ -175,8 +186,8 @@ export class AuthEmailService {
175
186
  /**
176
187
  * Reset password using token
177
188
  */ async resetPassword(token, newPassword) {
178
- const userRepository = await this.getUserRepository();
179
- const tokenRepository = await this.getUserTokenRepository();
189
+ const userRepository = await this.getRepository(User);
190
+ const tokenRepository = await this.getRepository(UserToken);
180
191
  // Hash the incoming token to compare with stored hash
181
192
  const tokenHash = this.hashToken(token);
182
193
  const userToken = await tokenRepository.findOne({
@@ -188,15 +199,7 @@ export class AuthEmailService {
188
199
  if (!userToken) {
189
200
  throw new BadRequestException('Invalid or expired reset token');
190
201
  }
191
- if (userToken.expiresAt < new Date()) {
192
- await tokenRepository.delete({
193
- id: userToken.id
194
- });
195
- throw new BadRequestException('Reset token has expired. Please request a new one.');
196
- }
197
- if (userToken.usedAt) {
198
- throw new BadRequestException('This reset token has already been used.');
199
- }
202
+ this.validateTokenNotExpiredOrUsed(userToken, 'Reset token has expired. Please request a new one.', 'This reset token has already been used.');
200
203
  const user = await userRepository.findOne({
201
204
  where: {
202
205
  id: userToken.userId
@@ -224,9 +227,9 @@ export class AuthEmailService {
224
227
  * Verify email using token
225
228
  * Handles both pending registration and existing user verification
226
229
  */ async verifyEmail(token) {
227
- const pendingRepository = await this.getPendingRegistrationRepository();
228
- const userRepository = await this.getUserRepository();
229
- const tokenRepository = await this.getUserTokenRepository();
230
+ const pendingRepository = await this.getRepository(PendingRegistration);
231
+ const userRepository = await this.getRepository(User);
232
+ const tokenRepository = await this.getRepository(UserToken);
230
233
  // Hash the incoming token to compare with stored hash
231
234
  const tokenHash = this.hashToken(token);
232
235
  // Check for pending registration first
@@ -251,7 +254,7 @@ export class AuthEmailService {
251
254
  pending
252
255
  };
253
256
  }
254
- // Check for existing user email verification token (token already hashed above)
257
+ // Check for existing user email verification token
255
258
  const userToken = await tokenRepository.findOne({
256
259
  where: {
257
260
  token: tokenHash,
@@ -296,12 +299,10 @@ export class AuthEmailService {
296
299
  /**
297
300
  * Resend verification email - handles both pending registrations and existing users
298
301
  */ async resendVerificationEmail(email, verifyUrlBase) {
299
- if (!this.emailProvider) {
300
- throw new BadRequestException('Email service is not configured');
301
- }
302
- const pendingRepository = await this.getPendingRegistrationRepository();
303
- const userRepository = await this.getUserRepository();
304
- const tokenRepository = await this.getUserTokenRepository();
302
+ this.assertEmailEnabled();
303
+ const pendingRepository = await this.getRepository(PendingRegistration);
304
+ const userRepository = await this.getRepository(User);
305
+ const tokenRepository = await this.getRepository(UserToken);
305
306
  // Check pending registration first
306
307
  const pending = await pendingRepository.findOne({
307
308
  where: {
@@ -310,19 +311,11 @@ export class AuthEmailService {
310
311
  });
311
312
  if (pending) {
312
313
  const token = this.generateToken();
313
- const tokenHash = this.hashToken(token);
314
- pending.token = tokenHash; // Store hashed token
314
+ pending.token = this.hashToken(token);
315
315
  pending.expiresAt = new Date(Date.now() + this.EMAIL_VERIFICATION_EXPIRY);
316
316
  await pendingRepository.save(pending);
317
317
  const verifyUrl = `${verifyUrlBase}?token=${token}`;
318
- try {
319
- await this.emailProvider.sendVerificationEmail(email, token, verifyUrl);
320
- this.logger.log(`Re-sent registration verification email to: ${email}`);
321
- } catch (error) {
322
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
323
- this.logger.error(`Failed to resend verification email: ${errorMessage}`);
324
- throw new BadRequestException('Failed to send verification email. Please try again.');
325
- }
318
+ await this.sendVerificationEmailSafe(email, token, verifyUrl, 'Re-sent registration');
326
319
  return {
327
320
  success: true,
328
321
  message: 'If the email requires verification, a link has been sent.'
@@ -346,23 +339,15 @@ export class AuthEmailService {
346
339
  type: TokenType.EMAIL_VERIFICATION
347
340
  });
348
341
  const token = this.generateToken();
349
- const tokenHash = this.hashToken(token);
350
342
  const userToken = tokenRepository.create({
351
343
  userId: user.id,
352
344
  type: TokenType.EMAIL_VERIFICATION,
353
- token: tokenHash,
345
+ token: this.hashToken(token),
354
346
  expiresAt: new Date(Date.now() + this.EMAIL_VERIFICATION_EXPIRY)
355
347
  });
356
348
  await tokenRepository.save(userToken);
357
349
  const verifyUrl = `${verifyUrlBase}?token=${token}`;
358
- try {
359
- await this.emailProvider.sendVerificationEmail(user.email, token, verifyUrl);
360
- this.logger.log(`Verification email sent to: ${user.email}`);
361
- } catch (error) {
362
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
363
- this.logger.error(`Failed to send verification email: ${errorMessage}`);
364
- throw new BadRequestException('Failed to send verification email. Please try again.');
365
- }
350
+ await this.sendVerificationEmailSafe(user.email, token, verifyUrl, 'Verification');
366
351
  return {
367
352
  success: true,
368
353
  message: 'If the email requires verification, a link has been sent.'
@@ -374,8 +359,8 @@ export class AuthEmailService {
374
359
  /**
375
360
  * Clean up expired tokens and pending registrations
376
361
  */ async cleanupExpired() {
377
- const tokenRepository = await this.getUserTokenRepository();
378
- const pendingRepository = await this.getPendingRegistrationRepository();
362
+ const tokenRepository = await this.getRepository(UserToken);
363
+ const pendingRepository = await this.getRepository(PendingRegistration);
379
364
  const now = new Date();
380
365
  const tokenResult = await tokenRepository.delete({
381
366
  expiresAt: LessThan(now)