@flusys/nestjs-auth 1.0.0-beta → 1.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 (106) hide show
  1. package/README.md +590 -473
  2. package/cjs/config/auth.constants.js +12 -0
  3. package/cjs/controllers/authentication.controller.js +365 -24
  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 +211 -7
  9. package/cjs/docs/auth-swagger.config.js +14 -7
  10. package/cjs/dtos/authentication.dto.js +114 -38
  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/index.js +5 -1
  15. package/cjs/entities/pending-registration.entity.js +125 -0
  16. package/cjs/entities/user-company-permission.entity.js +3 -9
  17. package/cjs/entities/user-token.entity.js +129 -0
  18. package/cjs/interceptors/clear-token.interceptor.js +10 -23
  19. package/cjs/interceptors/set-token.interceptor.js +15 -55
  20. package/cjs/interfaces/auth-email-provider.interface.js +45 -0
  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 +65 -143
  24. package/cjs/services/auth-config.service.js +25 -27
  25. package/cjs/services/auth-datasource.provider.js +13 -23
  26. package/cjs/services/auth-email.service.js +456 -0
  27. package/cjs/services/authentication.service.js +297 -349
  28. package/cjs/services/branch.service.js +9 -4
  29. package/cjs/services/company-selection-session.service.js +1 -1
  30. package/cjs/services/index.js +1 -0
  31. package/cjs/services/user-permission.service.js +43 -73
  32. package/cjs/services/user.service.js +184 -34
  33. package/cjs/strategies/jwt.strategy.js +44 -44
  34. package/cjs/validators/password.validator.js +45 -0
  35. package/config/auth.constants.d.ts +3 -0
  36. package/controllers/authentication.controller.d.ts +16 -10
  37. package/controllers/company-selection.controller.d.ts +6 -2
  38. package/controllers/user-permission.controller.d.ts +5 -4
  39. package/controllers/user.controller.d.ts +33 -0
  40. package/dtos/authentication.dto.d.ts +20 -6
  41. package/dtos/company-branch.dto.d.ts +0 -1
  42. package/dtos/company.dto.d.ts +0 -1
  43. package/dtos/index.d.ts +0 -1
  44. package/dtos/user-permission.dto.d.ts +4 -0
  45. package/dtos/user.dto.d.ts +1 -2
  46. package/entities/index.d.ts +7 -3
  47. package/entities/pending-registration.entity.d.ts +17 -0
  48. package/entities/user-company-permission.entity.d.ts +1 -3
  49. package/entities/user-token.entity.d.ts +15 -0
  50. package/fesm/config/auth.constants.js +6 -2
  51. package/fesm/controllers/authentication.controller.js +368 -27
  52. package/fesm/controllers/branch.controller.js +45 -2
  53. package/fesm/controllers/company-selection.controller.js +28 -21
  54. package/fesm/controllers/company.controller.js +45 -2
  55. package/fesm/controllers/user-permission.controller.js +84 -156
  56. package/fesm/controllers/user.controller.js +214 -10
  57. package/fesm/docs/auth-swagger.config.js +14 -7
  58. package/fesm/dtos/authentication.dto.js +101 -39
  59. package/fesm/dtos/index.js +0 -1
  60. package/fesm/dtos/user-permission.dto.js +28 -0
  61. package/fesm/dtos/user.dto.js +20 -5
  62. package/fesm/entities/index.js +7 -1
  63. package/fesm/entities/pending-registration.entity.js +115 -0
  64. package/fesm/entities/user-company-permission.entity.js +3 -9
  65. package/fesm/entities/user-token.entity.js +113 -0
  66. package/fesm/interceptors/clear-token.interceptor.js +9 -22
  67. package/fesm/interceptors/set-token.interceptor.js +14 -54
  68. package/fesm/interfaces/auth-email-provider.interface.js +38 -0
  69. package/fesm/interfaces/authentication.interface.js +1 -1
  70. package/fesm/interfaces/index.js +2 -0
  71. package/fesm/interfaces/user-enricher.interface.js +26 -0
  72. package/fesm/modules/auth.module.js +67 -145
  73. package/fesm/services/auth-config.service.js +26 -28
  74. package/fesm/services/auth-datasource.provider.js +14 -24
  75. package/fesm/services/auth-email.service.js +405 -0
  76. package/fesm/services/authentication.service.js +297 -349
  77. package/fesm/services/branch.service.js +9 -4
  78. package/fesm/services/company-selection-session.service.js +1 -1
  79. package/fesm/services/index.js +1 -0
  80. package/fesm/services/user-permission.service.js +44 -74
  81. package/fesm/services/user.service.js +186 -36
  82. package/fesm/strategies/jwt.strategy.js +44 -44
  83. package/fesm/validators/password.validator.js +48 -0
  84. package/interceptors/clear-token.interceptor.d.ts +0 -1
  85. package/interceptors/set-token.interceptor.d.ts +1 -1
  86. package/interfaces/auth-email-provider.interface.d.ts +6 -0
  87. package/interfaces/auth-module-options.interface.d.ts +4 -1
  88. package/interfaces/authentication.interface.d.ts +13 -3
  89. package/interfaces/index.d.ts +2 -0
  90. package/interfaces/user-enricher.interface.d.ts +64 -0
  91. package/interfaces/user.interface.d.ts +1 -0
  92. package/modules/auth.module.d.ts +6 -3
  93. package/package.json +4 -4
  94. package/services/auth-config.service.d.ts +7 -8
  95. package/services/auth-datasource.provider.d.ts +2 -4
  96. package/services/auth-email.service.d.ts +54 -0
  97. package/services/authentication.service.d.ts +27 -16
  98. package/services/company-selection-session.service.d.ts +1 -1
  99. package/services/index.d.ts +1 -0
  100. package/services/user-permission.service.d.ts +6 -9
  101. package/services/user.service.d.ts +19 -3
  102. package/strategies/jwt.strategy.d.ts +4 -1
  103. package/validators/password.validator.d.ts +10 -0
  104. package/cjs/dtos/user-company-permission.dto.js +0 -110
  105. package/dtos/user-company-permission.dto.d.ts +0 -10
  106. package/fesm/dtos/user-company-permission.dto.js +0 -98
