@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
@@ -27,74 +27,59 @@ function _ts_param(paramIndex, decorator) {
27
27
  }
28
28
  import { BadRequestException, Inject, Injectable, Logger, NotFoundException, Optional, Scope, UnauthorizedException } from '@nestjs/common';
29
29
  import { JwtService } from '@nestjs/jwt';
30
- import { InjectRepository } from '@nestjs/typeorm';
31
30
  import * as bcrypt from 'bcrypt';
32
- import { Repository } from 'typeorm';
33
31
  import { ErrorHandler } from '@flusys/nestjs-shared/utils';
34
32
  import { AuthConfigService } from './auth-config.service';
35
33
  import { AuthDataSourceProvider } from './auth-datasource.provider';
36
34
  import { PermissionType } from '../enums';
37
- // Password hashing configuration
38
- const BCRYPT_SALT_ROUNDS = 12; // Increased from 10 for better security
35
+ import { BCRYPT_SALT_ROUNDS, TOKEN_REVOKED_PREFIX } from '../config';
36
+ import { HybridCache } from '@flusys/nestjs-shared/classes';
37
+ import { AuthEmailService } from './auth-email.service';
39
38
  import { CompanyBranch } from '../entities/company-branch.entity';
40
39
  import { Company } from '../entities/company.entity';
41
40
  import { UserCompanyPermission } from '../entities/user-company-permission.entity';
42
41
  import { User } from '../entities/user.entity';
43
42
  import { CompanySelectionSessionService } from './company-selection-session.service';
