@flusys/nestjs-auth 0.1.0-beta.3 → 1.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 (102) hide show
  1. package/README.md +274 -301
  2. package/cjs/config/auth.constants.js +22 -4
  3. package/cjs/controllers/authentication.controller.js +222 -38
  4. package/cjs/controllers/branch.controller.js +46 -4
  5. package/cjs/controllers/company-selection.controller.js +27 -20
  6. package/cjs/controllers/company.controller.js +46 -4
  7. package/cjs/controllers/user-permission.controller.js +40 -37
  8. package/cjs/controllers/user.controller.js +222 -23
  9. package/cjs/dtos/authentication.dto.js +119 -13
  10. package/cjs/dtos/user-permission.dto.js +28 -0
  11. package/cjs/dtos/user.dto.js +19 -4
  12. package/cjs/entities/index.js +5 -1
  13. package/cjs/entities/pending-registration.entity.js +125 -0
  14. package/cjs/entities/user-company-permission.entity.js +0 -6
  15. package/cjs/entities/user-token.entity.js +129 -0
  16. package/cjs/guards/auth-throttler.guard.js +53 -0
  17. package/cjs/interceptors/clear-token.interceptor.js +3 -6
  18. package/cjs/interceptors/set-token.interceptor.js +38 -31
  19. package/cjs/interfaces/auth-email-provider.interface.js +45 -0
  20. package/cjs/interfaces/authentication.interface.js +0 -3
  21. package/cjs/interfaces/index.js +2 -0
  22. package/cjs/interfaces/user-enricher.interface.js +4 -0
  23. package/cjs/modules/auth.module.js +8 -20
  24. package/cjs/services/auth-config.service.js +42 -47
  25. package/cjs/services/auth-datasource.provider.js +18 -34
  26. package/cjs/services/auth-email.service.js +471 -0
  27. package/cjs/services/authentication.service.js +240 -153
  28. package/cjs/services/branch.service.js +15 -48
  29. package/cjs/services/company-selection-session.service.js +1 -1
  30. package/cjs/services/company.service.js +7 -36
  31. package/cjs/services/index.js +1 -0
  32. package/cjs/services/user-permission.service.js +9 -29
  33. package/cjs/services/user.service.js +228 -107
  34. package/cjs/strategies/jwt.strategy.js +14 -1
  35. package/cjs/validators/password.validator.js +45 -0
  36. package/config/auth.constants.d.ts +5 -0
  37. package/controllers/authentication.controller.d.ts +13 -9
  38. package/controllers/company-selection.controller.d.ts +6 -2
  39. package/controllers/user.controller.d.ts +33 -0
  40. package/dtos/authentication.dto.d.ts +21 -3
  41. package/dtos/company-branch.dto.d.ts +0 -1
  42. package/dtos/company.dto.d.ts +0 -1
  43. package/dtos/user-permission.dto.d.ts +4 -0
  44. package/dtos/user.dto.d.ts +1 -2
  45. package/entities/index.d.ts +7 -3
  46. package/entities/pending-registration.entity.d.ts +17 -0
  47. package/entities/user-company-permission.entity.d.ts +0 -2
  48. package/entities/user-token.entity.d.ts +15 -0
  49. package/fesm/config/auth.constants.js +11 -8
  50. package/fesm/controllers/authentication.controller.js +224 -40
  51. package/fesm/controllers/branch.controller.js +49 -7
  52. package/fesm/controllers/company-selection.controller.js +28 -21
  53. package/fesm/controllers/company.controller.js +49 -7
  54. package/fesm/controllers/user-permission.controller.js +41 -38
  55. package/fesm/controllers/user.controller.js +225 -26
  56. package/fesm/dtos/authentication.dto.js +123 -111
  57. package/fesm/dtos/user-permission.dto.js +28 -0
  58. package/fesm/dtos/user.dto.js +20 -5
  59. package/fesm/entities/index.js +7 -1
  60. package/fesm/entities/pending-registration.entity.js +115 -0
  61. package/fesm/entities/user-company-permission.entity.js +0 -6
  62. package/fesm/entities/user-token.entity.js +113 -0
  63. package/fesm/guards/auth-throttler.guard.js +43 -0
  64. package/fesm/interceptors/clear-token.interceptor.js +2 -5
  65. package/fesm/interceptors/set-token.interceptor.js +37 -30
  66. package/fesm/interfaces/auth-email-provider.interface.js +38 -0
  67. package/fesm/interfaces/authentication.interface.js +1 -7
  68. package/fesm/interfaces/index.js +2 -0
  69. package/fesm/interfaces/user-enricher.interface.js +26 -0
  70. package/fesm/modules/auth.module.js +10 -22
  71. package/fesm/services/auth-config.service.js +42 -47
  72. package/fesm/services/auth-datasource.provider.js +19 -35
  73. package/fesm/services/auth-email.service.js +420 -0
  74. package/fesm/services/authentication.service.js +238 -151
  75. package/fesm/services/branch.service.js +16 -49
  76. package/fesm/services/company-selection-session.service.js +1 -1
  77. package/fesm/services/company.service.js +8 -37
  78. package/fesm/services/index.js +1 -0
  79. package/fesm/services/user-permission.service.js +10 -30
  80. package/fesm/services/user.service.js +229 -108
  81. package/fesm/strategies/jwt.strategy.js +14 -1
  82. package/fesm/validators/password.validator.js +48 -0
  83. package/guards/auth-throttler.guard.d.ts +7 -0
  84. package/interceptors/set-token.interceptor.d.ts +1 -0
  85. package/interfaces/auth-email-provider.interface.d.ts +6 -0
  86. package/interfaces/auth-module-options.interface.d.ts +4 -1
  87. package/interfaces/authentication.interface.d.ts +14 -0
  88. package/interfaces/index.d.ts +2 -0
  89. package/interfaces/user-enricher.interface.d.ts +64 -0
  90. package/interfaces/user.interface.d.ts +1 -0
  91. package/modules/auth.module.d.ts +0 -1
  92. package/package.json +4 -4
  93. package/services/auth-config.service.d.ts +5 -1
  94. package/services/auth-email.service.d.ts +51 -0
  95. package/services/authentication.service.d.ts +21 -12
  96. package/services/branch.service.d.ts +2 -5
  97. package/services/company-selection-session.service.d.ts +1 -1
  98. package/services/company.service.d.ts +1 -4
  99. package/services/index.d.ts +1 -0
  100. package/services/user-permission.service.d.ts +0 -1
  101. package/services/user.service.d.ts +20 -6
  102. package/validators/password.validator.d.ts +10 -0