@@ -8,21 +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
17
  const _companybranchentity = require("../entities/company-branch.entity");
23
18
  const _companyentity = require("../entities/company.entity");
24
19
  const _usercompanypermissionentity = require("../entities/user-company-permission.entity");
25
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");
26
25
  const _companyselectionsessionservice = require("./company-selection-session.service");
27
26
  function _define_property(obj, key, value) {
28
27
  if (key in obj) {
@@ -93,45 +92,51 @@ function _ts_param(paramIndex, decorator) {
93
92
  };
94
93
  }
95
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
+ }
96
99
  // Repository Getters
97
100
  async getUserRepository() {
98
- if (this.dataSourceProvider) {
99
- return this.dataSourceProvider.getRepository(_userentity.User);
100
- }
101
- if (this.injectedUserRepository) {
102
- return this.injectedUserRepository;
101
+ if (!this.dataSourceProvider) {
102
+ throw new Error('User repository not available');
103
103
  }
104
- throw new Error('User repository not available');
104
+ return this.dataSourceProvider.getRepository(_userentity.User);
105
105
  }
106
106
  async getPermissionRepository() {
107
- if (!this.authConfig.isCompanyFeatureEnabled()) return null;
108
- if (this.dataSourceProvider) {
109
- return this.dataSourceProvider.getRepository(_usercompanypermissionentity.UserCompanyPermission);
110
- }
111
- return this.injectedPermissionRepository ?? null;
107
+ if (!this.isCompanyFeatureEnabled() || !this.dataSourceProvider) return null;
108
+ return this.dataSourceProvider.getRepository(_usercompanypermissionentity.UserCompanyPermission);
112
109
  }
113
110
  async getCompanyRepository() {
114
- if (!this.authConfig.isCompanyFeatureEnabled()) return null;
115
- if (this.dataSourceProvider) {
116
- return this.dataSourceProvider.getRepository(_companyentity.Company);
117
- }
118
- return this.injectedCompanyRepository ?? null;
111
+ if (!this.isCompanyFeatureEnabled() || !this.dataSourceProvider) return null;
112
+ return this.dataSourceProvider.getRepository(_companyentity.Company);
119
113
  }
120
114
  async getBranchRepository() {
121
- if (!this.authConfig.isCompanyFeatureEnabled()) return null;
122
- if (this.dataSourceProvider) {
123
- return this.dataSourceProvider.getRepository(_companybranchentity.CompanyBranch);
124
- }
125
- return this.injectedBranchRepository ?? null;
115
+ if (!this.isCompanyFeatureEnabled() || !this.dataSourceProvider) return null;
116
+ return this.dataSourceProvider.getRepository(_companybranchentity.CompanyBranch);
126
117
  }
127
118
  // Login
128
- /** 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
+ }
129
128
  const user = await this.validateUser(email, password);
130
129
  if (!user.isActive) {
131
130
  throw new _common.UnauthorizedException('User account is deactivated');
132
131
  }
133
- // If company feature not enabled, return simple token
134
- if (!this.authConfig.isCompanyFeatureEnabled()) {
132
+ if (this.authEmailService?.isEmailEnabled() && !user.emailVerified) {
133
+ return {
134
+ requiresEmailVerification: true,
135
+ email: user.email,
136
+ message: 'Please verify your email address before logging in.'
137
+ };
138
+ }
139
+ if (!this.isCompanyFeatureEnabled()) {
135
140
  return this.generateLoginResponse(user);
136
141
  }
137
142
  return this.handleCompanyBasedLogin(user);
@@ -163,7 +168,7 @@ let AuthenticationService = class AuthenticationService {
163
168
  // If user has no company assignments yet, return simple login response
164
169
  // This allows users to login and be assigned to companies later
165
170
  if (companies.length === 0) {
166
- return this.generateLoginResponse(user, true);
171
+ return this.generateLoginResponse(user);
167
172
  }
168
173
  // Get branch permissions
169
174
  const branchPermissions = await permissionRepository.find({
@@ -187,19 +192,80 @@ let AuthenticationService = class AuthenticationService {
187
192
  return await this.generateCompanySelectionResponse(user, companies, branches);
188
193
  }
189
194
  // Registration
190
- /** Register a new user. If company feature enabled, optionally join/create company. */ async register(dto) {
195
+ /** Register a new user. If email enabled, creates pending registration. Otherwise creates user directly. */ async register(dto, verifyUrlBase) {
191
196
  const userRepository = await this.getUserRepository();
192
- // Check if user exists
193
- // Note: Generic error message to prevent email enumeration attacks
194
197
  const existingUser = await userRepository.findOne({
195
198
  where: {
196
199
  email: dto.email
197
200
  }
198
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
199
207
  if (existingUser) {
200
- 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.');
209
+ }
210
+ // If email verification is enabled, create pending registration instead of user
211
+ if (this.authEmailService?.isEmailEnabled() && verifyUrlBase) {
212
+ const companyData = this.isCompanyFeatureEnabled() ? {
213
+ companySlug: dto.companySlug,
214
+ branchSlug: dto.branchSlug,
215
+ newCompanyName: dto.newCompanyName,
216
+ newCompanyPhone: dto.newCompanyPhone,
217
+ newCompanyAddress: dto.newCompanyAddress
218
+ } : undefined;
219
+ const result = await this.authEmailService.createPendingRegistration({
220
+ email: dto.email,
221
+ name: dto.name,
222
+ phone: dto.phone,
223
+ password: dto.password,
224
+ companyData,
225
+ verifyUrlBase
226
+ });
227
+ return {
228
+ requiresEmailVerification: true,
229
+ message: result.message
230
+ };
201
231
  }
202
- // Create user
232
+ // No email verification - create user directly
233
+ return this.createUserDirectly(dto);
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
+ }
267
+ /** Create user directly (no email verification) */ async createUserDirectly(dto) {
268
+ const userRepository = await this.getUserRepository();
203
269
  const hashedPassword = await _bcrypt.hash(dto.password, _config.BCRYPT_SALT_ROUNDS);
204
270
  const user = userRepository.create({
205
271
  name: dto.name,
@@ -211,112 +277,128 @@ let AuthenticationService = class AuthenticationService {
211
277
  phoneVerified: false
212
278
  });
213
279
  await userRepository.save(user);
214
- // If company feature not enabled, return simple response
215
- if (!this.authConfig.isCompanyFeatureEnabled()) {
216
- return this.generateLoginResponse(user, false);
280
+ if (!this.isCompanyFeatureEnabled()) {
281
+ return this.generateRegistrationResponse(user);
217
282
  }
218
- // Handle company feature
219
- return this.handleCompanyRegistration(user, dto);
283
+ return this.processCompanyRegistration(user, dto);
220
284
  }
221
- /** Handle company-related registration logic */ async handleCompanyRegistration(user, dto) {
285
+ /** Create user from pending registration (after email verification) */ async createUserFromPendingRegistration(pending) {
286
+ const userRepository = await this.getUserRepository();
287
+ const existingUser = await userRepository.findOne({
288
+ where: {
289
+ email: pending.email
290
+ }
291
+ });
292
+ if (existingUser) {
293
+ throw new _common.BadRequestException('This email has already been registered.');
294
+ }
295
+ const user = userRepository.create({
296
+ name: pending.name,
297
+ email: pending.email,
298
+ phone: pending.phone,
299
+ password: pending.passwordHash,
300
+ isActive: true,
301
+ emailVerified: true,
302
+ phoneVerified: false
303
+ });
304
+ await userRepository.save(user);
305
+ if (this.isCompanyFeatureEnabled() && pending.companyData) {
306
+ return this.processCompanyRegistration(user, pending.companyData);
307
+ }
308
+ return this.generateRegistrationResponse(user);
309
+ }
310
+ /** Process company registration (join existing or create new) */ async processCompanyRegistration(user, data, throwOnUnavailable = false) {
222
311
  const companyRepository = await this.getCompanyRepository();
223
312
  const branchRepository = await this.getBranchRepository();
224
313
  const permissionRepository = await this.getPermissionRepository();
225
314
  if (!companyRepository || !branchRepository || !permissionRepository) {
226
- return this.generateLoginResponse(user, true);
315
+ if (throwOnUnavailable) throw new _common.BadRequestException('Company feature repositories not available');
316
+ return this.generateRegistrationResponse(user);
227
317
  }
228
- let company = null;
229
- let branch = null;
230
- // Case 1: Join existing company by slug
231
- if (dto.companySlug) {
232
- company = await companyRepository.findOne({
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);
322
+ }
323
+ return this.generateRegistrationResponse(user);
324
+ }
325
+ /** Find existing company/branch or create new ones */ async findOrCreateCompanyAndBranch(data, companyRepo, branchRepo) {
326
+ if (data.companySlug) {
327
+ const company = await companyRepo.findOne({
233
328
  where: {
234
- slug: dto.companySlug,
329
+ slug: data.companySlug,
235
330
  isActive: true
236
331
  }
237
332
  });
238
- if (!company) {
239
- throw new _common.BadRequestException('Company not found or inactive');
240
- }
241
- // Find branch by slug or get default
242
- if (dto.branchSlug) {
243
- branch = await branchRepository.findOne({
244
- where: {
245
- slug: dto.branchSlug,
246
- companyId: company.id,
247
- isActive: true
248
- }
249
- });
250
- if (!branch) {
251
- 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
252
339
  }
253
- } else {
254
- // Get first active branch
255
- branch = await branchRepository.findOne({
256
- where: {
257
- companyId: company.id,
258
- isActive: true
259
- },
260
- order: {
261
- serial: 'ASC',
262
- createdAt: 'ASC'
263
- }
264
- });
265
- }
266
- } else if (dto.newCompanyName) {
267
- const slug = this.generateSlug(dto.newCompanyName);
268
- // Check if slug exists
269
- const existingCompany = await companyRepository.findOne({
340
+ }) : await branchRepo.findOne({
270
341
  where: {
271
- slug
342
+ companyId: company.id,
343
+ isActive: true
344
+ },
345
+ order: {
346
+ serial: 'ASC',
347
+ createdAt: 'ASC'
272
348
  }
273
349
  });
274
- if (existingCompany) {
275
- throw new _common.BadRequestException('Company creation failed. Please try a different name.');
276
- }
277
- // Create company
278
- company = companyRepository.create({
279
- name: dto.newCompanyName,
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({
365
+ name: data.newCompanyName,
280
366
  slug,
281
- phone: dto.newCompanyPhone,
282
- address: dto.newCompanyAddress,
367
+ phone: data.newCompanyPhone,
368
+ address: data.newCompanyAddress,
283
369
  isActive: true
284
370
  });
285
- await companyRepository.save(company);
286
- // Create default branch
287
- branch = branchRepository.create({
371
+ await companyRepo.save(company);
372
+ const branch = branchRepo.create({
288
373
  name: 'Main Branch',
289
374
  slug: 'main-branch',
290
375
  companyId: company.id,
291
376
  isActive: true
292
377
  });
293
- await branchRepository.save(branch);
294
- }
295
- // Create permissions if company exists
296
- if (company) {
297
- // Create company permission
298
- const companyPermission = permissionRepository.create({
299
- userId: user.id,
300
- permissionType: _enums.PermissionType.COMPANY,
301
- targetId: company.id
302
- });
303
- await permissionRepository.save(companyPermission);
304
- // Create branch permission if branch exists
305
- if (branch) {
306
- const branchPermission = permissionRepository.create({
307
- userId: user.id,
308
- permissionType: _enums.PermissionType.BRANCH,
309
- targetId: branch.id
310
- });
311
- await permissionRepository.save(branchPermission);
312
- }
313
- return this.generateLoginResponseWithCompany(user, company, branch ?? undefined);
378
+ await branchRepo.save(branch);
379
+ return {
380
+ company,
381
+ branch
382
+ };
314
383
  }
315
- // No company assigned
316
- return this.generateLoginResponse(user, true);
384
+ return {
385
+ company: null,
386
+ branch: null
387
+ };
317
388
  }
318
- /** Generate URL-friendly slug from name */ generateSlug(name) {
319
- 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
+ }
320
402
  }
321
403
  // Company/Branch Selection
322
404
  /** Select company and branch (step 2 of login). Frontend receives all options and user selects both. */ async select(sessionId, companyId, branchId) {
@@ -328,66 +410,13 @@ let AuthenticationService = class AuthenticationService {
328
410
  if (!session) {
329
411
  throw new _common.UnauthorizedException('Invalid or expired session');
330
412
  }
331
- const userRepository = await this.getUserRepository();
332
- const user = await userRepository.findOne({
333
- where: {
334
- id: session.userId
335
- }
336
- });
337
- if (!user) {
338
- throw new _common.NotFoundException('User not found');
339
- }
340
- const permissionRepository = await this.getPermissionRepository();
341
- const companyRepository = await this.getCompanyRepository();
342
- const branchRepository = await this.getBranchRepository();
343
- if (!permissionRepository || !companyRepository || !branchRepository) {
344
- throw new _common.BadRequestException('Repositories not available');
345
- }
346
- // Validate company access
347
- const companyPermission = await permissionRepository.findOne({
348
- where: {
349
- userId: user.id,
350
- permissionType: _enums.PermissionType.COMPANY,
351
- targetId: companyId
352
- }
353
- });
354
- if (!companyPermission) {
355
- throw new _common.UnauthorizedException('No access to this company');
356
- }
357
- const company = await companyRepository.findOne({
358
- where: {
359
- id: companyId,
360
- isActive: true
361
- }
362
- });
363
- if (!company) {
364
- throw new _common.UnauthorizedException('Company not found or inactive');
365
- }
366
- // Validate branch access
367
- const branchPermission = await permissionRepository.findOne({
368
- where: {
369
- userId: user.id,
370
- permissionType: _enums.PermissionType.BRANCH,
371
- targetId: branchId
372
- }
373
- });
374
- if (!branchPermission) {
375
- throw new _common.UnauthorizedException('No access to this branch');
376
- }
377
- const branch = await branchRepository.findOne({
378
- where: {
379
- id: branchId,
380
- companyId,
381
- isActive: true
382
- }
383
- });
384
- if (!branch) {
385
- throw new _common.UnauthorizedException('Branch not found or inactive');
386
- }
387
- return this.generateLoginResponseWithCompany(user, company, branch);
413
+ return this.validateAndLoginWithCompany(session.userId, companyId, branchId);
388
414
  }
389
415
  /** Switch company/branch after login */ async switchCompany(userId, companyId, branchId) {
390
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) {
391
420
  const userRepository = await this.getUserRepository();
392
421
  const user = await userRepository.findOne({
393
422
  where: {
@@ -404,14 +433,14 @@ let AuthenticationService = class AuthenticationService {
404
433
  throw new _common.BadRequestException('Repositories not available');
405
434
  }
406
435
  // Validate company access
407
- const companyPermission = await permissionRepository.findOne({
436
+ const hasCompanyAccess = await permissionRepository.findOne({
408
437
  where: {
409
438
  userId,
410
439
  permissionType: _enums.PermissionType.COMPANY,
411
440
  targetId: companyId
412
441
  }
413
442
  });
414
- if (!companyPermission) {
443
+ if (!hasCompanyAccess) {
415
444
  throw new _common.UnauthorizedException('No access to this company');
416
445
  }
417
446
  const company = await companyRepository.findOne({
@@ -423,14 +452,15 @@ let AuthenticationService = class AuthenticationService {
423
452
  if (!company) {
424
453
  throw new _common.UnauthorizedException('Company not found or inactive');
425
454
  }
426
- const branchPermission = await permissionRepository.findOne({
455
+ // Validate branch access
456
+ const hasBranchAccess = await permissionRepository.findOne({
427
457
  where: {
428
458
  userId,
429
459
  permissionType: _enums.PermissionType.BRANCH,
430
460
  targetId: branchId
431
461
  }
432
462
  });
433
- if (!branchPermission) {
463
+ if (!hasBranchAccess) {
434
464
  throw new _common.UnauthorizedException('No access to this branch');
435
465
  }
436
466
  const branch = await branchRepository.findOne({
@@ -451,6 +481,9 @@ let AuthenticationService = class AuthenticationService {
451
481
  const payload = this.jwtService.verify(refreshToken, {
452
482
  secret: this.authConfig.getRefreshTokenSecret()
453
483
  });
484
+ if (await this.isTokenRevoked(payload.id, payload.iat)) {
485
+ throw new _common.UnauthorizedException('Token has been revoked');
486
+ }
454
487
  const userRepository = await this.getUserRepository();
455
488
  const user = await userRepository.findOne({
456
489
  where: {
@@ -460,7 +493,7 @@ let AuthenticationService = class AuthenticationService {
460
493
  if (!user || !user.isActive) {
461
494
  throw new _common.UnauthorizedException('Invalid refresh token');
462
495
  }
463
- const accessPayload = {
496
+ return this.generateTokens({
464
497
  id: user.id,
465
498
  email: user.email,
466
499
  type: 'access',
@@ -470,44 +503,9 @@ let AuthenticationService = class AuthenticationService {
470
503
  },
471
504
  ...payload.branchId && {
472
505
  branchId: payload.branchId
473
- },
474
- ...payload.companyLogoId && {
475
- companyLogoId: payload.companyLogoId
476
- },
477
- ...payload.branchLogoId && {
478
- branchLogoId: payload.branchLogoId
479
506
  }
480
- };
481
- const refreshPayload = {
482
- id: user.id,
483
- email: user.email,
484
- type: 'refresh',
485
- profilePictureId: user.profilePictureId ?? undefined,
486
- ...payload.companyId && {
487
- companyId: payload.companyId
488
- },
489
- ...payload.branchId && {
490
- branchId: payload.branchId
491
- },
492
- ...payload.companyLogoId && {
493
- companyLogoId: payload.companyLogoId
494
- },
495
- ...payload.branchLogoId && {
496
- branchLogoId: payload.branchLogoId
497
- }
498
- };
499
- return {
500
- accessToken: this.jwtService.sign(accessPayload, {
501
- secret: this.authConfig.getJwtSecret(),
502
- expiresIn: this.authConfig.getJwtExpiration()
503
- }),
504
- refreshToken: this.jwtService.sign(refreshPayload, {
505
- secret: this.authConfig.getRefreshTokenSecret(),
506
- expiresIn: this.authConfig.getRefreshTokenExpiration()
507
- })
508
- };
507
+ });
509
508
  } catch (error) {
510
- // Log error details for debugging (not exposed to client)
511
509
  _utils.ErrorHandler.logError(this.logger, error, 'refreshToken');
512
510
  throw new _common.UnauthorizedException('Invalid or expired refresh token');
513
511
  }
@@ -519,11 +517,8 @@ let AuthenticationService = class AuthenticationService {
519
517
  id: userId
520
518
  }
521
519
  });
522
- if (!user) {
523
- throw new _common.UnauthorizedException('User not found');
524
- }
525
- const isPasswordValid = await _bcrypt.compare(currentPassword, user.password || '');
526
- if (!isPasswordValid) {
520
+ if (!user) throw new _common.UnauthorizedException('User not found');
521
+ if (!await _bcrypt.compare(currentPassword, user.password || '')) {
527
522
  throw new _common.UnauthorizedException('Current password is incorrect');
528
523
  }
529
524
  user.password = await _bcrypt.hash(newPassword, _config.BCRYPT_SALT_ROUNDS);
@@ -531,8 +526,9 @@ let AuthenticationService = class AuthenticationService {
531
526
  }
532
527
  /** Logout - Revokes all tokens. Stores revocation timestamp in cache. */ async logout(userId) {
533
528
  if (!this.cacheManager) {
534
- // Cache not available - graceful degradation (client-side logout only)
535
- 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.');
536
532
  }
537
533
  const revokedAt = Math.floor(Date.now() / 1000); // Unix timestamp in seconds
538
534
  const cacheKey = `${_config.TOKEN_REVOKED_PREFIX}:${userId}`;
@@ -540,6 +536,7 @@ let AuthenticationService = class AuthenticationService {
540
536
  const refreshTokenExpiry = this.authConfig.getRefreshTokenExpiration();
541
537
  const ttlMs = this.parseExpiryToMs(refreshTokenExpiry);
542
538
  await this.cacheManager.set(cacheKey, revokedAt, ttlMs);
539
+ this.logger.log(`User ${userId} logged out - all tokens revoked`);
543
540
  }
544
541
  /** Check if a token is revoked (issued before user's revocation timestamp) */ async isTokenRevoked(userId, tokenIssuedAt) {
545
542
  if (!this.cacheManager || !tokenIssuedAt) {
@@ -657,53 +654,28 @@ let AuthenticationService = class AuthenticationService {
657
654
  id: userId
658
655
  }
659
656
  });
660
- if (!user) {
661
- throw new _common.UnauthorizedException('User not found');
662
- }
663
- // Build response (no tokens)
657
+ if (!user) throw new _common.UnauthorizedException('User not found');
664
658
  const response = {
665
- user: {
666
- id: user.id,
667
- email: user.email,
668
- name: user.name ?? undefined,
669
- profilePictureId: user.profilePictureId ?? undefined
670
- }
659
+ user: this.toUserInfo(user)
671
660
  };
672
- // Add company/branch if available (when company feature is enabled)
673
- if (this.authConfig.isCompanyFeatureEnabled()) {
674
- // When company feature is enabled, companyId MUST be present
661
+ if (this.isCompanyFeatureEnabled()) {
675
662
  if (!companyId) {
676
- 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.');
677
664
  }
678
665
  const company = await this.getCompanyById(companyId);
679
- if (!company) {
680
- throw new _common.UnauthorizedException(`Company with ID ${companyId} not found. Please select a valid company.`);
681
- }
682
- response.company = {
683
- id: company.id,
684
- name: company.name,
685
- slug: company.slug,
686
- logoId: company.logoId ?? null
687
- };
688
- // 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);
689
668
  if (branchId) {
690
669
  const branch = await this.getBranchById(branchId);
691
- if (!branch) {
692
- throw new _common.UnauthorizedException(`Branch with ID ${branchId} not found. Please select a valid branch.`);
693
- }
694
- response.branch = {
695
- id: branch.id,
696
- name: branch.name,
697
- slug: branch.slug,
698
- logoId: branch.logoId ?? null
699
- };
670
+ if (!branch) throw new _common.UnauthorizedException('Branch not found.');
671
+ response.branch = this.toBranchInfo(branch);
700
672
  }
701
673
  }
702
674
  return response;
703
675
  }
704
676
  // Helper Methods
705
677
  ensureCompanyFeatureEnabled() {
706
- if (!this.authConfig.isCompanyFeatureEnabled()) {
678
+ if (!this.isCompanyFeatureEnabled()) {
707
679
  throw new _common.BadRequestException('Company feature is not enabled');
708
680
  }
709
681
  }
@@ -714,11 +686,7 @@ let AuthenticationService = class AuthenticationService {
714
686
  email
715
687
  }
716
688
  });
717
- if (!user) {
718
- throw new _common.UnauthorizedException('Invalid credentials');
719
- }
720
- const isPasswordValid = await _bcrypt.compare(password, user.password || '');
721
- if (!isPasswordValid) {
689
+ if (!user || !await _bcrypt.compare(password, user.password || '')) {
722
690
  throw new _common.UnauthorizedException('Invalid credentials');
723
691
  }
724
692
  user.lastLoginAt = new Date();
@@ -726,82 +694,77 @@ let AuthenticationService = class AuthenticationService {
726
694
  return user;
727
695
  }
728
696
  // Response Generators
729
- generateLoginResponse(user, companyFeatureEnabled = false) {
730
- const payload = {
697
+ /** Create user info object */ toUserInfo(user) {
698
+ return {
731
699
  id: user.id,
732
700
  email: user.email,
733
- 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
734
720
  };
735
- 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
+ }, {
736
727
  secret: this.authConfig.getJwtSecret(),
737
728
  expiresIn: this.authConfig.getJwtExpiration()
738
729
  });
739
- const refreshPayload = {
740
- id: user.id,
741
- email: user.email,
730
+ const refreshToken = this.jwtService.sign({
731
+ ...payload,
742
732
  type: 'refresh'
743
- };
744
- const refreshToken = this.jwtService.sign(refreshPayload, {
733
+ }, {
745
734
  secret: this.authConfig.getRefreshTokenSecret(),
746
735
  expiresIn: this.authConfig.getRefreshTokenExpiration()
747
736
  });
748
737
  return {
749
738
  accessToken,
750
- refreshToken,
751
- user: {
752
- id: user.id,
753
- email: user.email,
754
- name: user.name ?? undefined,
755
- profilePictureId: user.profilePictureId ?? null
756
- },
757
- companyFeatureEnabled
739
+ refreshToken
758
740
  };
759
741
  }
760
- generateLoginResponseWithCompany(user, company, branch) {
761
- const payload = {
742
+ generateLoginResponse(user) {
743
+ const { accessToken, refreshToken } = this.generateTokens({
762
744
  id: user.id,
763
745
  email: user.email,
764
- type: 'access',
765
- companyId: company.id,
766
- branchId: branch?.id
767
- };
768
- const accessToken = this.jwtService.sign(payload, {
769
- secret: this.authConfig.getJwtSecret(),
770
- expiresIn: this.authConfig.getJwtExpiration()
746
+ type: 'access'
771
747
  });
772
- 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({
773
756
  id: user.id,
774
757
  email: user.email,
775
- type: 'refresh',
758
+ type: 'access',
776
759
  companyId: company.id,
777
760
  branchId: branch?.id
778
- };
779
- const refreshToken = this.jwtService.sign(refreshPayload, {
780
- secret: this.authConfig.getRefreshTokenSecret(),
781
- expiresIn: this.authConfig.getRefreshTokenExpiration()
782
761
  });
783
762
  return {
784
763
  accessToken,
785
764
  refreshToken,
786
- user: {
787
- id: user.id,
788
- email: user.email,
789
- name: user.name ?? undefined,
790
- profilePictureId: user.profilePictureId ?? null
791
- },
792
- company: {
793
- id: company.id,
794
- name: company.name,
795
- slug: company.slug,
796
- logoId: company.logoId ?? null
797
- },
798
- branch: branch ? {
799
- id: branch.id,
800
- name: branch.name,
801
- slug: branch.slug,
802
- logoId: branch.logoId ?? null
803
- } : undefined,
804
- companyFeatureEnabled: true
765
+ user: this.toUserInfo(user),
766
+ company: this.toCompanyInfo(company),
767
+ branch: branch ? this.toBranchInfo(branch) : undefined
805
768
  };
806
769
  }
807
770
  async generateCompanySelectionResponse(user, companies, branches) {
@@ -809,55 +772,49 @@ let AuthenticationService = class AuthenticationService {
809
772
  throw new _common.BadRequestException('Session service not available');
810
773
  }
811
774
  const sessionId = await this.sessionService.createSession(user.id);
812
- // Build companies with their branches
813
775
  const companiesWithBranches = companies.map((company)=>({
814
- id: company.id,
815
- name: company.name,
816
- slug: company.slug,
776
+ ...this.toCompanyInfo(company),
817
777
  isActive: company.isActive,
818
- logoId: company.logoId ?? null,
819
778
  branches: branches.filter((b)=>b.companyId === company.id).map((b)=>({
820
- id: b.id,
821
- name: b.name,
822
- slug: b.slug,
823
- isActive: b.isActive,
824
- logoId: b.logoId ?? null
779
+ ...this.toBranchInfo(b),
780
+ isActive: b.isActive
825
781
  }))
826
782
  }));
827
783
  return {
828
784
  requiresSelection: true,
829
785
  sessionId,
830
786
  expiresAt: new Date(Date.now() + 5 * 60 * 1000),
831
- user: {
832
- id: user.id,
833
- email: user.email,
834
- name: user.name ?? undefined,
835
- profilePictureId: user.profilePictureId ?? null
836
- },
787
+ user: this.toUserInfo(user),
837
788
  companies: companiesWithBranches
838
789
  };
839
790
  }
791
+ generateRegistrationResponse(user) {
792
+ return {
793
+ ...this.generateLoginResponse(user),
794
+ requiresEmailVerification: false
795
+ };
796
+ }
797
+ generateRegistrationResponseWithCompany(user, company, branch) {
798
+ return {
799
+ ...this.generateLoginResponseWithCompany(user, company, branch),
800
+ requiresEmailVerification: false
801
+ };
802
+ }
840
803
  // NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
841
- constructor(jwtService, authConfig, cacheManager, dataSourceProvider, sessionService, injectedUserRepository, injectedPermissionRepository, injectedCompanyRepository, injectedBranchRepository){
804
+ constructor(jwtService, authConfig, cacheManager, dataSourceProvider, sessionService, authEmailService){
842
805
  _define_property(this, "jwtService", void 0);
843
806
  _define_property(this, "authConfig", void 0);
844
807
  _define_property(this, "cacheManager", void 0);
845
808
  _define_property(this, "dataSourceProvider", void 0);
846
809
  _define_property(this, "sessionService", void 0);
847
- _define_property(this, "injectedUserRepository", void 0);
848
- _define_property(this, "injectedPermissionRepository", void 0);
849
- _define_property(this, "injectedCompanyRepository", void 0);
850
- _define_property(this, "injectedBranchRepository", void 0);
810
+ _define_property(this, "authEmailService", void 0);
851
811
  _define_property(this, "logger", void 0);
852
812
  this.jwtService = jwtService;
853
813
  this.authConfig = authConfig;
854
814
  this.cacheManager = cacheManager;
855
815
  this.dataSourceProvider = dataSourceProvider;
856
816
  this.sessionService = sessionService;
857
- this.injectedUserRepository = injectedUserRepository;
858
- this.injectedPermissionRepository = injectedPermissionRepository;
859
- this.injectedCompanyRepository = injectedCompanyRepository;
860
- this.injectedBranchRepository = injectedBranchRepository;
817
+ this.authEmailService = authEmailService;
861
818
  this.logger = new _common.Logger(AuthenticationService.name);
862
819
  }
863
820
  };
@@ -874,13 +831,7 @@ AuthenticationService = _ts_decorate([
874
831
  _ts_param(4, (0, _common.Optional)()),
875
832
  _ts_param(4, (0, _common.Inject)(_companyselectionsessionservice.CompanySelectionSessionService)),
876
833
  _ts_param(5, (0, _common.Optional)()),
877
- _ts_param(5, (0, _typeorm.InjectRepository)(_userentity.User)),
878
- _ts_param(6, (0, _common.Optional)()),
879
- _ts_param(6, (0, _typeorm.InjectRepository)(_usercompanypermissionentity.UserCompanyPermission)),
880
- _ts_param(7, (0, _common.Optional)()),
881
- _ts_param(7, (0, _typeorm.InjectRepository)(_companyentity.Company)),
882
- _ts_param(8, (0, _common.Optional)()),
883
- _ts_param(8, (0, _typeorm.InjectRepository)(_companybranchentity.CompanyBranch)),
834
+ _ts_param(5, (0, _common.Inject)(_authemailservice.AuthEmailService)),
884
835
  _ts_metadata("design:type", Function),
885
836
  _ts_metadata("design:paramtypes", [
886
837
  typeof _jwt.JwtService === "undefined" ? Object : _jwt.JwtService,
@@ -888,9 +839,6 @@ AuthenticationService = _ts_decorate([
888
839
  typeof _classes.HybridCache === "undefined" ? Object : _classes.HybridCache,
889
840
  typeof _authdatasourceprovider.AuthDataSourceProvider === "undefined" ? Object : _authdatasourceprovider.AuthDataSourceProvider,
890
841
  typeof _companyselectionsessionservice.CompanySelectionSessionService === "undefined" ? Object : _companyselectionsessionservice.CompanySelectionSessionService,
891
- typeof _typeorm1.Repository === "undefined" ? Object : _typeorm1.Repository,
892
- typeof _typeorm1.Repository === "undefined" ? Object : _typeorm1.Repository,
893
- typeof _typeorm1.Repository === "undefined" ? Object : _typeorm1.Repository,
894
- typeof _typeorm1.Repository === "undefined" ? Object : _typeorm1.Repository
842
+ typeof _authemailservice.AuthEmailService === "undefined" ? Object : _authemailservice.AuthEmailService
895
843
  ])
896
844
  ], AuthenticationService);