44
43
  export class AuthenticationService {
45
- // ==========================================================================
46
- // REPOSITORY GETTERS
47
- // ==========================================================================
44
+ // Repository Getters
48
45
  async getUserRepository() {
49
- if (this.dataSourceProvider) {
50
- return this.dataSourceProvider.getRepository(User);
46
+ if (!this.dataSourceProvider) {
47
+ throw new Error('User repository not available');
51
48
  }
52
- if (this.injectedUserRepository) {
53
- return this.injectedUserRepository;
54
- }
55
- throw new Error('User repository not available');
49
+ return this.dataSourceProvider.getRepository(User);
56
50
  }
57
51
  async getPermissionRepository() {
58
- if (!this.authConfig.isCompanyFeatureEnabled()) return null;
59
- if (this.dataSourceProvider) {
60
- return this.dataSourceProvider.getRepository(UserCompanyPermission);
61
- }
62
- return this.injectedPermissionRepository ?? null;
52
+ if (!this.authConfig.isCompanyFeatureEnabled() || !this.dataSourceProvider) return null;
53
+ return this.dataSourceProvider.getRepository(UserCompanyPermission);
63
54
  }
64
55
  async getCompanyRepository() {
65
- if (!this.authConfig.isCompanyFeatureEnabled()) return null;
66
- if (this.dataSourceProvider) {
67
- return this.dataSourceProvider.getRepository(Company);
68
- }
69
- return this.injectedCompanyRepository ?? null;
56
+ if (!this.authConfig.isCompanyFeatureEnabled() || !this.dataSourceProvider) return null;
57
+ return this.dataSourceProvider.getRepository(Company);
70
58
  }
71
59
  async getBranchRepository() {
72
- if (!this.authConfig.isCompanyFeatureEnabled()) return null;
73
- if (this.dataSourceProvider) {
74
- return this.dataSourceProvider.getRepository(CompanyBranch);
75
- }
76
- return this.injectedBranchRepository ?? null;
77
- }
78
- // ==========================================================================
79
- // LOGIN
80
- // ==========================================================================
81
- /**
82
- * Login with email and password
83
- * Returns either tokens directly or requires company/branch selection
84
- */ async login(email, password) {
60
+ if (!this.authConfig.isCompanyFeatureEnabled() || !this.dataSourceProvider) return null;
61
+ return this.dataSourceProvider.getRepository(CompanyBranch);
62
+ }
63
+ // Login
64
+ /** Login with email and password. Returns tokens or requires company/branch selection. */ async login(email, password) {
85
65
  const user = await this.validateUser(email, password);
86
66
  if (!user.isActive) {
87
67
  throw new UnauthorizedException('User account is deactivated');
88
68
  }
89
- // If company feature not enabled, return simple token
69
+ // Check email verification if email service is enabled
70
+ if (this.authEmailService?.isEmailEnabled() && !user.emailVerified) {
71
+ return {
72
+ requiresEmailVerification: true,
73
+ email: user.email,
74
+ message: 'Please verify your email address before logging in.'
75
+ };
76
+ }
90
77
  if (!this.authConfig.isCompanyFeatureEnabled()) {
91
78
  return this.generateLoginResponse(user);
92
79
  }
93
80
  return this.handleCompanyBasedLogin(user);
94
81
  }
95
- /**
96
- * Handle login when company feature is enabled
97
- */ async handleCompanyBasedLogin(user) {
82
+ /** Handle login when company feature is enabled */ async handleCompanyBasedLogin(user) {
98
83
  const permissionRepository = await this.getPermissionRepository();
99
84
  const branchRepository = await this.getBranchRepository();
100
85
  if (!permissionRepository || !branchRepository) {
@@ -144,16 +129,9 @@ export class AuthenticationService {
144
129
  // Returns all companies with their branches for frontend to display
145
130
  return await this.generateCompanySelectionResponse(user, companies, branches);
146
131
  }
147
- // ==========================================================================
148
- // REGISTRATION
149
- // ==========================================================================
150
- /**
151
- * Register a new user
152
- * If company feature enabled, optionally join/create company
153
- */ async register(dto) {
132
+ // Registration
133
+ /** Register a new user. If email enabled, creates pending registration. Otherwise creates user directly. */ async register(dto, verifyUrlBase) {
154
134
  const userRepository = await this.getUserRepository();
155
- // Check if user exists
156
- // Note: Generic error message to prevent email enumeration attacks
157
135
  const existingUser = await userRepository.findOne({
158
136
  where: {
159
137
  email: dto.email
@@ -162,7 +140,33 @@ export class AuthenticationService {
162
140
  if (existingUser) {
163
141
  throw new BadRequestException('Registration failed. Please try again or contact support.');
164
142
  }
165
- // Create user
143
+ // If email verification is enabled, create pending registration instead of user
144
+ if (this.authEmailService?.isEmailEnabled() && verifyUrlBase) {
145
+ const companyData = this.authConfig.isCompanyFeatureEnabled() ? {
146
+ companySlug: dto.companySlug,
147
+ branchSlug: dto.branchSlug,
148
+ newCompanyName: dto.newCompanyName,
149
+ newCompanyPhone: dto.newCompanyPhone,
150
+ newCompanyAddress: dto.newCompanyAddress
151
+ } : undefined;
152
+ const result = await this.authEmailService.createPendingRegistration({
153
+ email: dto.email,
154
+ name: dto.name,
155
+ phone: dto.phone,
156
+ password: dto.password,
157
+ companyData,
158
+ verifyUrlBase
159
+ });
160
+ return {
161
+ requiresEmailVerification: true,
162
+ message: result.message
163
+ };
164
+ }
165
+ // No email verification - create user directly
166
+ return this.createUserDirectly(dto);
167
+ }
168
+ /** Create user directly (no email verification) */ async createUserDirectly(dto) {
169
+ const userRepository = await this.getUserRepository();
166
170
  const hashedPassword = await bcrypt.hash(dto.password, BCRYPT_SALT_ROUNDS);
167
171
  const user = userRepository.create({
168
172
  name: dto.name,
@@ -174,40 +178,68 @@ export class AuthenticationService {
174
178
  phoneVerified: false
175
179
  });
176
180
  await userRepository.save(user);
177
- // If company feature not enabled, return simple response
178
181
  if (!this.authConfig.isCompanyFeatureEnabled()) {
179
- return this.generateLoginResponse(user, false);
182
+ return this.generateRegistrationResponse(user, false, false);
180
183
  }
181
- // Handle company feature
182
- return this.handleCompanyRegistration(user, dto);
184
+ return this.handleCompanyRegistration(user, dto, false);
185
+ }
186
+ /** Create user from pending registration (after email verification) */ async createUserFromPendingRegistration(pending) {
187
+ const userRepository = await this.getUserRepository();
188
+ // Check if user already exists (race condition protection)
189
+ const existingUser = await userRepository.findOne({
190
+ where: {
191
+ email: pending.email
192
+ }
193
+ });
194
+ if (existingUser) {
195
+ throw new BadRequestException('This email has already been registered.');
196
+ }
197
+ // Create user with verified email
198
+ const user = userRepository.create({
199
+ name: pending.name,
200
+ email: pending.email,
201
+ phone: pending.phone,
202
+ password: pending.passwordHash,
203
+ isActive: true,
204
+ emailVerified: true,
205
+ phoneVerified: false
206
+ });
207
+ await userRepository.save(user);
208
+ // Handle company feature if enabled
209
+ if (this.authConfig.isCompanyFeatureEnabled() && pending.companyData) {
210
+ return this.handleCompanyRegistrationFromPending(user, pending.companyData);
211
+ }
212
+ return this.generateRegistrationResponse(user, this.authConfig.isCompanyFeatureEnabled(), false);
213
+ }
214
+ /** Handle company registration from pending registration data */ async handleCompanyRegistrationFromPending(user, companyData) {
215
+ return this.processCompanyRegistration(user, companyData, false);
183
216
  }
184
- /**
185
- * Handle company-related registration logic
186
- */ async handleCompanyRegistration(user, dto) {
217
+ /** Handle company-related registration logic */ async handleCompanyRegistration(user, dto, requiresEmailVerification) {
218
+ return this.processCompanyRegistration(user, dto, requiresEmailVerification);
219
+ }
220
+ /** Shared logic for company registration (join existing or create new) */ async processCompanyRegistration(user, data, requiresEmailVerification) {
187
221
  const companyRepository = await this.getCompanyRepository();
188
222
  const branchRepository = await this.getBranchRepository();
189
223
  const permissionRepository = await this.getPermissionRepository();
190
224
  if (!companyRepository || !branchRepository || !permissionRepository) {
191
- return this.generateLoginResponse(user, true);
225
+ return this.generateRegistrationResponse(user, true, requiresEmailVerification);
192
226
  }
193
227
  let company = null;
194
228
  let branch = null;
195
- // Case 1: Join existing company by slug
196
- if (dto.companySlug) {
229
+ if (data.companySlug) {
197
230
  company = await companyRepository.findOne({
198
231
  where: {
199
- slug: dto.companySlug,
232
+ slug: data.companySlug,
200
233
  isActive: true
201
234
  }
202
235
  });
203
236
  if (!company) {
204
237
  throw new BadRequestException('Company not found or inactive');
205
238
  }
206
- // Find branch by slug or get default
207
- if (dto.branchSlug) {
239
+ if (data.branchSlug) {
208
240
  branch = await branchRepository.findOne({
209
241
  where: {
210
- slug: dto.branchSlug,
242
+ slug: data.branchSlug,
211
243
  companyId: company.id,
212
244
  isActive: true
213
245
  }
@@ -216,7 +248,6 @@ export class AuthenticationService {
216
248
  throw new BadRequestException('Branch not found or inactive');
217
249
  }
218
250
  } else {
219
- // Get first active branch
220
251
  branch = await branchRepository.findOne({
221
252
  where: {
222
253
  companyId: company.id,
@@ -228,9 +259,8 @@ export class AuthenticationService {
228
259
  }
229
260
  });
230
261
  }
231
- } else if (dto.newCompanyName) {
232
- const slug = this.generateSlug(dto.newCompanyName);
233
- // Check if slug exists
262
+ } else if (data.newCompanyName) {
263
+ const slug = this.generateSlug(data.newCompanyName);
234
264
  const existingCompany = await companyRepository.findOne({
235
265
  where: {
236
266
  slug
@@ -239,16 +269,14 @@ export class AuthenticationService {
239
269
  if (existingCompany) {
240
270
  throw new BadRequestException('Company creation failed. Please try a different name.');
241
271
  }
242
- // Create company
243
272
  company = companyRepository.create({
244
- name: dto.newCompanyName,
273
+ name: data.newCompanyName,
245
274
  slug,
246
- phone: dto.newCompanyPhone,
247
- address: dto.newCompanyAddress,
275
+ phone: data.newCompanyPhone,
276
+ address: data.newCompanyAddress,
248
277
  isActive: true
249
278
  });
250
279
  await companyRepository.save(company);
251
- // Create default branch
252
280
  branch = branchRepository.create({
253
281
  name: 'Main Branch',
254
282
  slug: 'main-branch',
@@ -257,16 +285,13 @@ export class AuthenticationService {
257
285
  });
258
286
  await branchRepository.save(branch);
259
287
  }
260
- // Create permissions if company exists
261
288
  if (company) {
262
- // Create company permission
263
289
  const companyPermission = permissionRepository.create({
264
290
  userId: user.id,
265
291
  permissionType: PermissionType.COMPANY,
266
292
  targetId: company.id
267
293
  });
268
294
  await permissionRepository.save(companyPermission);
269
- // Create branch permission if branch exists
270
295
  if (branch) {
271
296
  const branchPermission = permissionRepository.create({
272
297
  userId: user.id,
@@ -275,23 +300,15 @@ export class AuthenticationService {
275
300
  });
276
301
  await permissionRepository.save(branchPermission);
277
302
  }
278
- return this.generateLoginResponseWithCompany(user, company, branch ?? undefined);
303
+ return this.generateRegistrationResponseWithCompany(user, company, branch ?? undefined, requiresEmailVerification);
279
304
  }
280
- // No company assigned
281
- return this.generateLoginResponse(user, true);
305
+ return this.generateRegistrationResponse(user, true, requiresEmailVerification);
282
306
  }
283
- /**
284
- * Generate URL-friendly slug from name
285
- */ generateSlug(name) {
307
+ /** Generate URL-friendly slug from name */ generateSlug(name) {
286
308
  return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').substring(0, 100);
287
309
  }
288
- // ==========================================================================
289
- // COMPANY/BRANCH SELECTION
290
- // ==========================================================================
291
- /**
292
- * Select company and branch (step 2 of login)
293
- * Frontend receives all options and user selects both together
294
- */ async select(sessionId, companyId, branchId) {
310
+ // Company/Branch Selection
311
+ /** Select company and branch (step 2 of login). Frontend receives all options and user selects both. */ async select(sessionId, companyId, branchId) {
295
312
  this.ensureCompanyFeatureEnabled();
296
313
  if (!this.sessionService) {
297
314
  throw new BadRequestException('Session service not available');
@@ -358,9 +375,7 @@ export class AuthenticationService {
358
375
  }
359
376
  return this.generateLoginResponseWithCompany(user, company, branch);
360
377
  }
361
- /**
362
- * Switch company/branch after login
363
- */ async switchCompany(userId, companyId, branchId) {
378
+ /** Switch company/branch after login */ async switchCompany(userId, companyId, branchId) {
364
379
  this.ensureCompanyFeatureEnabled();
365
380
  const userRepository = await this.getUserRepository();
366
381
  const user = await userRepository.findOne({
@@ -419,17 +434,17 @@ export class AuthenticationService {
419
434
  }
420
435
  return this.generateLoginResponseWithCompany(user, company, branch);
421
436
  }
422
- // ==========================================================================
423
- // TOKEN MANAGEMENT
424
- // ==========================================================================
425
- /**
426
- * Refresh access token (with token rotation)
427
- * Returns new access token and new refresh token
428
- */ async refreshToken(refreshToken) {
437
+ // Token Management
438
+ /** Refresh access token (with token rotation). Returns new access token and new refresh token. */ async refreshToken(refreshToken) {
429
439
  try {
430
440
  const payload = this.jwtService.verify(refreshToken, {
431
441
  secret: this.authConfig.getRefreshTokenSecret()
432
442
  });
443
+ // Check if token has been revoked (user logged out after token was issued)
444
+ const isRevoked = await this.isTokenRevoked(payload.id, payload.iat);
445
+ if (isRevoked) {
446
+ throw new UnauthorizedException('Token has been revoked');
447
+ }
433
448
  const userRepository = await this.getUserRepository();
434
449
  const user = await userRepository.findOne({
435
450
  where: {
@@ -491,9 +506,7 @@ export class AuthenticationService {
491
506
  throw new UnauthorizedException('Invalid or expired refresh token');
492
507
  }
493
508
  }
494
- /**
495
- * Change password
496
- */ async changePassword(userId, currentPassword, newPassword) {
509
+ /** Change password */ async changePassword(userId, currentPassword, newPassword) {
497
510
  const userRepository = await this.getUserRepository();
498
511
  const user = await userRepository.findOne({
499
512
  where: {
@@ -510,15 +523,52 @@ export class AuthenticationService {
510
523
  user.password = await bcrypt.hash(newPassword, BCRYPT_SALT_ROUNDS);
511
524
  await userRepository.save(user);
512
525
  }
513
- /**
514
- * Logout (placeholder for token blacklisting if needed)
515
- */ async logout(_userId) {}
516
- // ==========================================================================
517
- // QUERY METHODS
518
- // ==========================================================================
519
- /**
520
- * Get user's companies
521
- */ async getUserCompanies(userId) {
526
+ /** Logout - Revokes all tokens. Stores revocation timestamp in cache. */ async logout(userId) {
527
+ if (!this.cacheManager) {
528
+ // Cache is required for secure logout - tokens cannot be revoked without it
529
+ 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.');
530
+ throw new BadRequestException('Logout service temporarily unavailable. Please try again later.');
531
+ }
532
+ const revokedAt = Math.floor(Date.now() / 1000); // Unix timestamp in seconds
533
+ const cacheKey = `${TOKEN_REVOKED_PREFIX}:${userId}`;
534
+ // TTL matches refresh token expiry (tokens older than this are already expired)
535
+ const refreshTokenExpiry = this.authConfig.getRefreshTokenExpiration();
536
+ const ttlMs = this.parseExpiryToMs(refreshTokenExpiry);
537
+ await this.cacheManager.set(cacheKey, revokedAt, ttlMs);
538
+ this.logger.log(`User ${userId} logged out - all tokens revoked`);
539
+ }
540
+ /** Check if a token is revoked (issued before user's revocation timestamp) */ async isTokenRevoked(userId, tokenIssuedAt) {
541
+ if (!this.cacheManager || !tokenIssuedAt) {
542
+ return false;
543
+ }
544
+ const cacheKey = `${TOKEN_REVOKED_PREFIX}:${userId}`;
545
+ const revokedAt = await this.cacheManager.get(cacheKey);
546
+ if (!revokedAt) {
547
+ return false;
548
+ }
549
+ // Token is revoked if it was issued before the revocation timestamp
550
+ return tokenIssuedAt < revokedAt;
551
+ }
552
+ /** Parse JWT expiry string to milliseconds */ parseExpiryToMs(expiry) {
553
+ const match = expiry.match(/^(\d+)([smhd])$/);
554
+ if (!match) return 7 * 24 * 60 * 60 * 1000; // Default 7 days
555
+ const value = parseInt(match[1], 10);
556
+ const unit = match[2];
557
+ switch(unit){
558
+ case 's':
559
+ return value * 1000;
560
+ case 'm':
561
+ return value * 60 * 1000;
562
+ case 'h':
563
+ return value * 60 * 60 * 1000;
564
+ case 'd':
565
+ return value * 24 * 60 * 60 * 1000;
566
+ default:
567
+ return 7 * 24 * 60 * 60 * 1000;
568
+ }
569
+ }
570
+ // Query Methods
571
+ /** Get user's companies */ async getUserCompanies(userId) {
522
572
  this.ensureCompanyFeatureEnabled();
523
573
  const permissionRepository = await this.getPermissionRepository();
524
574
  const companyRepository = await this.getCompanyRepository();
@@ -539,9 +589,7 @@ export class AuthenticationService {
539
589
  active: true
540
590
  }).orderBy('c.name', 'ASC').getMany();
541
591
  }
542
- /**
543
- * Get branches for a company that user has access to
544
- */ async getUserBranches(userId, companyId) {
592
+ /** Get branches for a company that user has access to */ async getUserBranches(userId, companyId) {
545
593
  this.ensureCompanyFeatureEnabled();
546
594
  const permissionRepository = await this.getPermissionRepository();
547
595
  const branchRepository = await this.getBranchRepository();
@@ -576,9 +624,7 @@ export class AuthenticationService {
576
624
  active: true
577
625
  }).orderBy('b.serial', 'ASC').addOrderBy('b.name', 'ASC').getMany();
578
626
  }
579
- /**
580
- * Get company by ID
581
- */ async getCompanyById(companyId) {
627
+ /** Get company by ID */ async getCompanyById(companyId) {
582
628
  const companyRepository = await this.getCompanyRepository();
583
629
  if (!companyRepository) {
584
630
  return null;
@@ -589,9 +635,7 @@ export class AuthenticationService {
589
635
  }
590
636
  });
591
637
  }
592
- /**
593
- * Get branch by ID
594
- */ async getBranchById(branchId) {
638
+ /** Get branch by ID */ async getBranchById(branchId) {
595
639
  const branchRepository = await this.getBranchRepository();
596
640
  if (!branchRepository) {
597
641
  return null;
@@ -602,10 +646,7 @@ export class AuthenticationService {
602
646
  }
603
647
  });
604
648
  }
605
- /**
606
- * Get user info for /auth/me endpoint
607
- * Returns only user, company, and branch information (no tokens)
608
- */ async getUserInfo(userId, companyId, branchId) {
649
+ /** Get user info for /auth/me endpoint. Returns user, company, and branch info (no tokens). */ async getUserInfo(userId, companyId, branchId) {
609
650
  const userRepository = await this.getUserRepository();
610
651
  const user = await userRepository.findOne({
611
652
  where: {
@@ -621,6 +662,7 @@ export class AuthenticationService {
621
662
  id: user.id,
622
663
  email: user.email,
623
664
  name: user.name ?? undefined,
665
+ phone: user.phone ?? undefined,
624
666
  profilePictureId: user.profilePictureId ?? undefined
625
667
  }
626
668
  };
@@ -656,9 +698,7 @@ export class AuthenticationService {
656
698
  }
657
699
  return response;
658
700
  }
659
- // ==========================================================================
660
- // HELPER METHODS
661
- // ==========================================================================
701
+ // Helper Methods
662
702
  ensureCompanyFeatureEnabled() {
663
703
  if (!this.authConfig.isCompanyFeatureEnabled()) {
664
704
  throw new BadRequestException('Company feature is not enabled');
@@ -682,9 +722,7 @@ export class AuthenticationService {
682
722
  await userRepository.save(user);
683
723
  return user;
684
724
  }
685
- // ==========================================================================
686
- // RESPONSE GENERATORS
687
- // ==========================================================================
725
+ // Response Generators
688
726
  generateLoginResponse(user, companyFeatureEnabled = false) {
689
727
  const payload = {
690
728
  id: user.id,
@@ -711,6 +749,7 @@ export class AuthenticationService {
711
749
  id: user.id,
712
750
  email: user.email,
713
751
  name: user.name ?? undefined,
752
+ phone: user.phone ?? null,
714
753
  profilePictureId: user.profilePictureId ?? null
715
754
  },
716
755
  companyFeatureEnabled
@@ -746,6 +785,7 @@ export class AuthenticationService {
746
785
  id: user.id,
747
786
  email: user.email,
748
787
  name: user.name ?? undefined,
788
+ phone: user.phone ?? null,
749
789
  profilePictureId: user.profilePictureId ?? null
750
790
  },
751
791
  company: {
@@ -791,30 +831,83 @@ export class AuthenticationService {
791
831
  id: user.id,
792
832
  email: user.email,
793
833
  name: user.name ?? undefined,
834
+ phone: user.phone ?? null,
794
835
  profilePictureId: user.profilePictureId ?? null
795
836
  },
796
837
  companies: companiesWithBranches
797
838
  };
798
839
  }
840
+ /** Generate registration response - tokens included only if email verification not required */ generateRegistrationResponse(user, companyFeatureEnabled, requiresEmailVerification) {
841
+ const userInfo = {
842
+ id: user.id,
843
+ email: user.email,
844
+ name: user.name ?? undefined,
845
+ phone: user.phone ?? null,
846
+ profilePictureId: user.profilePictureId ?? null
847
+ };
848
+ if (requiresEmailVerification) {
849
+ return {
850
+ user: userInfo,
851
+ companyFeatureEnabled,
852
+ requiresEmailVerification: true
853
+ };
854
+ }
855
+ const loginResponse = this.generateLoginResponse(user, companyFeatureEnabled);
856
+ return {
857
+ ...loginResponse,
858
+ requiresEmailVerification: false
859
+ };
860
+ }
861
+ /** Generate registration response with company - tokens included only if email verification not required */ generateRegistrationResponseWithCompany(user, company, branch, requiresEmailVerification = false) {
862
+ const userInfo = {
863
+ id: user.id,
864
+ email: user.email,
865
+ name: user.name ?? undefined,
866
+ phone: user.phone ?? null,
867
+ profilePictureId: user.profilePictureId ?? null
868
+ };
869
+ const companyInfo = {
870
+ id: company.id,
871
+ name: company.name,
872
+ slug: company.slug,
873
+ logoId: company.logoId ?? null
874
+ };
875
+ const branchInfo = branch ? {
876
+ id: branch.id,
877
+ name: branch.name,
878
+ slug: branch.slug,
879
+ logoId: branch.logoId ?? null
880
+ } : undefined;
881
+ if (requiresEmailVerification) {
882
+ return {
883
+ user: userInfo,
884
+ company: companyInfo,
885
+ branch: branchInfo,
886
+ companyFeatureEnabled: true,
887
+ requiresEmailVerification: true
888
+ };
889
+ }
890
+ const loginResponse = this.generateLoginResponseWithCompany(user, company, branch);
891
+ return {
892
+ ...loginResponse,
893
+ requiresEmailVerification: false
894
+ };
895
+ }
799
896
  // NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
800
- constructor(jwtService, authConfig, dataSourceProvider, sessionService, injectedUserRepository, injectedPermissionRepository, injectedCompanyRepository, injectedBranchRepository){
897
+ constructor(jwtService, authConfig, cacheManager, dataSourceProvider, sessionService, authEmailService){
801
898
  _define_property(this, "jwtService", void 0);
802
899
  _define_property(this, "authConfig", void 0);
900
+ _define_property(this, "cacheManager", void 0);
803
901
  _define_property(this, "dataSourceProvider", void 0);
804
902
  _define_property(this, "sessionService", void 0);
805
- _define_property(this, "injectedUserRepository", void 0);
806
- _define_property(this, "injectedPermissionRepository", void 0);
807
- _define_property(this, "injectedCompanyRepository", void 0);
808
- _define_property(this, "injectedBranchRepository", void 0);
903
+ _define_property(this, "authEmailService", void 0);
809
904
  _define_property(this, "logger", void 0);
810
905
  this.jwtService = jwtService;
811
906
  this.authConfig = authConfig;
907
+ this.cacheManager = cacheManager;
812
908
  this.dataSourceProvider = dataSourceProvider;
813
909
  this.sessionService = sessionService;
814
- this.injectedUserRepository = injectedUserRepository;
815
- this.injectedPermissionRepository = injectedPermissionRepository;
816
- this.injectedCompanyRepository = injectedCompanyRepository;
817
- this.injectedBranchRepository = injectedBranchRepository;
910
+ this.authEmailService = authEmailService;
818
911
  this.logger = new Logger(AuthenticationService.name);
819
912
  }
820
913
  }
@@ -825,26 +918,20 @@ AuthenticationService = _ts_decorate([
825
918
  _ts_param(0, Inject(JwtService)),
826
919
  _ts_param(1, Inject(AuthConfigService)),
827
920
  _ts_param(2, Optional()),
828
- _ts_param(2, Inject(AuthDataSourceProvider)),
921
+ _ts_param(2, Inject('CACHE_INSTANCE')),
829
922
  _ts_param(3, Optional()),
830
- _ts_param(3, Inject(CompanySelectionSessionService)),
923
+ _ts_param(3, Inject(AuthDataSourceProvider)),
831
924
  _ts_param(4, Optional()),
832
- _ts_param(4, InjectRepository(User)),
925
+ _ts_param(4, Inject(CompanySelectionSessionService)),
833
926
  _ts_param(5, Optional()),
834
- _ts_param(5, InjectRepository(UserCompanyPermission)),
835
- _ts_param(6, Optional()),
836
- _ts_param(6, InjectRepository(Company)),
837
- _ts_param(7, Optional()),
838
- _ts_param(7, InjectRepository(CompanyBranch)),
927
+ _ts_param(5, Inject(AuthEmailService)),
839
928
  _ts_metadata("design:type", Function),
840
929
  _ts_metadata("design:paramtypes", [
841
930
  typeof JwtService === "undefined" ? Object : JwtService,
842
931
  typeof AuthConfigService === "undefined" ? Object : AuthConfigService,
932
+ typeof HybridCache === "undefined" ? Object : HybridCache,
843
933
  typeof AuthDataSourceProvider === "undefined" ? Object : AuthDataSourceProvider,
844
934
  typeof CompanySelectionSessionService === "undefined" ? Object : CompanySelectionSessionService,
845
- typeof Repository === "undefined" ? Object : Repository,
846
- typeof Repository === "undefined" ? Object : Repository,
847
- typeof Repository === "undefined" ? Object : Repository,
848
- typeof Repository === "undefined" ? Object : Repository
935
+ typeof AuthEmailService === "undefined" ? Object : AuthEmailService
849
936
  ])
850
937
  ], AuthenticationService);