@@ -10,13 +10,14 @@ Object.defineProperty(exports, "AuthenticationService", {
10
10
  });
11
11
  const _common = require("@nestjs/common");
12
12
  const _jwt = require("@nestjs/jwt");
13
- const _typeorm = require("@nestjs/typeorm");
14
13
  const _bcrypt = /*#__PURE__*/ _interop_require_wildcard(require("bcrypt"));
15
- const _typeorm1 = require("typeorm");
16
14
  const _utils = require("@flusys/nestjs-shared/utils");
17
15
  const _authconfigservice = require("./auth-config.service");
18
16
  const _authdatasourceprovider = require("./auth-datasource.provider");
19
17
  const _enums = require("../enums");
18
+ const _config = require("../config");
19
+ const _classes = require("@flusys/nestjs-shared/classes");
20
+ const _authemailservice = require("./auth-email.service");
20
21
  const _companybranchentity = require("../entities/company-branch.entity");
21
22
  const _companyentity = require("../entities/company.entity");
22
23
  const _usercompanypermissionentity = require("../entities/user-company-permission.entity");
@@ -90,62 +91,46 @@ function _ts_param(paramIndex, decorator) {
90
91
  decorator(target, key, paramIndex);
91
92
  };
92
93
  }
93
- // Password hashing configuration
94
- const BCRYPT_SALT_ROUNDS = 12; // Increased from 10 for better security
95
94
  let AuthenticationService = class AuthenticationService {
96
- // ==========================================================================
97
- // REPOSITORY GETTERS
98
- // ==========================================================================
95
+ // Repository Getters
99
96
  async getUserRepository() {
100
- if (this.dataSourceProvider) {
101
- return this.dataSourceProvider.getRepository(_userentity.User);
97
+ if (!this.dataSourceProvider) {
98
+ throw new Error('User repository not available');
102
99
  }
103
- if (this.injectedUserRepository) {
104
- return this.injectedUserRepository;
105
- }
106
- throw new Error('User repository not available');
100
+ return this.dataSourceProvider.getRepository(_userentity.User);
107
101
  }
108
102
  async getPermissionRepository() {
109
- if (!this.authConfig.isCompanyFeatureEnabled()) return null;
110
- if (this.dataSourceProvider) {
111
- return this.dataSourceProvider.getRepository(_usercompanypermissionentity.UserCompanyPermission);
112
- }
113
- return this.injectedPermissionRepository ?? null;
103
+ if (!this.authConfig.isCompanyFeatureEnabled() || !this.dataSourceProvider) return null;
104
+ return this.dataSourceProvider.getRepository(_usercompanypermissionentity.UserCompanyPermission);
114
105
  }
115
106
  async getCompanyRepository() {
116
- if (!this.authConfig.isCompanyFeatureEnabled()) return null;
117
- if (this.dataSourceProvider) {
118
- return this.dataSourceProvider.getRepository(_companyentity.Company);
119
- }
120
- return this.injectedCompanyRepository ?? null;
107
+ if (!this.authConfig.isCompanyFeatureEnabled() || !this.dataSourceProvider) return null;
108
+ return this.dataSourceProvider.getRepository(_companyentity.Company);
121
109
  }
122
110
  async getBranchRepository() {
123
- if (!this.authConfig.isCompanyFeatureEnabled()) return null;
124
- if (this.dataSourceProvider) {
125
- return this.dataSourceProvider.getRepository(_companybranchentity.CompanyBranch);
126
- }
127
- return this.injectedBranchRepository ?? null;
128
- }
129
- // ==========================================================================
130
- // LOGIN
131
- // ==========================================================================
132
- /**
133
- * Login with email and password
134
- * Returns either tokens directly or requires company/branch selection
135
- */ async login(email, password) {
111
+ if (!this.authConfig.isCompanyFeatureEnabled() || !this.dataSourceProvider) return null;
112
+ return this.dataSourceProvider.getRepository(_companybranchentity.CompanyBranch);
113
+ }
114
+ // Login
115
+ /** Login with email and password. Returns tokens or requires company/branch selection. */ async login(email, password) {
136
116
  const user = await this.validateUser(email, password);
137
117
  if (!user.isActive) {
138
118
  throw new _common.UnauthorizedException('User account is deactivated');
139
119
  }
140
- // If company feature not enabled, return simple token
120
+ // Check email verification if email service is enabled
121
+ if (this.authEmailService?.isEmailEnabled() && !user.emailVerified) {
122
+ return {
123
+ requiresEmailVerification: true,
124
+ email: user.email,
125
+ message: 'Please verify your email address before logging in.'
126
+ };
127
+ }
141
128
  if (!this.authConfig.isCompanyFeatureEnabled()) {
142
129
  return this.generateLoginResponse(user);
143
130
  }
144
131
  return this.handleCompanyBasedLogin(user);
145
132
  }
146
- /**
147
- * Handle login when company feature is enabled
148
- */ async handleCompanyBasedLogin(user) {
133
+ /** Handle login when company feature is enabled */ async handleCompanyBasedLogin(user) {
149
134
  const permissionRepository = await this.getPermissionRepository();
150
135
  const branchRepository = await this.getBranchRepository();
151
136
  if (!permissionRepository || !branchRepository) {
@@ -195,16 +180,9 @@ let AuthenticationService = class AuthenticationService {
195
180
  // Returns all companies with their branches for frontend to display
196
181
  return await this.generateCompanySelectionResponse(user, companies, branches);
197
182
  }
198
- // ==========================================================================
199
- // REGISTRATION
200
- // ==========================================================================
201
- /**
202
- * Register a new user
203
- * If company feature enabled, optionally join/create company
204
- */ async register(dto) {
183
+ // Registration
184
+ /** Register a new user. If email enabled, creates pending registration. Otherwise creates user directly. */ async register(dto, verifyUrlBase) {
205
185
  const userRepository = await this.getUserRepository();
206
- // Check if user exists
207
- // Note: Generic error message to prevent email enumeration attacks
208
186
  const existingUser = await userRepository.findOne({
209
187
  where: {
210
188
  email: dto.email
@@ -213,8 +191,34 @@ let AuthenticationService = class AuthenticationService {
213
191
  if (existingUser) {
214
192
  throw new _common.BadRequestException('Registration failed. Please try again or contact support.');
215
193
  }
216
- // Create user
217
- const hashedPassword = await _bcrypt.hash(dto.password, BCRYPT_SALT_ROUNDS);
194
+ // If email verification is enabled, create pending registration instead of user
195
+ if (this.authEmailService?.isEmailEnabled() && verifyUrlBase) {
196
+ const companyData = this.authConfig.isCompanyFeatureEnabled() ? {
197
+ companySlug: dto.companySlug,
198
+ branchSlug: dto.branchSlug,
199
+ newCompanyName: dto.newCompanyName,
200
+ newCompanyPhone: dto.newCompanyPhone,
201
+ newCompanyAddress: dto.newCompanyAddress
202
+ } : undefined;
203
+ const result = await this.authEmailService.createPendingRegistration({
204
+ email: dto.email,
205
+ name: dto.name,
206
+ phone: dto.phone,
207
+ password: dto.password,
208
+ companyData,
209
+ verifyUrlBase
210
+ });
211
+ return {
212
+ requiresEmailVerification: true,
213
+ message: result.message
214
+ };
215
+ }
216
+ // No email verification - create user directly
217
+ return this.createUserDirectly(dto);
218
+ }
219
+ /** Create user directly (no email verification) */ async createUserDirectly(dto) {
220
+ const userRepository = await this.getUserRepository();
221
+ const hashedPassword = await _bcrypt.hash(dto.password, _config.BCRYPT_SALT_ROUNDS);
218
222
  const user = userRepository.create({
219
223
  name: dto.name,
220
224
  email: dto.email,
@@ -225,40 +229,68 @@ let AuthenticationService = class AuthenticationService {
225
229
  phoneVerified: false
226
230
  });
227
231
  await userRepository.save(user);
228
- // If company feature not enabled, return simple response
229
232
  if (!this.authConfig.isCompanyFeatureEnabled()) {
230
- return this.generateLoginResponse(user, false);
233
+ return this.generateRegistrationResponse(user, false, false);
231
234
  }
232
- // Handle company feature
233
- return this.handleCompanyRegistration(user, dto);
235
+ return this.handleCompanyRegistration(user, dto, false);
236
+ }
237
+ /** Create user from pending registration (after email verification) */ async createUserFromPendingRegistration(pending) {
238
+ const userRepository = await this.getUserRepository();
239
+ // Check if user already exists (race condition protection)
240
+ const existingUser = await userRepository.findOne({
241
+ where: {
242
+ email: pending.email
243
+ }
244
+ });
245
+ if (existingUser) {
246
+ throw new _common.BadRequestException('This email has already been registered.');
247
+ }
248
+ // Create user with verified email
249
+ const user = userRepository.create({
250
+ name: pending.name,
251
+ email: pending.email,
252
+ phone: pending.phone,
253
+ password: pending.passwordHash,
254
+ isActive: true,
255
+ emailVerified: true,
256
+ phoneVerified: false
257
+ });
258
+ await userRepository.save(user);
259
+ // Handle company feature if enabled
260
+ if (this.authConfig.isCompanyFeatureEnabled() && pending.companyData) {
261
+ return this.handleCompanyRegistrationFromPending(user, pending.companyData);
262
+ }
263
+ return this.generateRegistrationResponse(user, this.authConfig.isCompanyFeatureEnabled(), false);
264
+ }
265
+ /** Handle company registration from pending registration data */ async handleCompanyRegistrationFromPending(user, companyData) {
266
+ return this.processCompanyRegistration(user, companyData, false);
234
267
  }
235
- /**
236
- * Handle company-related registration logic
237
- */ async handleCompanyRegistration(user, dto) {
268
+ /** Handle company-related registration logic */ async handleCompanyRegistration(user, dto, requiresEmailVerification) {
269
+ return this.processCompanyRegistration(user, dto, requiresEmailVerification);
270
+ }
271
+ /** Shared logic for company registration (join existing or create new) */ async processCompanyRegistration(user, data, requiresEmailVerification) {
238
272
  const companyRepository = await this.getCompanyRepository();
239
273
  const branchRepository = await this.getBranchRepository();
240
274
  const permissionRepository = await this.getPermissionRepository();
241
275
  if (!companyRepository || !branchRepository || !permissionRepository) {
242
- return this.generateLoginResponse(user, true);
276
+ return this.generateRegistrationResponse(user, true, requiresEmailVerification);
243
277
  }
244
278
  let company = null;
245
279
  let branch = null;
246
- // Case 1: Join existing company by slug
247
- if (dto.companySlug) {
280
+ if (data.companySlug) {
248
281
  company = await companyRepository.findOne({
249
282
  where: {
250
- slug: dto.companySlug,
283
+ slug: data.companySlug,
251
284
  isActive: true
252
285
  }
253
286
  });
254
287
  if (!company) {
255
288
  throw new _common.BadRequestException('Company not found or inactive');
256
289
  }
257
- // Find branch by slug or get default
258
- if (dto.branchSlug) {
290
+ if (data.branchSlug) {
259
291
  branch = await branchRepository.findOne({
260
292
  where: {
261
- slug: dto.branchSlug,
293
+ slug: data.branchSlug,
262
294
  companyId: company.id,
263
295
  isActive: true
264
296
  }
@@ -267,7 +299,6 @@ let AuthenticationService = class AuthenticationService {
267
299
  throw new _common.BadRequestException('Branch not found or inactive');
268
300
  }
269
301
  } else {
270
- // Get first active branch
271
302
  branch = await branchRepository.findOne({
272
303
  where: {
273
304
  companyId: company.id,
@@ -279,9 +310,8 @@ let AuthenticationService = class AuthenticationService {
279
310
  }
280
311
  });
281
312
  }
282
- } else if (dto.newCompanyName) {
283
- const slug = this.generateSlug(dto.newCompanyName);
284
- // Check if slug exists
313
+ } else if (data.newCompanyName) {
314
+ const slug = this.generateSlug(data.newCompanyName);
285
315
  const existingCompany = await companyRepository.findOne({
286
316
  where: {
287
317
  slug
@@ -290,16 +320,14 @@ let AuthenticationService = class AuthenticationService {
290
320
  if (existingCompany) {
291
321
  throw new _common.BadRequestException('Company creation failed. Please try a different name.');
292
322
  }
293
- // Create company
294
323
  company = companyRepository.create({
295
- name: dto.newCompanyName,
324
+ name: data.newCompanyName,
296
325
  slug,
297
- phone: dto.newCompanyPhone,
298
- address: dto.newCompanyAddress,
326
+ phone: data.newCompanyPhone,
327
+ address: data.newCompanyAddress,
299
328
  isActive: true
300
329
  });
301
330
  await companyRepository.save(company);
302
- // Create default branch
303
331
  branch = branchRepository.create({
304
332
  name: 'Main Branch',
305
333
  slug: 'main-branch',
@@ -308,16 +336,13 @@ let AuthenticationService = class AuthenticationService {
308
336
  });
309
337
  await branchRepository.save(branch);
310
338
  }
311
- // Create permissions if company exists
312
339
  if (company) {
313
- // Create company permission
314
340
  const companyPermission = permissionRepository.create({
315
341
  userId: user.id,
316
342
  permissionType: _enums.PermissionType.COMPANY,
317
343
  targetId: company.id
318
344
  });
319
345
  await permissionRepository.save(companyPermission);
320
- // Create branch permission if branch exists
321
346
  if (branch) {
322
347
  const branchPermission = permissionRepository.create({
323
348
  userId: user.id,
@@ -326,23 +351,15 @@ let AuthenticationService = class AuthenticationService {
326
351
  });
327
352
  await permissionRepository.save(branchPermission);
328
353
  }
329
- return this.generateLoginResponseWithCompany(user, company, branch ?? undefined);
354
+ return this.generateRegistrationResponseWithCompany(user, company, branch ?? undefined, requiresEmailVerification);
330
355
  }
331
- // No company assigned
332
- return this.generateLoginResponse(user, true);
356
+ return this.generateRegistrationResponse(user, true, requiresEmailVerification);
333
357
  }
334
- /**
335
- * Generate URL-friendly slug from name
336
- */ generateSlug(name) {
358
+ /** Generate URL-friendly slug from name */ generateSlug(name) {
337
359
  return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').substring(0, 100);
338
360
  }
339
- // ==========================================================================
340
- // COMPANY/BRANCH SELECTION
341
- // ==========================================================================
342
- /**
343
- * Select company and branch (step 2 of login)
344
- * Frontend receives all options and user selects both together
345
- */ async select(sessionId, companyId, branchId) {
361
+ // Company/Branch Selection
362
+ /** Select company and branch (step 2 of login). Frontend receives all options and user selects both. */ async select(sessionId, companyId, branchId) {
346
363
  this.ensureCompanyFeatureEnabled();
347
364
  if (!this.sessionService) {
348
365
  throw new _common.BadRequestException('Session service not available');
@@ -409,9 +426,7 @@ let AuthenticationService = class AuthenticationService {
409
426
  }
410
427
  return this.generateLoginResponseWithCompany(user, company, branch);
411
428
  }
412
- /**
413
- * Switch company/branch after login
414
- */ async switchCompany(userId, companyId, branchId) {
429
+ /** Switch company/branch after login */ async switchCompany(userId, companyId, branchId) {
415
430
  this.ensureCompanyFeatureEnabled();
416
431
  const userRepository = await this.getUserRepository();
417
432
  const user = await userRepository.findOne({
@@ -470,17 +485,17 @@ let AuthenticationService = class AuthenticationService {
470
485
  }
471
486
  return this.generateLoginResponseWithCompany(user, company, branch);
472
487
  }
473
- // ==========================================================================
474
- // TOKEN MANAGEMENT
475
- // ==========================================================================
476
- /**
477
- * Refresh access token (with token rotation)
478
- * Returns new access token and new refresh token
479
- */ async refreshToken(refreshToken) {
488
+ // Token Management
489
+ /** Refresh access token (with token rotation). Returns new access token and new refresh token. */ async refreshToken(refreshToken) {
480
490
  try {
481
491
  const payload = this.jwtService.verify(refreshToken, {
482
492
  secret: this.authConfig.getRefreshTokenSecret()
483
493
  });
494
+ // Check if token has been revoked (user logged out after token was issued)
495
+ const isRevoked = await this.isTokenRevoked(payload.id, payload.iat);
496
+ if (isRevoked) {
497
+ throw new _common.UnauthorizedException('Token has been revoked');
498
+ }
484
499
  const userRepository = await this.getUserRepository();
485
500
  const user = await userRepository.findOne({
486
501
  where: {
@@ -542,9 +557,7 @@ let AuthenticationService = class AuthenticationService {
542
557
  throw new _common.UnauthorizedException('Invalid or expired refresh token');
543
558
  }
544
559
  }
545
- /**
546
- * Change password
547
- */ async changePassword(userId, currentPassword, newPassword) {
560
+ /** Change password */ async changePassword(userId, currentPassword, newPassword) {
548
561
  const userRepository = await this.getUserRepository();
549
562
  const user = await userRepository.findOne({
550
563
  where: {
@@ -558,18 +571,55 @@ let AuthenticationService = class AuthenticationService {
558
571
  if (!isPasswordValid) {
559
572
  throw new _common.UnauthorizedException('Current password is incorrect');
560
573
  }
561
- user.password = await _bcrypt.hash(newPassword, BCRYPT_SALT_ROUNDS);
574
+ user.password = await _bcrypt.hash(newPassword, _config.BCRYPT_SALT_ROUNDS);
562
575
  await userRepository.save(user);
563
576
  }
564
- /**
565
- * Logout (placeholder for token blacklisting if needed)
566
- */ async logout(_userId) {}
567
- // ==========================================================================
568
- // QUERY METHODS
569
- // ==========================================================================
570
- /**
571
- * Get user's companies
572
- */ async getUserCompanies(userId) {
577
+ /** Logout - Revokes all tokens. Stores revocation timestamp in cache. */ async logout(userId) {
578
+ if (!this.cacheManager) {
579
+ // Cache is required for secure logout - tokens cannot be revoked without it
580
+ 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.');
581
+ throw new _common.BadRequestException('Logout service temporarily unavailable. Please try again later.');
582
+ }
583
+ const revokedAt = Math.floor(Date.now() / 1000); // Unix timestamp in seconds
584
+ const cacheKey = `${_config.TOKEN_REVOKED_PREFIX}:${userId}`;
585
+ // TTL matches refresh token expiry (tokens older than this are already expired)
586
+ const refreshTokenExpiry = this.authConfig.getRefreshTokenExpiration();
587
+ const ttlMs = this.parseExpiryToMs(refreshTokenExpiry);
588
+ await this.cacheManager.set(cacheKey, revokedAt, ttlMs);
589
+ this.logger.log(`User ${userId} logged out - all tokens revoked`);
590
+ }
591
+ /** Check if a token is revoked (issued before user's revocation timestamp) */ async isTokenRevoked(userId, tokenIssuedAt) {
592
+ if (!this.cacheManager || !tokenIssuedAt) {
593
+ return false;
594
+ }
595
+ const cacheKey = `${_config.TOKEN_REVOKED_PREFIX}:${userId}`;
596
+ const revokedAt = await this.cacheManager.get(cacheKey);
597
+ if (!revokedAt) {
598
+ return false;
599
+ }
600
+ // Token is revoked if it was issued before the revocation timestamp
601
+ return tokenIssuedAt < revokedAt;
602
+ }
603
+ /** Parse JWT expiry string to milliseconds */ parseExpiryToMs(expiry) {
604
+ const match = expiry.match(/^(\d+)([smhd])$/);
605
+ if (!match) return 7 * 24 * 60 * 60 * 1000; // Default 7 days
606
+ const value = parseInt(match[1], 10);
607
+ const unit = match[2];
608
+ switch(unit){
609
+ case 's':
610
+ return value * 1000;
611
+ case 'm':
612
+ return value * 60 * 1000;
613
+ case 'h':
614
+ return value * 60 * 60 * 1000;
615
+ case 'd':
616
+ return value * 24 * 60 * 60 * 1000;
617
+ default:
618
+ return 7 * 24 * 60 * 60 * 1000;
619
+ }
620
+ }
621
+ // Query Methods
622
+ /** Get user's companies */ async getUserCompanies(userId) {
573
623
  this.ensureCompanyFeatureEnabled();
574
624
  const permissionRepository = await this.getPermissionRepository();
575
625
  const companyRepository = await this.getCompanyRepository();
@@ -590,9 +640,7 @@ let AuthenticationService = class AuthenticationService {
590
640
  active: true
591
641
  }).orderBy('c.name', 'ASC').getMany();
592
642
  }
593
- /**
594
- * Get branches for a company that user has access to
595
- */ async getUserBranches(userId, companyId) {
643
+ /** Get branches for a company that user has access to */ async getUserBranches(userId, companyId) {
596
644
  this.ensureCompanyFeatureEnabled();
597
645
  const permissionRepository = await this.getPermissionRepository();
598
646
  const branchRepository = await this.getBranchRepository();
@@ -627,9 +675,7 @@ let AuthenticationService = class AuthenticationService {
627
675
  active: true
628
676
  }).orderBy('b.serial', 'ASC').addOrderBy('b.name', 'ASC').getMany();
629
677
  }
630
- /**
631
- * Get company by ID
632
- */ async getCompanyById(companyId) {
678
+ /** Get company by ID */ async getCompanyById(companyId) {
633
679
  const companyRepository = await this.getCompanyRepository();
634
680
  if (!companyRepository) {
635
681
  return null;
@@ -640,9 +686,7 @@ let AuthenticationService = class AuthenticationService {
640
686
  }
641
687
  });
642
688
  }
643
- /**
644
- * Get branch by ID
645
- */ async getBranchById(branchId) {
689
+ /** Get branch by ID */ async getBranchById(branchId) {
646
690
  const branchRepository = await this.getBranchRepository();
647
691
  if (!branchRepository) {
648
692
  return null;
@@ -653,10 +697,7 @@ let AuthenticationService = class AuthenticationService {
653
697
  }
654
698
  });
655
699
  }
656
- /**
657
- * Get user info for /auth/me endpoint
658
- * Returns only user, company, and branch information (no tokens)
659
- */ async getUserInfo(userId, companyId, branchId) {
700
+ /** Get user info for /auth/me endpoint. Returns user, company, and branch info (no tokens). */ async getUserInfo(userId, companyId, branchId) {
660
701
  const userRepository = await this.getUserRepository();
661
702
  const user = await userRepository.findOne({
662
703
  where: {
@@ -672,6 +713,7 @@ let AuthenticationService = class AuthenticationService {
672
713
  id: user.id,
673
714
  email: user.email,
674
715
  name: user.name ?? undefined,
716
+ phone: user.phone ?? undefined,
675
717
  profilePictureId: user.profilePictureId ?? undefined
676
718
  }
677
719
  };
@@ -707,9 +749,7 @@ let AuthenticationService = class AuthenticationService {
707
749
  }
708
750
  return response;
709
751
  }
710
- // ==========================================================================
711
- // HELPER METHODS
712
- // ==========================================================================
752
+ // Helper Methods
713
753
  ensureCompanyFeatureEnabled() {
714
754
  if (!this.authConfig.isCompanyFeatureEnabled()) {
715
755
  throw new _common.BadRequestException('Company feature is not enabled');
@@ -733,9 +773,7 @@ let AuthenticationService = class AuthenticationService {
733
773
  await userRepository.save(user);
734
774
  return user;
735
775
  }
736
- // ==========================================================================
737
- // RESPONSE GENERATORS
738
- // ==========================================================================
776
+ // Response Generators
739
777
  generateLoginResponse(user, companyFeatureEnabled = false) {
740
778
  const payload = {
741
779
  id: user.id,
@@ -762,6 +800,7 @@ let AuthenticationService = class AuthenticationService {
762
800
  id: user.id,
763
801
  email: user.email,
764
802
  name: user.name ?? undefined,
803
+ phone: user.phone ?? null,
765
804
  profilePictureId: user.profilePictureId ?? null
766
805
  },
767
806
  companyFeatureEnabled
@@ -797,6 +836,7 @@ let AuthenticationService = class AuthenticationService {
797
836
  id: user.id,
798
837
  email: user.email,
799
838
  name: user.name ?? undefined,
839
+ phone: user.phone ?? null,
800
840
  profilePictureId: user.profilePictureId ?? null
801
841
  },
802
842
  company: {
@@ -842,30 +882,83 @@ let AuthenticationService = class AuthenticationService {
842
882
  id: user.id,
843
883
  email: user.email,
844
884
  name: user.name ?? undefined,
885
+ phone: user.phone ?? null,
845
886
  profilePictureId: user.profilePictureId ?? null
846
887
  },
847
888
  companies: companiesWithBranches
848
889
  };
849
890
  }
891
+ /** Generate registration response - tokens included only if email verification not required */ generateRegistrationResponse(user, companyFeatureEnabled, requiresEmailVerification) {
892
+ const userInfo = {
893
+ id: user.id,
894
+ email: user.email,
895
+ name: user.name ?? undefined,
896
+ phone: user.phone ?? null,
897
+ profilePictureId: user.profilePictureId ?? null
898
+ };
899
+ if (requiresEmailVerification) {
900
+ return {
901
+ user: userInfo,
902
+ companyFeatureEnabled,
903
+ requiresEmailVerification: true
904
+ };
905
+ }
906
+ const loginResponse = this.generateLoginResponse(user, companyFeatureEnabled);
907
+ return {
908
+ ...loginResponse,
909
+ requiresEmailVerification: false
910
+ };
911
+ }
912
+ /** Generate registration response with company - tokens included only if email verification not required */ generateRegistrationResponseWithCompany(user, company, branch, requiresEmailVerification = false) {
913
+ const userInfo = {
914
+ id: user.id,
915
+ email: user.email,
916
+ name: user.name ?? undefined,
917
+ phone: user.phone ?? null,
918
+ profilePictureId: user.profilePictureId ?? null
919
+ };
920
+ const companyInfo = {
921
+ id: company.id,
922
+ name: company.name,
923
+ slug: company.slug,
924
+ logoId: company.logoId ?? null
925
+ };
926
+ const branchInfo = branch ? {
927
+ id: branch.id,
928
+ name: branch.name,
929
+ slug: branch.slug,
930
+ logoId: branch.logoId ?? null
931
+ } : undefined;
932
+ if (requiresEmailVerification) {
933
+ return {
934
+ user: userInfo,
935
+ company: companyInfo,
936
+ branch: branchInfo,
937
+ companyFeatureEnabled: true,
938
+ requiresEmailVerification: true
939
+ };
940
+ }
941
+ const loginResponse = this.generateLoginResponseWithCompany(user, company, branch);
942
+ return {
943
+ ...loginResponse,
944
+ requiresEmailVerification: false
945
+ };
946
+ }
850
947
  // NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
851
- constructor(jwtService, authConfig, dataSourceProvider, sessionService, injectedUserRepository, injectedPermissionRepository, injectedCompanyRepository, injectedBranchRepository){
948
+ constructor(jwtService, authConfig, cacheManager, dataSourceProvider, sessionService, authEmailService){
852
949
  _define_property(this, "jwtService", void 0);
853
950
  _define_property(this, "authConfig", void 0);
951
+ _define_property(this, "cacheManager", void 0);
854
952
  _define_property(this, "dataSourceProvider", void 0);
855
953
  _define_property(this, "sessionService", void 0);
856
- _define_property(this, "injectedUserRepository", void 0);
857
- _define_property(this, "injectedPermissionRepository", void 0);
858
- _define_property(this, "injectedCompanyRepository", void 0);
859
- _define_property(this, "injectedBranchRepository", void 0);
954
+ _define_property(this, "authEmailService", void 0);
860
955
  _define_property(this, "logger", void 0);
861
956
  this.jwtService = jwtService;
862
957
  this.authConfig = authConfig;
958
+ this.cacheManager = cacheManager;
863
959
  this.dataSourceProvider = dataSourceProvider;
864
960
  this.sessionService = sessionService;
865
- this.injectedUserRepository = injectedUserRepository;
866
- this.injectedPermissionRepository = injectedPermissionRepository;
867
- this.injectedCompanyRepository = injectedCompanyRepository;
868
- this.injectedBranchRepository = injectedBranchRepository;
961
+ this.authEmailService = authEmailService;
869
962
  this.logger = new _common.Logger(AuthenticationService.name);
870
963
  }
871
964
  };
@@ -876,26 +969,20 @@ AuthenticationService = _ts_decorate([
876
969
  _ts_param(0, (0, _common.Inject)(_jwt.JwtService)),
877
970
  _ts_param(1, (0, _common.Inject)(_authconfigservice.AuthConfigService)),
878
971
  _ts_param(2, (0, _common.Optional)()),
879
- _ts_param(2, (0, _common.Inject)(_authdatasourceprovider.AuthDataSourceProvider)),
972
+ _ts_param(2, (0, _common.Inject)('CACHE_INSTANCE')),
880
973
  _ts_param(3, (0, _common.Optional)()),
881
- _ts_param(3, (0, _common.Inject)(_companyselectionsessionservice.CompanySelectionSessionService)),
974
+ _ts_param(3, (0, _common.Inject)(_authdatasourceprovider.AuthDataSourceProvider)),
882
975
  _ts_param(4, (0, _common.Optional)()),
883
- _ts_param(4, (0, _typeorm.InjectRepository)(_userentity.User)),
976
+ _ts_param(4, (0, _common.Inject)(_companyselectionsessionservice.CompanySelectionSessionService)),
884
977
  _ts_param(5, (0, _common.Optional)()),
885
- _ts_param(5, (0, _typeorm.InjectRepository)(_usercompanypermissionentity.UserCompanyPermission)),
886
- _ts_param(6, (0, _common.Optional)()),
887
- _ts_param(6, (0, _typeorm.InjectRepository)(_companyentity.Company)),
888
- _ts_param(7, (0, _common.Optional)()),
889
- _ts_param(7, (0, _typeorm.InjectRepository)(_companybranchentity.CompanyBranch)),
978
+ _ts_param(5, (0, _common.Inject)(_authemailservice.AuthEmailService)),
890
979
  _ts_metadata("design:type", Function),
891
980
  _ts_metadata("design:paramtypes", [
892
981
  typeof _jwt.JwtService === "undefined" ? Object : _jwt.JwtService,
893
982
  typeof _authconfigservice.AuthConfigService === "undefined" ? Object : _authconfigservice.AuthConfigService,
983
+ typeof _classes.HybridCache === "undefined" ? Object : _classes.HybridCache,
894
984
  typeof _authdatasourceprovider.AuthDataSourceProvider === "undefined" ? Object : _authdatasourceprovider.AuthDataSourceProvider,
895
985
  typeof _companyselectionsessionservice.CompanySelectionSessionService === "undefined" ? Object : _companyselectionsessionservice.CompanySelectionSessionService,
896
- typeof _typeorm1.Repository === "undefined" ? Object : _typeorm1.Repository,
897
- typeof _typeorm1.Repository === "undefined" ? Object : _typeorm1.Repository,
898
- typeof _typeorm1.Repository === "undefined" ? Object : _typeorm1.Repository,
899
- typeof _typeorm1.Repository === "undefined" ? Object : _typeorm1.Repository
986
+ typeof _authemailservice.AuthEmailService === "undefined" ? Object : _authemailservice.AuthEmailService
900
987
  ])
901
988
  ], AuthenticationService);