@flusys/nestjs-auth 1.1.0-beta → 2.0.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
@@ -8,22 +8,20 @@ Object.defineProperty(exports, "AuthenticationService", {
8
8
  return AuthenticationService;
9
9
  }
10
10
  });
11
+ const _classes = require("@flusys/nestjs-shared/classes");
12
+ const _utils = require("@flusys/nestjs-shared/utils");
11
13
  const _common = require("@nestjs/common");
12
14
  const _jwt = require("@nestjs/jwt");
13
- const _typeorm = require("@nestjs/typeorm");
14
15
  const _bcrypt = /*#__PURE__*/ _interop_require_wildcard(require("bcrypt"));
15
- const _typeorm1 = require("typeorm");
16
- const _utils = require("@flusys/nestjs-shared/utils");
17
- const _authconfigservice = require("./auth-config.service");
18
- const _authdatasourceprovider = require("./auth-datasource.provider");
19
- const _enums = require("../enums");
20
16
  const _config = require("../config");
21
- const _classes = require("@flusys/nestjs-shared/classes");
22
- const _authemailservice = require("./auth-email.service");
23
17
  const _companybranchentity = require("../entities/company-branch.entity");
24
18
  const _companyentity = require("../entities/company.entity");
25
19
  const _usercompanypermissionentity = require("../entities/user-company-permission.entity");
26
20
  const _userentity = require("../entities/user.entity");
21
+ const _enums = require("../enums");
22
+ const _authconfigservice = require("./auth-config.service");
23
+ const _authdatasourceprovider = require("./auth-datasource.provider");
24
+ const _authemailservice = require("./auth-email.service");
27
25
  const _companyselectionsessionservice = require("./company-selection-session.service");
28
26
  function _define_property(obj, key, value) {
29
27
  if (key in obj) {
@@ -94,44 +92,43 @@ function _ts_param(paramIndex, decorator) {
94
92
  };
95
93
  }
96
94
  let AuthenticationService = class AuthenticationService {
95
+ // Feature Flags
96
+ /** Check if company feature is enabled (tenant-aware with global fallback) */ isCompanyFeatureEnabled() {
97
+ return this.dataSourceProvider?.isCompanyFeatureEnabled() ?? this.authConfig.isCompanyFeatureEnabled();
98
+ }
97
99
  // Repository Getters
98
100
  async getUserRepository() {
99
- if (this.dataSourceProvider) {
100
- return this.dataSourceProvider.getRepository(_userentity.User);
101
- }
102
- if (this.injectedUserRepository) {
103
- return this.injectedUserRepository;
101
+ if (!this.dataSourceProvider) {
102
+ throw new Error('User repository not available');
104
103
  }
105
- throw new Error('User repository not available');
104
+ return this.dataSourceProvider.getRepository(_userentity.User);
106
105
  }
107
106
  async getPermissionRepository() {
108
- if (!this.authConfig.isCompanyFeatureEnabled()) return null;
109
- if (this.dataSourceProvider) {
110
- return this.dataSourceProvider.getRepository(_usercompanypermissionentity.UserCompanyPermission);
111
- }
112
- return this.injectedPermissionRepository ?? null;
107
+ if (!this.isCompanyFeatureEnabled() || !this.dataSourceProvider) return null;
108
+ return this.dataSourceProvider.getRepository(_usercompanypermissionentity.UserCompanyPermission);
113
109
  }
114
110
  async getCompanyRepository() {
115
- if (!this.authConfig.isCompanyFeatureEnabled()) return null;
116
- if (this.dataSourceProvider) {
117
- return this.dataSourceProvider.getRepository(_companyentity.Company);
118
- }
119
- return this.injectedCompanyRepository ?? null;
111
+ if (!this.isCompanyFeatureEnabled() || !this.dataSourceProvider) return null;
112
+ return this.dataSourceProvider.getRepository(_companyentity.Company);
120
113
  }
121
114
  async getBranchRepository() {
122
- if (!this.authConfig.isCompanyFeatureEnabled()) return null;
123
- if (this.dataSourceProvider) {
124
- return this.dataSourceProvider.getRepository(_companybranchentity.CompanyBranch);
125
- }
126
- return this.injectedBranchRepository ?? null;
115
+ if (!this.isCompanyFeatureEnabled() || !this.dataSourceProvider) return null;
116
+ return this.dataSourceProvider.getRepository(_companybranchentity.CompanyBranch);
127
117
  }
128
118
  // Login
129
- /** Login with email and password. Returns tokens or requires company/branch selection. */ async login(email, password) {
119
+ async login(email, password) {
120
+ // Check for pending registration first (user registered but hasn't verified email yet)
121
+ if (await this.authEmailService?.hasPendingRegistration(email)) {
122
+ return {
123
+ requiresEmailVerification: true,
124
+ email,
125
+ message: 'Please verify your email address to complete registration.'
126
+ };
127
+ }
130
128
  const user = await this.validateUser(email, password);
131
129
  if (!user.isActive) {
132
130
  throw new _common.UnauthorizedException('User account is deactivated');
133
131
  }
134
- // Check email verification if email service is enabled
135
132
  if (this.authEmailService?.isEmailEnabled() && !user.emailVerified) {
136
133
  return {
137
134
  requiresEmailVerification: true,
@@ -139,7 +136,7 @@ let AuthenticationService = class AuthenticationService {
139
136
  message: 'Please verify your email address before logging in.'
140
137
  };
141
138
  }
142
- if (!this.authConfig.isCompanyFeatureEnabled()) {
139
+ if (!this.isCompanyFeatureEnabled()) {
143
140
  return this.generateLoginResponse(user);
144
141
  }
145
142
  return this.handleCompanyBasedLogin(user);
@@ -171,7 +168,7 @@ let AuthenticationService = class AuthenticationService {
171
168
  // If user has no company assignments yet, return simple login response
172
169
  // This allows users to login and be assigned to companies later
173
170
  if (companies.length === 0) {
174
- return this.generateLoginResponse(user, true);
171
+ return this.generateLoginResponse(user);
175
172
  }
176
173
  // Get branch permissions
177
174
  const branchPermissions = await permissionRepository.find({
@@ -202,12 +199,17 @@ let AuthenticationService = class AuthenticationService {
202
199
  email: dto.email
203
200
  }
204
201
  });
202
+ // When company feature is enabled and user exists, allow joining/creating company
203
+ if (existingUser && this.isCompanyFeatureEnabled()) {
204
+ return this.handleExistingUserCompanyRegistration(existingUser, dto);
205
+ }
206
+ // User exists but company feature is off - email must be unique
205
207
  if (existingUser) {
206
- throw new _common.BadRequestException('Registration failed. Please try again or contact support.');
208
+ throw new _common.BadRequestException('Registration failed due to email exists. Please try again or contact support.');
207
209
  }
208
210
  // If email verification is enabled, create pending registration instead of user
209
211
  if (this.authEmailService?.isEmailEnabled() && verifyUrlBase) {
210
- const companyData = this.authConfig.isCompanyFeatureEnabled() ? {
212
+ const companyData = this.isCompanyFeatureEnabled() ? {
211
213
  companySlug: dto.companySlug,
212
214
  branchSlug: dto.branchSlug,
213
215
  newCompanyName: dto.newCompanyName,
@@ -230,6 +232,38 @@ let AuthenticationService = class AuthenticationService {
230
232
  // No email verification - create user directly
231
233
  return this.createUserDirectly(dto);
232
234
  }
235
+ /** Handle registration for existing user when company feature is enabled */ async handleExistingUserCompanyRegistration(existingUser, dto) {
236
+ if (!dto.companySlug && !dto.newCompanyName) {
237
+ throw new _common.BadRequestException('Email already registered. Please login or provide a company to join/create.');
238
+ }
239
+ const permissionRepository = await this.getPermissionRepository();
240
+ const companyRepository = await this.getCompanyRepository();
241
+ if (!permissionRepository || !companyRepository) {
242
+ throw new _common.BadRequestException('Company feature repositories not available');
243
+ }
244
+ // For existing users joining a company, check if already assigned
245
+ if (dto.companySlug) {
246
+ const company = await companyRepository.findOne({
247
+ where: {
248
+ slug: dto.companySlug,
249
+ isActive: true
250
+ }
251
+ });
252
+ if (company) {
253
+ const existingPermission = await permissionRepository.findOne({
254
+ where: {
255
+ userId: existingUser.id,
256
+ permissionType: _enums.PermissionType.COMPANY,
257
+ targetId: company.id
258
+ }
259
+ });
260
+ if (existingPermission) {
261
+ throw new _common.BadRequestException('You are already assigned to this company. Please login.');
262
+ }
263
+ }
264
+ }
265
+ return this.processCompanyRegistration(existingUser, dto, true);
266
+ }
233
267
  /** Create user directly (no email verification) */ async createUserDirectly(dto) {
234
268
  const userRepository = await this.getUserRepository();
235
269
  const hashedPassword = await _bcrypt.hash(dto.password, _config.BCRYPT_SALT_ROUNDS);
@@ -243,14 +277,13 @@ let AuthenticationService = class AuthenticationService {
243
277
  phoneVerified: false
244
278
  });
245
279
  await userRepository.save(user);
246
- if (!this.authConfig.isCompanyFeatureEnabled()) {
247
- return this.generateRegistrationResponse(user, false, false);
280
+ if (!this.isCompanyFeatureEnabled()) {
281
+ return this.generateRegistrationResponse(user);
248
282
  }
249
- return this.handleCompanyRegistration(user, dto, false);
283
+ return this.processCompanyRegistration(user, dto);
250
284
  }
251
285
  /** Create user from pending registration (after email verification) */ async createUserFromPendingRegistration(pending) {
252
286
  const userRepository = await this.getUserRepository();
253
- // Check if user already exists (race condition protection)
254
287
  const existingUser = await userRepository.findOne({
255
288
  where: {
256
289
  email: pending.email
@@ -259,7 +292,6 @@ let AuthenticationService = class AuthenticationService {
259
292
  if (existingUser) {
260
293
  throw new _common.BadRequestException('This email has already been registered.');
261
294
  }
262
- // Create user with verified email
263
295
  const user = userRepository.create({
264
296
  name: pending.name,
265
297
  email: pending.email,
@@ -270,107 +302,103 @@ let AuthenticationService = class AuthenticationService {
270
302
  phoneVerified: false
271
303
  });
272
304
  await userRepository.save(user);
273
- // Handle company feature if enabled
274
- if (this.authConfig.isCompanyFeatureEnabled() && pending.companyData) {
275
- return this.handleCompanyRegistrationFromPending(user, pending.companyData);
305
+ if (this.isCompanyFeatureEnabled() && pending.companyData) {
306
+ return this.processCompanyRegistration(user, pending.companyData);
276
307
  }
277
- return this.generateRegistrationResponse(user, this.authConfig.isCompanyFeatureEnabled(), false);
308
+ return this.generateRegistrationResponse(user);
278
309
  }
279
- /** Handle company registration from pending registration data */ async handleCompanyRegistrationFromPending(user, companyData) {
280
- return this.processCompanyRegistration(user, companyData, false);
281
- }
282
- /** Handle company-related registration logic */ async handleCompanyRegistration(user, dto, requiresEmailVerification) {
283
- return this.processCompanyRegistration(user, dto, requiresEmailVerification);
284
- }
285
- /** Shared logic for company registration (join existing or create new) */ async processCompanyRegistration(user, data, requiresEmailVerification) {
310
+ /** Process company registration (join existing or create new) */ async processCompanyRegistration(user, data, throwOnUnavailable = false) {
286
311
  const companyRepository = await this.getCompanyRepository();
287
312
  const branchRepository = await this.getBranchRepository();
288
313
  const permissionRepository = await this.getPermissionRepository();
289
314
  if (!companyRepository || !branchRepository || !permissionRepository) {
290
- return this.generateRegistrationResponse(user, true, requiresEmailVerification);
315
+ if (throwOnUnavailable) throw new _common.BadRequestException('Company feature repositories not available');
316
+ return this.generateRegistrationResponse(user);
317
+ }
318
+ const { company, branch } = await this.findOrCreateCompanyAndBranch(data, companyRepository, branchRepository);
319
+ if (company) {
320
+ await this.assignUserPermissions(user.id, company.id, branch?.id, permissionRepository);
321
+ return this.generateRegistrationResponseWithCompany(user, company, branch ?? undefined);
291
322
  }
292
- let company = null;
293
- let branch = null;
323
+ return this.generateRegistrationResponse(user);
324
+ }
325
+ /** Find existing company/branch or create new ones */ async findOrCreateCompanyAndBranch(data, companyRepo, branchRepo) {
294
326
  if (data.companySlug) {
295
- company = await companyRepository.findOne({
327
+ const company = await companyRepo.findOne({
296
328
  where: {
297
329
  slug: data.companySlug,
298
330
  isActive: true
299
331
  }
300
332
  });
301
- if (!company) {
302
- throw new _common.BadRequestException('Company not found or inactive');
303
- }
304
- if (data.branchSlug) {
305
- branch = await branchRepository.findOne({
306
- where: {
307
- slug: data.branchSlug,
308
- companyId: company.id,
309
- isActive: true
310
- }
311
- });
312
- if (!branch) {
313
- throw new _common.BadRequestException('Branch not found or inactive');
333
+ if (!company) throw new _common.BadRequestException('Company not found or inactive');
334
+ const branch = data.branchSlug ? await branchRepo.findOne({
335
+ where: {
336
+ slug: data.branchSlug,
337
+ companyId: company.id,
338
+ isActive: true
314
339
  }
315
- } else {
316
- branch = await branchRepository.findOne({
317
- where: {
318
- companyId: company.id,
319
- isActive: true
320
- },
321
- order: {
322
- serial: 'ASC',
323
- createdAt: 'ASC'
324
- }
325
- });
326
- }
327
- } else if (data.newCompanyName) {
328
- const slug = this.generateSlug(data.newCompanyName);
329
- const existingCompany = await companyRepository.findOne({
340
+ }) : await branchRepo.findOne({
330
341
  where: {
331
- slug
342
+ companyId: company.id,
343
+ isActive: true
344
+ },
345
+ order: {
346
+ serial: 'ASC',
347
+ createdAt: 'ASC'
332
348
  }
333
349
  });
334
- if (existingCompany) {
335
- throw new _common.BadRequestException('Company creation failed. Please try a different name.');
336
- }
337
- company = companyRepository.create({
350
+ if (data.branchSlug && !branch) throw new _common.BadRequestException('Branch not found or inactive');
351
+ return {
352
+ company,
353
+ branch
354
+ };
355
+ }
356
+ if (data.newCompanyName) {
357
+ const slug = await (0, _utils.generateUniqueSlug)(data.newCompanyName, async (baseSlug)=>{
358
+ const rows = await companyRepo.createQueryBuilder('c').select('c.slug').where('c.slug = :base OR c.slug LIKE :pattern', {
359
+ base: baseSlug,
360
+ pattern: `${baseSlug}-%`
361
+ }).getMany();
362
+ return rows.map((r)=>r.slug);
363
+ });
364
+ const company = companyRepo.create({
338
365
  name: data.newCompanyName,
339
366
  slug,
340
367
  phone: data.newCompanyPhone,
341
368
  address: data.newCompanyAddress,
342
369
  isActive: true
343
370
  });
344
- await companyRepository.save(company);
345
- branch = branchRepository.create({
371
+ await companyRepo.save(company);
372
+ const branch = branchRepo.create({
346
373
  name: 'Main Branch',
347
374
  slug: 'main-branch',
348
375
  companyId: company.id,
349
376
  isActive: true
350
377
  });
351
- await branchRepository.save(branch);
352
- }
353
- if (company) {
354
- const companyPermission = permissionRepository.create({
355
- userId: user.id,
356
- permissionType: _enums.PermissionType.COMPANY,
357
- targetId: company.id
358
- });
359
- await permissionRepository.save(companyPermission);
360
- if (branch) {
361
- const branchPermission = permissionRepository.create({
362
- userId: user.id,
363
- permissionType: _enums.PermissionType.BRANCH,
364
- targetId: branch.id
365
- });
366
- await permissionRepository.save(branchPermission);
367
- }
368
- return this.generateRegistrationResponseWithCompany(user, company, branch ?? undefined, requiresEmailVerification);
378
+ await branchRepo.save(branch);
379
+ return {
380
+ company,
381
+ branch
382
+ };
369
383
  }
370
- return this.generateRegistrationResponse(user, true, requiresEmailVerification);
384
+ return {
385
+ company: null,
386
+ branch: null
387
+ };
371
388
  }
372
- /** Generate URL-friendly slug from name */ generateSlug(name) {
373
- return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').substring(0, 100);
389
+ /** Assign user to company and optionally branch */ async assignUserPermissions(userId, companyId, branchId, permissionRepo) {
390
+ await permissionRepo.save(permissionRepo.create({
391
+ userId,
392
+ permissionType: _enums.PermissionType.COMPANY,
393
+ targetId: companyId
394
+ }));
395
+ if (branchId) {
396
+ await permissionRepo.save(permissionRepo.create({
397
+ userId,
398
+ permissionType: _enums.PermissionType.BRANCH,
399
+ targetId: branchId
400
+ }));
401
+ }
374
402
  }
375
403
  // Company/Branch Selection
376
404
  /** Select company and branch (step 2 of login). Frontend receives all options and user selects both. */ async select(sessionId, companyId, branchId) {
@@ -382,66 +410,13 @@ let AuthenticationService = class AuthenticationService {
382
410
  if (!session) {
383
411
  throw new _common.UnauthorizedException('Invalid or expired session');
384
412
  }
385
- const userRepository = await this.getUserRepository();
386
- const user = await userRepository.findOne({
387
- where: {
388
- id: session.userId
389
- }
390
- });
391
- if (!user) {
392
- throw new _common.NotFoundException('User not found');
393
- }
394
- const permissionRepository = await this.getPermissionRepository();
395
- const companyRepository = await this.getCompanyRepository();
396
- const branchRepository = await this.getBranchRepository();
397
- if (!permissionRepository || !companyRepository || !branchRepository) {
398
- throw new _common.BadRequestException('Repositories not available');
399
- }
400
- // Validate company access
401
- const companyPermission = await permissionRepository.findOne({
402
- where: {
403
- userId: user.id,
404
- permissionType: _enums.PermissionType.COMPANY,
405
- targetId: companyId
406
- }
407
- });
408
- if (!companyPermission) {
409
- throw new _common.UnauthorizedException('No access to this company');
410
- }
411
- const company = await companyRepository.findOne({
412
- where: {
413
- id: companyId,
414
- isActive: true
415
- }
416
- });
417
- if (!company) {
418
- throw new _common.UnauthorizedException('Company not found or inactive');
419
- }
420
- // Validate branch access
421
- const branchPermission = await permissionRepository.findOne({
422
- where: {
423
- userId: user.id,
424
- permissionType: _enums.PermissionType.BRANCH,
425
- targetId: branchId
426
- }
427
- });
428
- if (!branchPermission) {
429
- throw new _common.UnauthorizedException('No access to this branch');
430
- }
431
- const branch = await branchRepository.findOne({
432
- where: {
433
- id: branchId,
434
- companyId,
435
- isActive: true
436
- }
437
- });
438
- if (!branch) {
439
- throw new _common.UnauthorizedException('Branch not found or inactive');
440
- }
441
- return this.generateLoginResponseWithCompany(user, company, branch);
413
+ return this.validateAndLoginWithCompany(session.userId, companyId, branchId);
442
414
  }
443
415
  /** Switch company/branch after login */ async switchCompany(userId, companyId, branchId) {
444
416
  this.ensureCompanyFeatureEnabled();
417
+ return this.validateAndLoginWithCompany(userId, companyId, branchId);
418
+ }
419
+ /** Shared: Validate user access to company/branch and generate login response */ async validateAndLoginWithCompany(userId, companyId, branchId) {
445
420
  const userRepository = await this.getUserRepository();
446
421
  const user = await userRepository.findOne({
447
422
  where: {
@@ -458,14 +433,14 @@ let AuthenticationService = class AuthenticationService {
458
433
  throw new _common.BadRequestException('Repositories not available');
459
434
  }
460
435
  // Validate company access
461
- const companyPermission = await permissionRepository.findOne({
436
+ const hasCompanyAccess = await permissionRepository.findOne({
462
437
  where: {
463
438
  userId,
464
439
  permissionType: _enums.PermissionType.COMPANY,
465
440
  targetId: companyId
466
441
  }
467
442
  });
468
- if (!companyPermission) {
443
+ if (!hasCompanyAccess) {
469
444
  throw new _common.UnauthorizedException('No access to this company');
470
445
  }
471
446
  const company = await companyRepository.findOne({
@@ -477,14 +452,15 @@ let AuthenticationService = class AuthenticationService {
477
452
  if (!company) {
478
453
  throw new _common.UnauthorizedException('Company not found or inactive');
479
454
  }
480
- const branchPermission = await permissionRepository.findOne({
455
+ // Validate branch access
456
+ const hasBranchAccess = await permissionRepository.findOne({
481
457
  where: {
482
458
  userId,
483
459
  permissionType: _enums.PermissionType.BRANCH,
484
460
  targetId: branchId
485
461
  }
486
462
  });
487
- if (!branchPermission) {
463
+ if (!hasBranchAccess) {
488
464
  throw new _common.UnauthorizedException('No access to this branch');
489
465
  }
490
466
  const branch = await branchRepository.findOne({
@@ -505,6 +481,9 @@ let AuthenticationService = class AuthenticationService {
505
481
  const payload = this.jwtService.verify(refreshToken, {
506
482
  secret: this.authConfig.getRefreshTokenSecret()
507
483
  });
484
+ if (await this.isTokenRevoked(payload.id, payload.iat)) {
485
+ throw new _common.UnauthorizedException('Token has been revoked');
486
+ }
508
487
  const userRepository = await this.getUserRepository();
509
488
  const user = await userRepository.findOne({
510
489
  where: {
@@ -514,7 +493,7 @@ let AuthenticationService = class AuthenticationService {
514
493
  if (!user || !user.isActive) {
515
494
  throw new _common.UnauthorizedException('Invalid refresh token');
516
495
  }
517
- const accessPayload = {
496
+ return this.generateTokens({
518
497
  id: user.id,
519
498
  email: user.email,
520
499
  type: 'access',
@@ -524,44 +503,9 @@ let AuthenticationService = class AuthenticationService {
524
503
  },
525
504
  ...payload.branchId && {
526
505
  branchId: payload.branchId
527
- },
528
- ...payload.companyLogoId && {
529
- companyLogoId: payload.companyLogoId
530
- },
531
- ...payload.branchLogoId && {
532
- branchLogoId: payload.branchLogoId
533
506
  }
534
- };
535
- const refreshPayload = {
536
- id: user.id,
537
- email: user.email,
538
- type: 'refresh',
539
- profilePictureId: user.profilePictureId ?? undefined,
540
- ...payload.companyId && {
541
- companyId: payload.companyId
542
- },
543
- ...payload.branchId && {
544
- branchId: payload.branchId
545
- },
546
- ...payload.companyLogoId && {
547
- companyLogoId: payload.companyLogoId
548
- },
549
- ...payload.branchLogoId && {
550
- branchLogoId: payload.branchLogoId
551
- }
552
- };
553
- return {
554
- accessToken: this.jwtService.sign(accessPayload, {
555
- secret: this.authConfig.getJwtSecret(),
556
- expiresIn: this.authConfig.getJwtExpiration()
557
- }),
558
- refreshToken: this.jwtService.sign(refreshPayload, {
559
- secret: this.authConfig.getRefreshTokenSecret(),
560
- expiresIn: this.authConfig.getRefreshTokenExpiration()
561
- })
562
- };
507
+ });
563
508
  } catch (error) {
564
- // Log error details for debugging (not exposed to client)
565
509
  _utils.ErrorHandler.logError(this.logger, error, 'refreshToken');
566
510
  throw new _common.UnauthorizedException('Invalid or expired refresh token');
567
511
  }
@@ -573,11 +517,8 @@ let AuthenticationService = class AuthenticationService {
573
517
  id: userId
574
518
  }
575
519
  });
576
- if (!user) {
577
- throw new _common.UnauthorizedException('User not found');
578
- }
579
- const isPasswordValid = await _bcrypt.compare(currentPassword, user.password || '');
580
- if (!isPasswordValid) {
520
+ if (!user) throw new _common.UnauthorizedException('User not found');
521
+ if (!await _bcrypt.compare(currentPassword, user.password || '')) {
581
522
  throw new _common.UnauthorizedException('Current password is incorrect');
582
523
  }
583
524
  user.password = await _bcrypt.hash(newPassword, _config.BCRYPT_SALT_ROUNDS);
@@ -585,8 +526,9 @@ let AuthenticationService = class AuthenticationService {
585
526
  }
586
527
  /** Logout - Revokes all tokens. Stores revocation timestamp in cache. */ async logout(userId) {
587
528
  if (!this.cacheManager) {
588
- // Cache not available - graceful degradation (client-side logout only)
589
- return;
529
+ // Cache is required for secure logout - tokens cannot be revoked without it
530
+ 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.');
531
+ throw new _common.BadRequestException('Logout service temporarily unavailable. Please try again later.');
590
532
  }
591
533
  const revokedAt = Math.floor(Date.now() / 1000); // Unix timestamp in seconds
592
534
  const cacheKey = `${_config.TOKEN_REVOKED_PREFIX}:${userId}`;
@@ -594,6 +536,7 @@ let AuthenticationService = class AuthenticationService {
594
536
  const refreshTokenExpiry = this.authConfig.getRefreshTokenExpiration();
595
537
  const ttlMs = this.parseExpiryToMs(refreshTokenExpiry);
596
538
  await this.cacheManager.set(cacheKey, revokedAt, ttlMs);
539
+ this.logger.log(`User ${userId} logged out - all tokens revoked`);
597
540
  }
598
541
  /** Check if a token is revoked (issued before user's revocation timestamp) */ async isTokenRevoked(userId, tokenIssuedAt) {
599
542
  if (!this.cacheManager || !tokenIssuedAt) {
@@ -711,54 +654,28 @@ let AuthenticationService = class AuthenticationService {
711
654
  id: userId
712
655
  }
713
656
  });
714
- if (!user) {
715
- throw new _common.UnauthorizedException('User not found');
716
- }
717
- // Build response (no tokens)
657
+ if (!user) throw new _common.UnauthorizedException('User not found');
718
658
  const response = {
719
- user: {
720
- id: user.id,
721
- email: user.email,
722
- name: user.name ?? undefined,
723
- phone: user.phone ?? undefined,
724
- profilePictureId: user.profilePictureId ?? undefined
725
- }
659
+ user: this.toUserInfo(user)
726
660
  };
727
- // Add company/branch if available (when company feature is enabled)
728
- if (this.authConfig.isCompanyFeatureEnabled()) {
729
- // When company feature is enabled, companyId MUST be present
661
+ if (this.isCompanyFeatureEnabled()) {
730
662
  if (!companyId) {
731
- throw new _common.UnauthorizedException('Company feature is enabled but user has no company assigned. Please select a company.');
663
+ throw new _common.UnauthorizedException('Company feature is enabled but user has no company assigned.');
732
664
  }
733
665
  const company = await this.getCompanyById(companyId);
734
- if (!company) {
735
- throw new _common.UnauthorizedException(`Company with ID ${companyId} not found. Please select a valid company.`);
736
- }
737
- response.company = {
738
- id: company.id,
739
- name: company.name,
740
- slug: company.slug,
741
- logoId: company.logoId ?? null
742
- };
743
- // Branch is optional in some configurations, but if provided it must exist
666
+ if (!company) throw new _common.UnauthorizedException('Company not found.');
667
+ response.company = this.toCompanyInfo(company);
744
668
  if (branchId) {
745
669
  const branch = await this.getBranchById(branchId);
746
- if (!branch) {
747
- throw new _common.UnauthorizedException(`Branch with ID ${branchId} not found. Please select a valid branch.`);
748
- }
749
- response.branch = {
750
- id: branch.id,
751
- name: branch.name,
752
- slug: branch.slug,
753
- logoId: branch.logoId ?? null
754
- };
670
+ if (!branch) throw new _common.UnauthorizedException('Branch not found.');
671
+ response.branch = this.toBranchInfo(branch);
755
672
  }
756
673
  }
757
674
  return response;
758
675
  }
759
676
  // Helper Methods
760
677
  ensureCompanyFeatureEnabled() {
761
- if (!this.authConfig.isCompanyFeatureEnabled()) {
678
+ if (!this.isCompanyFeatureEnabled()) {
762
679
  throw new _common.BadRequestException('Company feature is not enabled');
763
680
  }
764
681
  }
@@ -769,11 +686,7 @@ let AuthenticationService = class AuthenticationService {
769
686
  email
770
687
  }
771
688
  });
772
- if (!user) {
773
- throw new _common.UnauthorizedException('Invalid credentials');
774
- }
775
- const isPasswordValid = await _bcrypt.compare(password, user.password || '');
776
- if (!isPasswordValid) {
689
+ if (!user || !await _bcrypt.compare(password, user.password || '')) {
777
690
  throw new _common.UnauthorizedException('Invalid credentials');
778
691
  }
779
692
  user.lastLoginAt = new Date();
@@ -781,84 +694,77 @@ let AuthenticationService = class AuthenticationService {
781
694
  return user;
782
695
  }
783
696
  // Response Generators
784
- generateLoginResponse(user, companyFeatureEnabled = false) {
785
- const payload = {
697
+ /** Create user info object */ toUserInfo(user) {
698
+ return {
786
699
  id: user.id,
787
700
  email: user.email,
788
- type: 'access'
701
+ name: user.name ?? undefined,
702
+ phone: user.phone ?? null,
703
+ profilePictureId: user.profilePictureId ?? null
704
+ };
705
+ }
706
+ /** Create company info object */ toCompanyInfo(company) {
707
+ return {
708
+ id: company.id,
709
+ name: company.name,
710
+ slug: company.slug,
711
+ logoId: company.logoId ?? null
712
+ };
713
+ }
714
+ /** Create branch info object */ toBranchInfo(branch) {
715
+ return {
716
+ id: branch.id,
717
+ name: branch.name,
718
+ slug: branch.slug,
719
+ logoId: branch.logoId ?? null
789
720
  };
790
- const accessToken = this.jwtService.sign(payload, {
721
+ }
722
+ /** Generate JWT tokens */ generateTokens(payload) {
723
+ const accessToken = this.jwtService.sign({
724
+ ...payload,
725
+ type: 'access'
726
+ }, {
791
727
  secret: this.authConfig.getJwtSecret(),
792
728
  expiresIn: this.authConfig.getJwtExpiration()
793
729
  });
794
- const refreshPayload = {
795
- id: user.id,
796
- email: user.email,
730
+ const refreshToken = this.jwtService.sign({
731
+ ...payload,
797
732
  type: 'refresh'
798
- };
799
- const refreshToken = this.jwtService.sign(refreshPayload, {
733
+ }, {
800
734
  secret: this.authConfig.getRefreshTokenSecret(),
801
735
  expiresIn: this.authConfig.getRefreshTokenExpiration()
802
736
  });
803
737
  return {
804
738
  accessToken,
805
- refreshToken,
806
- user: {
807
- id: user.id,
808
- email: user.email,
809
- name: user.name ?? undefined,
810
- phone: user.phone ?? null,
811
- profilePictureId: user.profilePictureId ?? null
812
- },
813
- companyFeatureEnabled
739
+ refreshToken
814
740
  };
815
741
  }
816
- generateLoginResponseWithCompany(user, company, branch) {
817
- const payload = {
742
+ generateLoginResponse(user) {
743
+ const { accessToken, refreshToken } = this.generateTokens({
818
744
  id: user.id,
819
745
  email: user.email,
820
- type: 'access',
821
- companyId: company.id,
822
- branchId: branch?.id
823
- };
824
- const accessToken = this.jwtService.sign(payload, {
825
- secret: this.authConfig.getJwtSecret(),
826
- expiresIn: this.authConfig.getJwtExpiration()
746
+ type: 'access'
827
747
  });
828
- const refreshPayload = {
748
+ return {
749
+ accessToken,
750
+ refreshToken,
751
+ user: this.toUserInfo(user)
752
+ };
753
+ }
754
+ generateLoginResponseWithCompany(user, company, branch) {
755
+ const { accessToken, refreshToken } = this.generateTokens({
829
756
  id: user.id,
830
757
  email: user.email,
831
- type: 'refresh',
758
+ type: 'access',
832
759
  companyId: company.id,
833
760
  branchId: branch?.id
834
- };
835
- const refreshToken = this.jwtService.sign(refreshPayload, {
836
- secret: this.authConfig.getRefreshTokenSecret(),
837
- expiresIn: this.authConfig.getRefreshTokenExpiration()
838
761
  });
839
762
  return {
840
763
  accessToken,
841
764
  refreshToken,
842
- user: {
843
- id: user.id,
844
- email: user.email,
845
- name: user.name ?? undefined,
846
- phone: user.phone ?? null,
847
- profilePictureId: user.profilePictureId ?? null
848
- },
849
- company: {
850
- id: company.id,
851
- name: company.name,
852
- slug: company.slug,
853
- logoId: company.logoId ?? null
854
- },
855
- branch: branch ? {
856
- id: branch.id,
857
- name: branch.name,
858
- slug: branch.slug,
859
- logoId: branch.logoId ?? null
860
- } : undefined,
861
- companyFeatureEnabled: true
765
+ user: this.toUserInfo(user),
766
+ company: this.toCompanyInfo(company),
767
+ branch: branch ? this.toBranchInfo(branch) : undefined
862
768
  };
863
769
  }
864
770
  async generateCompanySelectionResponse(user, companies, branches) {
@@ -866,103 +772,42 @@ let AuthenticationService = class AuthenticationService {
866
772
  throw new _common.BadRequestException('Session service not available');
867
773
  }
868
774
  const sessionId = await this.sessionService.createSession(user.id);
869
- // Build companies with their branches
870
775
  const companiesWithBranches = companies.map((company)=>({
871
- id: company.id,
872
- name: company.name,
873
- slug: company.slug,
776
+ ...this.toCompanyInfo(company),
874
777
  isActive: company.isActive,
875
- logoId: company.logoId ?? null,
876
778
  branches: branches.filter((b)=>b.companyId === company.id).map((b)=>({
877
- id: b.id,
878
- name: b.name,
879
- slug: b.slug,
880
- isActive: b.isActive,
881
- logoId: b.logoId ?? null
779
+ ...this.toBranchInfo(b),
780
+ isActive: b.isActive
882
781
  }))
883
782
  }));
884
783
  return {
885
784
  requiresSelection: true,
886
785
  sessionId,
887
786
  expiresAt: new Date(Date.now() + 5 * 60 * 1000),
888
- user: {
889
- id: user.id,
890
- email: user.email,
891
- name: user.name ?? undefined,
892
- phone: user.phone ?? null,
893
- profilePictureId: user.profilePictureId ?? null
894
- },
787
+ user: this.toUserInfo(user),
895
788
  companies: companiesWithBranches
896
789
  };
897
790
  }
898
- /** Generate registration response - tokens included only if email verification not required */ generateRegistrationResponse(user, companyFeatureEnabled, requiresEmailVerification) {
899
- const userInfo = {
900
- id: user.id,
901
- email: user.email,
902
- name: user.name ?? undefined,
903
- phone: user.phone ?? null,
904
- profilePictureId: user.profilePictureId ?? null
905
- };
906
- if (requiresEmailVerification) {
907
- return {
908
- user: userInfo,
909
- companyFeatureEnabled,
910
- requiresEmailVerification: true
911
- };
912
- }
913
- const loginResponse = this.generateLoginResponse(user, companyFeatureEnabled);
791
+ generateRegistrationResponse(user) {
914
792
  return {
915
- ...loginResponse,
793
+ ...this.generateLoginResponse(user),
916
794
  requiresEmailVerification: false
917
795
  };
918
796
  }
919
- /** Generate registration response with company - tokens included only if email verification not required */ generateRegistrationResponseWithCompany(user, company, branch, requiresEmailVerification = false) {
920
- const userInfo = {
921
- id: user.id,
922
- email: user.email,
923
- name: user.name ?? undefined,
924
- phone: user.phone ?? null,
925
- profilePictureId: user.profilePictureId ?? null
926
- };
927
- const companyInfo = {
928
- id: company.id,
929
- name: company.name,
930
- slug: company.slug,
931
- logoId: company.logoId ?? null
932
- };
933
- const branchInfo = branch ? {
934
- id: branch.id,
935
- name: branch.name,
936
- slug: branch.slug,
937
- logoId: branch.logoId ?? null
938
- } : undefined;
939
- if (requiresEmailVerification) {
940
- return {
941
- user: userInfo,
942
- company: companyInfo,
943
- branch: branchInfo,
944
- companyFeatureEnabled: true,
945
- requiresEmailVerification: true
946
- };
947
- }
948
- const loginResponse = this.generateLoginResponseWithCompany(user, company, branch);
797
+ generateRegistrationResponseWithCompany(user, company, branch) {
949
798
  return {
950
- ...loginResponse,
799
+ ...this.generateLoginResponseWithCompany(user, company, branch),
951
800
  requiresEmailVerification: false
952
801
  };
953
802
  }
954
803
  // NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
955
- constructor(jwtService, authConfig, cacheManager, dataSourceProvider, sessionService, authEmailService, injectedUserRepository, injectedPermissionRepository, injectedCompanyRepository, injectedBranchRepository){
804
+ constructor(jwtService, authConfig, cacheManager, dataSourceProvider, sessionService, authEmailService){
956
805
  _define_property(this, "jwtService", void 0);
957
806
  _define_property(this, "authConfig", void 0);
958
807
  _define_property(this, "cacheManager", void 0);
959
808
  _define_property(this, "dataSourceProvider", void 0);
960
809
  _define_property(this, "sessionService", void 0);
961
810
  _define_property(this, "authEmailService", void 0);
962
- _define_property(this, "injectedUserRepository", void 0);
963
- _define_property(this, "injectedPermissionRepository", void 0);
964
- _define_property(this, "injectedCompanyRepository", void 0);
965
- _define_property(this, "injectedBranchRepository", void 0);
966
811
  _define_property(this, "logger", void 0);
967
812
  this.jwtService = jwtService;
968
813
  this.authConfig = authConfig;
@@ -970,10 +815,6 @@ let AuthenticationService = class AuthenticationService {
970
815
  this.dataSourceProvider = dataSourceProvider;
971
816
  this.sessionService = sessionService;
972
817
  this.authEmailService = authEmailService;
973
- this.injectedUserRepository = injectedUserRepository;
974
- this.injectedPermissionRepository = injectedPermissionRepository;
975
- this.injectedCompanyRepository = injectedCompanyRepository;
976
- this.injectedBranchRepository = injectedBranchRepository;
977
818
  this.logger = new _common.Logger(AuthenticationService.name);
978
819
  }
979
820
  };
@@ -991,14 +832,6 @@ AuthenticationService = _ts_decorate([
991
832
  _ts_param(4, (0, _common.Inject)(_companyselectionsessionservice.CompanySelectionSessionService)),
992
833
  _ts_param(5, (0, _common.Optional)()),
993
834
  _ts_param(5, (0, _common.Inject)(_authemailservice.AuthEmailService)),
994
- _ts_param(6, (0, _common.Optional)()),
995
- _ts_param(6, (0, _typeorm.InjectRepository)(_userentity.User)),
996
- _ts_param(7, (0, _common.Optional)()),
997
- _ts_param(7, (0, _typeorm.InjectRepository)(_usercompanypermissionentity.UserCompanyPermission)),
998
- _ts_param(8, (0, _common.Optional)()),
999
- _ts_param(8, (0, _typeorm.InjectRepository)(_companyentity.Company)),
1000
- _ts_param(9, (0, _common.Optional)()),
1001
- _ts_param(9, (0, _typeorm.InjectRepository)(_companybranchentity.CompanyBranch)),
1002
835
  _ts_metadata("design:type", Function),
1003
836
  _ts_metadata("design:paramtypes", [
1004
837
  typeof _jwt.JwtService === "undefined" ? Object : _jwt.JwtService,
@@ -1006,10 +839,6 @@ AuthenticationService = _ts_decorate([
1006
839
  typeof _classes.HybridCache === "undefined" ? Object : _classes.HybridCache,
1007
840
  typeof _authdatasourceprovider.AuthDataSourceProvider === "undefined" ? Object : _authdatasourceprovider.AuthDataSourceProvider,
1008
841
  typeof _companyselectionsessionservice.CompanySelectionSessionService === "undefined" ? Object : _companyselectionsessionservice.CompanySelectionSessionService,
1009
- typeof _authemailservice.AuthEmailService === "undefined" ? Object : _authemailservice.AuthEmailService,
1010
- typeof _typeorm1.Repository === "undefined" ? Object : _typeorm1.Repository,
1011
- typeof _typeorm1.Repository === "undefined" ? Object : _typeorm1.Repository,
1012
- typeof _typeorm1.Repository === "undefined" ? Object : _typeorm1.Repository,
1013
- typeof _typeorm1.Repository === "undefined" ? Object : _typeorm1.Repository
842
+ typeof _authemailservice.AuthEmailService === "undefined" ? Object : _authemailservice.AuthEmailService
1014
843
  ])
1015
844
  ], AuthenticationService);