@flusys/nestjs-auth 1.1.0-beta → 2.0.0-rc.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
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "BranchService", {
10
10
  });
11
11
  const _classes = require("@flusys/nestjs-shared/classes");
12
12
  const _modules = require("@flusys/nestjs-shared/modules");
13
+ const _utils = require("@flusys/nestjs-shared/utils");
13
14
  const _common = require("@nestjs/common");
14
15
  const _companybranchentity = require("../entities/company-branch.entity");
15
16
  const _authconfigservice = require("./auth-config.service");
@@ -76,10 +77,14 @@ let BranchService = class BranchService extends _classes.RequestScopedApiService
76
77
  }
77
78
  async getExtraManipulateQuery(query, filterDto, user) {
78
79
  const result = await super.getExtraManipulateQuery(query, filterDto, user);
79
- if (this.authConfig.isCompanyFeatureEnabled() && user?.companyId) {
80
- query.andWhere('company_branch.company_id = :companyId', {
81
- companyId: user.companyId
82
- });
80
+ // Only apply company filter if companyId is NOT explicitly provided in filter
81
+ // This prevents dual filtering when user selects a specific company
82
+ if (!filterDto?.filter?.companyId) {
83
+ (0, _utils.applyCompanyFilter)(query, {
84
+ isCompanyFeatureEnabled: this.authConfig.isCompanyFeatureEnabled(),
85
+ entityAlias: 'company_branch',
86
+ columnName: 'company_id'
87
+ }, user);
83
88
  }
84
89
  return result;
85
90
  }
@@ -43,7 +43,7 @@ const SESSION_TTL_MS = 5 * 60 * 1000; // 5 minutes
43
43
  let CompanySelectionSessionService = class CompanySelectionSessionService {
44
44
  /**
45
45
  * Create a new session for company/branch selection
46
- */ async createSession(userId, companyId) {
46
+ */ async createSession(userId) {
47
47
  const sessionId = (0, _crypto.randomUUID)();
48
48
  const now = new Date();
49
49
  const expiresAt = new Date(now.getTime() + SESSION_TTL_MS);
@@ -39,122 +39,92 @@ function _ts_param(paramIndex, decorator) {
39
39
  };
40
40
  }
41
41
  let UserPermissionService = class UserPermissionService {
42
- /** Get repository lazily - initialized on first access */ async getRepository() {
42
+ async getRepository() {
43
43
  if (!this._repository) {
44
44
  this._repository = await this.dataSourceProvider.getRepository(_usercompanypermissionentity.UserCompanyPermission);
45
45
  }
46
46
  return this._repository;
47
47
  }
48
- // Company Permissions
49
- /** Find all companies a user has access to */ async findUserCompanies(userId) {
48
+ // Generic Permission Operations
49
+ /** Find all permissions of a specific type for a user */ async findUserPermissions(userId, type) {
50
50
  const repository = await this.getRepository();
51
51
  return repository.find({
52
52
  where: {
53
53
  userId,
54
- permissionType: _usercompanypermissionentity.PermissionType.COMPANY
54
+ permissionType: type
55
55
  },
56
56
  relations: [
57
- 'company'
57
+ type === _usercompanypermissionentity.PermissionType.COMPANY ? 'company' : 'branch'
58
58
  ],
59
59
  order: {
60
60
  createdAt: 'DESC'
61
61
  }
62
62
  });
63
63
  }
64
- /** Assign user to a company */ async assignUserToCompany(dto) {
64
+ /** Assign permission to user */ async assignPermission(userId, targetId, type, metadata) {
65
65
  const repository = await this.getRepository();
66
- // Check if already exists
66
+ const typeName = type === _usercompanypermissionentity.PermissionType.COMPANY ? 'company' : 'branch';
67
67
  const existing = await repository.findOne({
68
68
  where: {
69
- userId: dto.userId,
70
- targetId: dto.companyId,
71
- permissionType: _usercompanypermissionentity.PermissionType.COMPANY
69
+ userId,
70
+ targetId,
71
+ permissionType: type
72
72
  }
73
73
  });
74
74
  if (existing) {
75
- throw new _common.BadRequestException('User already assigned to this company');
75
+ throw new _common.ConflictException(`User already assigned to this ${typeName}`);
76
76
  }
77
- const permission = repository.create({
78
- userId: dto.userId,
79
- permissionType: _usercompanypermissionentity.PermissionType.COMPANY,
80
- targetId: dto.companyId,
81
- metadata: dto.metadata
82
- });
83
- await repository.save(permission);
84
- }
85
- /** Revoke user access to a company */ async revokeCompanyAccess(userId, companyId) {
86
- const repository = await this.getRepository();
87
- const permission = await repository.findOne({
88
- where: {
77
+ try {
78
+ await repository.save(repository.create({
89
79
  userId,
90
- targetId: companyId,
91
- permissionType: _usercompanypermissionentity.PermissionType.COMPANY
80
+ permissionType: type,
81
+ targetId,
82
+ metadata
83
+ }));
84
+ } catch (error) {
85
+ if (error?.code === 'ER_DUP_ENTRY' || error?.message?.includes('Duplicate entry')) {
86
+ throw new _common.ConflictException(`User already assigned to this ${typeName}. Please refresh and try again.`);
92
87
  }
93
- });
94
- if (!permission) {
95
- throw new _common.NotFoundException('Company permission not found');
88
+ throw error;
96
89
  }
97
- await repository.remove(permission);
98
90
  }
99
- // Branch Permissions
100
- /** Find all branches a user has access to */ async findUserBranches(userId) {
101
- const repository = await this.getRepository();
102
- return repository.find({
103
- where: {
104
- userId,
105
- permissionType: _usercompanypermissionentity.PermissionType.BRANCH
106
- },
107
- relations: [
108
- 'branch'
109
- ],
110
- order: {
111
- createdAt: 'DESC'
112
- }
113
- });
114
- }
115
- /** Assign user to a branch */ async assignUserToBranch(dto) {
116
- const repository = await this.getRepository();
117
- // Check if already exists
118
- const existing = await repository.findOne({
119
- where: {
120
- userId: dto.userId,
121
- targetId: dto.branchId,
122
- permissionType: _usercompanypermissionentity.PermissionType.BRANCH
123
- }
124
- });
125
- if (existing) {
126
- throw new _common.BadRequestException('User already assigned to this branch');
127
- }
128
- const permission = repository.create({
129
- userId: dto.userId,
130
- permissionType: _usercompanypermissionentity.PermissionType.BRANCH,
131
- targetId: dto.branchId,
132
- metadata: dto.metadata
133
- });
134
- await repository.save(permission);
135
- }
136
- /** Revoke user access to a branch */ async revokeBranchAccess(userId, branchId) {
91
+ /** Revoke permission from user */ async revokePermission(userId, targetId, type) {
137
92
  const repository = await this.getRepository();
93
+ const typeName = type === _usercompanypermissionentity.PermissionType.COMPANY ? 'company' : 'branch';
138
94
  const permission = await repository.findOne({
139
95
  where: {
140
96
  userId,
141
- targetId: branchId,
142
- permissionType: _usercompanypermissionentity.PermissionType.BRANCH
97
+ targetId,
98
+ permissionType: type
143
99
  }
144
100
  });
145
101
  if (!permission) {
146
- throw new _common.NotFoundException('Branch permission not found');
102
+ throw new _common.NotFoundException(`${typeName.charAt(0).toUpperCase() + typeName.slice(1)} permission not found`);
147
103
  }
148
104
  await repository.remove(permission);
149
105
  }
150
- // NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
106
+ /** Find user branch permissions with optional company filter */ async findUserBranches(userId, companyId) {
107
+ const repository = await this.getRepository();
108
+ const queryBuilder = repository.createQueryBuilder('permission').leftJoinAndSelect('permission.branch', 'branch').where('permission.userId = :userId', {
109
+ userId
110
+ }).andWhere('permission.permissionType = :type', {
111
+ type: _usercompanypermissionentity.PermissionType.BRANCH
112
+ }).orderBy('permission.createdAt', 'DESC');
113
+ if (companyId) {
114
+ queryBuilder.andWhere('branch.companyId = :companyId', {
115
+ companyId
116
+ });
117
+ }
118
+ return queryBuilder.getMany();
119
+ }
151
120
  constructor(dataSourceProvider){
152
121
  _define_property(this, "dataSourceProvider", void 0);
153
- _define_property(this, "logger", void 0);
154
122
  _define_property(this, "_repository", void 0);
123
+ // Convenience Methods
124
+ _define_property(this, "findUserCompanies", void 0);
155
125
  this.dataSourceProvider = dataSourceProvider;
156
- this.logger = new _common.Logger(UserPermissionService.name);
157
126
  this._repository = null;
127
+ this.findUserCompanies = (userId)=>this.findUserPermissions(userId, _usercompanypermissionentity.PermissionType.COMPANY);
158
128
  }
159
129
  };
160
130
  UserPermissionService = _ts_decorate([
@@ -110,6 +110,24 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
110
110
  }
111
111
  }
112
112
  // Custom Methods
113
+ /**
114
+ * Validate profile ownership, ensure repository initialized, and fetch user.
115
+ * Throws ForbiddenException if not owner, NotFoundException if user not found.
116
+ */ async getUserForProfile(userId, user) {
117
+ if (userId !== user.id) {
118
+ throw new _common.ForbiddenException('Cannot access another user\'s profile');
119
+ }
120
+ await this.ensureRepositoryInitialized();
121
+ const userEntity = await this.repository.findOne({
122
+ where: {
123
+ id: userId
124
+ }
125
+ });
126
+ if (!userEntity) {
127
+ throw new _common.NotFoundException('User not found');
128
+ }
129
+ return userEntity;
130
+ }
113
131
  /** Verify email */ async verifyEmail(id) {
114
132
  // Ensure repository is initialized for custom method
115
133
  await this.ensureRepositoryInitialized();
@@ -178,26 +196,51 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
178
196
  return entity;
179
197
  }
180
198
  /** Convert entity to response DTO (removes password). In raw mode with company feature, isActive comes from permission table. */ convertEntityToResponseDto(entity, isRaw) {
199
+ if (isRaw) {
200
+ return this.convertRawToResponseDto(entity);
201
+ }
202
+ return this.convertEntityToDto(entity);
203
+ }
204
+ /** Convert raw query result to response DTO */ convertRawToResponseDto(raw) {
181
205
  // When raw mode is used with company feature, isActive comes from computed field
182
- // Raw mode uses snake_case column names (due to SnakeNamingStrategy)
183
- const isActive = isRaw && entity.user_companyIsActive !== undefined ? Boolean(entity.user_companyIsActive) : isRaw ? entity.user_is_active : entity.isActive;
206
+ const isActive = raw.user_companyIsActive !== undefined ? Boolean(raw.user_companyIsActive) : raw.user_is_active;
184
207
  return {
185
- id: isRaw ? entity.user_id : entity.id,
186
- name: isRaw ? entity.user_name : entity.name,
187
- phone: isRaw ? entity.user_phone : entity.phone,
188
- email: isRaw ? entity.user_email : entity.email,
189
- profilePictureId: isRaw ? entity.user_profile_picture_id : entity.profilePictureId,
208
+ id: raw.user_id,
209
+ name: raw.user_name,
210
+ phone: raw.user_phone,
211
+ email: raw.user_email,
212
+ profilePictureId: raw.user_profile_picture_id,
190
213
  isActive,
191
- emailVerified: isRaw ? entity.user_email_verified : entity.emailVerified,
192
- phoneVerified: isRaw ? entity.user_phone_verified : entity.phoneVerified,
193
- lastLoginAt: isRaw ? entity.user_last_login_at : entity.lastLoginAt,
194
- additionalFields: isRaw ? entity.user_additional_fields : entity.additionalFields,
195
- createdAt: isRaw ? entity.user_created_at : entity.createdAt,
196
- updatedAt: isRaw ? entity.user_updated_at : entity.updatedAt,
197
- deletedAt: isRaw ? entity.user_deleted_at : entity.deletedAt,
198
- createdById: isRaw ? entity.user_created_by_id : entity.createdById,
199
- updatedById: isRaw ? entity.user_updated_by_id : entity.updatedById,
200
- deletedById: isRaw ? entity.user_deleted_by_id : entity.deletedById
214
+ emailVerified: raw.user_email_verified,
215
+ phoneVerified: raw.user_phone_verified,
216
+ lastLoginAt: raw.user_last_login_at,
217
+ additionalFields: raw.user_additional_fields,
218
+ createdAt: raw.user_created_at,
219
+ updatedAt: raw.user_updated_at,
220
+ deletedAt: raw.user_deleted_at,
221
+ createdById: raw.user_created_by_id,
222
+ updatedById: raw.user_updated_by_id,
223
+ deletedById: raw.user_deleted_by_id
224
+ };
225
+ }
226
+ /** Convert entity to response DTO */ convertEntityToDto(entity) {
227
+ return {
228
+ id: entity.id,
229
+ name: entity.name,
230
+ phone: entity.phone,
231
+ email: entity.email,
232
+ profilePictureId: entity.profilePictureId,
233
+ isActive: entity.isActive,
234
+ emailVerified: entity.emailVerified,
235
+ phoneVerified: entity.phoneVerified,
236
+ lastLoginAt: entity.lastLoginAt,
237
+ additionalFields: entity.additionalFields,
238
+ createdAt: entity.createdAt,
239
+ updatedAt: entity.updatedAt,
240
+ deletedAt: entity.deletedAt,
241
+ createdById: entity.createdById,
242
+ updatedById: entity.updatedById,
243
+ deletedById: entity.deletedById
201
244
  };
202
245
  }
203
246
  /** Convert entity list to response DTO list */ convertEntityListToResponseListDto(entities, isRaw) {
@@ -342,15 +385,7 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
342
385
  * Get user profile with extras from enricher
343
386
  * Returns base user data plus any extra data provided by enricher (roles, permissions, etc.)
344
387
  */ async getProfileWithExtras(userId, user) {
345
- await this.ensureRepositoryInitialized();
346
- const userEntity = await this.repository.findOne({
347
- where: {
348
- id: userId
349
- }
350
- });
351
- if (!userEntity) {
352
- throw new _common.NotFoundException('User not found');
353
- }
388
+ const userEntity = await this.getUserForProfile(userId, user);
354
389
  const userData = this.convertEntityToResponseDto(userEntity, false);
355
390
  // Get extras from enricher if available
356
391
  let extras;
@@ -385,16 +420,7 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
385
420
  /**
386
421
  * Get specific profile section data
387
422
  */ async getProfileSectionData(userId, sectionId, user) {
388
- await this.ensureRepositoryInitialized();
389
- // Verify user exists
390
- const userEntity = await this.repository.findOne({
391
- where: {
392
- id: userId
393
- }
394
- });
395
- if (!userEntity) {
396
- throw new _common.NotFoundException('User not found');
397
- }
423
+ await this.getUserForProfile(userId, user);
398
424
  if (this.userEnricher?.getProfileSectionData) {
399
425
  return this.userEnricher.getProfileSectionData(userId, sectionId, user);
400
426
  }
@@ -403,19 +429,10 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
403
429
  /**
404
430
  * Update specific profile section
405
431
  */ async updateProfileSection(userId, sectionId, data, user) {
406
- await this.ensureRepositoryInitialized();
432
+ const userEntity = await this.getUserForProfile(userId, user);
407
433
  if (!this.dataSource) {
408
434
  throw new _common.BadRequestException('DataSource not available');
409
435
  }
410
- // Verify user exists
411
- const userEntity = await this.repository.findOne({
412
- where: {
413
- id: userId
414
- }
415
- });
416
- if (!userEntity) {
417
- throw new _common.NotFoundException('User not found');
418
- }
419
436
  if (!this.userEnricher?.updateProfileSection) {
420
437
  throw new _common.BadRequestException('Profile sections not supported');
421
438
  }
@@ -442,16 +459,7 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
442
459
  /**
443
460
  * Get profile completion percentage
444
461
  */ async getProfileCompletion(userId, user) {
445
- await this.ensureRepositoryInitialized();
446
- // Verify user exists
447
- const userEntity = await this.repository.findOne({
448
- where: {
449
- id: userId
450
- }
451
- });
452
- if (!userEntity) {
453
- throw new _common.NotFoundException('User not found');
454
- }
462
+ await this.getUserForProfile(userId, user);
455
463
  if (this.userEnricher?.calculateProfileCompletion) {
456
464
  return this.userEnricher.calculateProfileCompletion(userId, user);
457
465
  }
@@ -482,16 +490,22 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
482
490
  }
483
491
  // Handle password update
484
492
  if (dto.password) {
485
- // If updating own profile, require old password
486
- if (!dto.id || dto.id === user.id) {
487
- // This would need oldPassword in DTO - for now, skip if not provided
488
- // In real implementation, add oldPassword to UpdateUserDto
493
+ // If updating own profile, require old password verification
494
+ const isOwnProfile = !dto.id || dto.id === user.id;
495
+ if (isOwnProfile) {
496
+ if (!dto.oldPassword) {
497
+ throw new _common.BadRequestException('Current password is required to change your password');
498
+ }
499
+ const isPasswordValid = await _bcrypt.compare(dto.oldPassword, findUser.password);
500
+ if (!isPasswordValid) {
501
+ throw new _common.BadRequestException('Current password is incorrect');
502
+ }
489
503
  }
490
- const salt = await _bcrypt.genSalt();
491
- findUser.password = await _bcrypt.hash(dto.password, salt);
504
+ findUser.password = await _bcrypt.hash(dto.password, _config.BCRYPT_SALT_ROUNDS);
492
505
  }
493
- // Merge other fields (excluding password and extras)
494
- const { password, extras, ...restDto } = dto;
506
+ // Merge other fields (excluding password, oldPassword, extras, and sensitive flags)
507
+ // Users cannot self-modify emailVerified, phoneVerified, or isActive via profile update
508
+ const { password, oldPassword, extras, emailVerified, phoneVerified, isActive, ...restDto } = dto;
495
509
  Object.assign(findUser, restDto);
496
510
  await queryRunner.manager.save(this.repository.target, findUser);
497
511
  // Handle extras update via enricher
@@ -12,10 +12,10 @@ const _common = require("@nestjs/common");
12
12
  const _core = require("@nestjs/core");
13
13
  const _passport = require("@nestjs/passport");
14
14
  const _passportjwt = require("passport-jwt");
15
+ const _userentity = require("../entities/user.entity");
15
16
  const _authconfigservice = require("../services/auth-config.service");
16
17
  const _authdatasourceprovider = require("../services/auth-datasource.provider");
17
18
  const _authenticationservice = require("../services/authentication.service");
18
- const _userentity = require("../entities/user.entity");
19
19
  function _define_property(obj, key, value) {
20
20
  if (key in obj) {
21
21
  Object.defineProperty(obj, key, {
@@ -38,71 +38,71 @@ function _ts_decorate(decorators, target, key, desc) {
38
38
  function _ts_metadata(k, v) {
39
39
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
40
40
  }
41
- /**
42
- * Type guard to check if payload includes company context
43
- */ function isTokenPayloadWithCompany(payload) {
41
+ function hasCompanyContext(payload) {
44
42
  return 'companyId' in payload && 'branchId' in payload;
45
43
  }
46
44
  let JwtStrategy = class JwtStrategy extends (0, _passport.PassportStrategy)(_passportjwt.Strategy, 'jwt') {
47
45
  async validate(_req, payload) {
48
- // Resolve request-scoped services for this request
49
- let userRepository = null;
50
- let authService = null;
46
+ const userRepo = await this.resolveUserRepository();
47
+ await this.checkTokenRevocation(payload);
48
+ const user = await userRepo.findOne({
49
+ where: {
50
+ id: payload.id,
51
+ isActive: true
52
+ }
53
+ });
54
+ if (!user) {
55
+ throw new _common.UnauthorizedException('User not found or inactive');
56
+ }
57
+ return this.buildUserInfo(payload);
58
+ }
59
+ async resolveUserRepository() {
51
60
  try {
52
- const dataSourceProvider = await this.moduleRef.resolve(_authdatasourceprovider.AuthDataSourceProvider, undefined, {
61
+ const provider = await this.moduleRef.resolve(_authdatasourceprovider.AuthDataSourceProvider, undefined, {
53
62
  strict: false
54
63
  });
55
- if (dataSourceProvider) {
56
- userRepository = await dataSourceProvider.getRepository(_userentity.User);
64
+ if (provider) {
65
+ return provider.getRepository(_userentity.User);
57
66
  }
58
- // Resolve AuthenticationService for token revocation check
59
- authService = await this.moduleRef.resolve(_authenticationservice.AuthenticationService, undefined, {
60
- strict: false
61
- });
62
- } catch (error) {
67
+ } catch {
63
68
  throw new _common.UnauthorizedException('Unable to access user data');
64
69
  }
65
- if (!userRepository) {
66
- throw new _common.UnauthorizedException('User repository not available');
67
- }
68
- // Check if token is revoked (logged out)
69
- if (authService && payload.iat) {
70
- const isRevoked = await authService.isTokenRevoked(payload.id, payload.iat);
71
- if (isRevoked) {
70
+ throw new _common.UnauthorizedException('User repository not available');
71
+ }
72
+ async checkTokenRevocation(payload) {
73
+ if (!payload.iat) return;
74
+ try {
75
+ const authService = await this.moduleRef.resolve(_authenticationservice.AuthenticationService, undefined, {
76
+ strict: false
77
+ });
78
+ if (authService && await authService.isTokenRevoked(payload.id, payload.iat)) {
72
79
  throw new _common.UnauthorizedException('Token has been revoked');
73
80
  }
81
+ } catch (err) {
82
+ if (err instanceof _common.UnauthorizedException) throw err;
74
83
  }
75
- // Find user and check if active
76
- const user = await userRepository.findOne({
77
- where: {
78
- id: payload.id,
79
- isActive: true
80
- }
81
- });
82
- if (!user) {
83
- throw new _common.UnauthorizedException('User not found or inactive');
84
- }
85
- const result = {
84
+ }
85
+ buildUserInfo(payload) {
86
+ const base = {
86
87
  id: payload.id,
87
88
  email: payload.email
88
89
  };
89
- // Company context - use type guard for type safety
90
- if (isTokenPayloadWithCompany(payload)) {
91
- result.companyId = payload.companyId;
92
- // Only set branchId if it's not empty string
93
- if (payload.branchId) {
94
- result.branchId = payload.branchId;
95
- }
90
+ if (!hasCompanyContext(payload)) {
91
+ return base;
96
92
  }
97
- return result;
93
+ return {
94
+ ...base,
95
+ companyId: payload.companyId,
96
+ ...payload.branchId && {
97
+ branchId: payload.branchId
98
+ }
99
+ };
98
100
  }
99
101
  constructor(authConfig, moduleRef){
100
102
  super({
101
103
  jwtFromRequest: _passportjwt.ExtractJwt.fromAuthHeaderAsBearerToken(),
102
104
  ignoreExpiration: false,
103
- // Use secretOrKeyProvider instead of secretOrKey to defer secret lookup
104
- // This ensures AuthConfigService is fully initialized when using forRootAsync
105
- secretOrKeyProvider: (_request, _rawJwtToken, done)=>{
105
+ secretOrKeyProvider: (_req, _token, done)=>{
106
106
  done(null, this.authConfig.getJwtSecret());
107
107
  },
108
108
  passReqToCallback: true
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "IsStrongPassword", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return IsStrongPassword;
9
+ }
10
+ });
11
+ const _classvalidator = require("class-validator");
12
+ function IsStrongPassword(options = {}, validationOptions) {
13
+ const { minLength = 8, maxLength = 128, requireUppercase = true, requireLowercase = true, requireNumber = true, requireSpecialChar = true } = options;
14
+ return function(object, propertyName) {
15
+ (0, _classvalidator.registerDecorator)({
16
+ name: 'isStrongPassword',
17
+ target: object.constructor,
18
+ propertyName: propertyName,
19
+ options: validationOptions,
20
+ validator: {
21
+ validate (value) {
22
+ if (typeof value !== 'string') return false;
23
+ const checks = [
24
+ value.length >= minLength,
25
+ value.length <= maxLength,
26
+ !requireUppercase || /[A-Z]/.test(value),
27
+ !requireLowercase || /[a-z]/.test(value),
28
+ !requireNumber || /[0-9]/.test(value),
29
+ !requireSpecialChar || /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?~`]/.test(value)
30
+ ];
31
+ return checks.every(Boolean);
32
+ },
33
+ defaultMessage (args) {
34
+ const requirements = [];
35
+ if (minLength > 0) requirements.push(`at least ${minLength} characters`);
36
+ if (requireUppercase) requirements.push('one uppercase letter');
37
+ if (requireLowercase) requirements.push('one lowercase letter');
38
+ if (requireNumber) requirements.push('one number');
39
+ if (requireSpecialChar) requirements.push('one special character (!@#$%^&*...)');
40
+ return `Password must contain ${requirements.join(', ')}`;
41
+ }
42
+ }
43
+ });
44
+ };
45
+ }
@@ -1,5 +1,7 @@
1
1
  export declare const AUTH_MODULE_OPTIONS = "AUTH_MODULE_OPTIONS";
2
2
  export declare const USER_ENRICHER = "USER_ENRICHER";
3
3
  export declare const BCRYPT_SALT_ROUNDS = 12;
4
+ export declare const DEFAULT_COOKIE_NAME = "fsn_refresh_token";
5
+ export declare const MAX_COOKIE_BYTES = 3500;
4
6
  export declare const SESSION_CACHE_PREFIX = "auth:session";
5
7
  export declare const TOKEN_REVOKED_PREFIX = "auth:revoked";
@@ -1,7 +1,7 @@
1
+ import { MessageResponseDto } from '@flusys/nestjs-shared/dtos';
1
2
  import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
2
3
  import { Request } from 'express';
3
- import { ChangePasswordDto, CompanySelectionResponseDto, ForgotPasswordDto, LoginDto, LoginResponseDto, MeResponseDto, RefreshTokenDto, RegistrationDto, RegistrationResponseDto, ResendVerificationDto, ResetPasswordDto, VerifyEmailDto } from '../dtos/authentication.dto';
4
- import { MessageResponseDto } from '@flusys/nestjs-shared/dtos';
4
+ import { ChangePasswordDto, CompanySelectionResponseDto, ForgotPasswordDto, LoginDto, LoginResponseDto, LoginResponseRequiresEmailVerificationDto, MeResponseDto, RefreshTokenDto, RegistrationDto, RegistrationResponseDto, ResendVerificationDto, ResetPasswordDto, VerifyEmailDto } from '../dtos/authentication.dto';
5
5
  import { AuthConfigService } from '../services/auth-config.service';
6
6
  import { AuthEmailService } from '../services/auth-email.service';
7
7
  import { AuthenticationService } from '../services/authentication.service';
@@ -11,7 +11,9 @@ export declare class AuthenticationController {
11
11
  private readonly authEmailService?;
12
12
  constructor(authService: AuthenticationService, authConfig?: AuthConfigService, authEmailService?: AuthEmailService);
13
13
  private buildAuthUrl;
14
- login(loginDto: LoginDto): Promise<LoginResponseDto | CompanySelectionResponseDto>;
14
+ private get emailService();
15
+ private get isEmailEnabled();
16
+ login(loginDto: LoginDto): Promise<LoginResponseDto | CompanySelectionResponseDto | LoginResponseRequiresEmailVerificationDto>;
15
17
  register(req: Request, registrationDto: RegistrationDto): Promise<RegistrationResponseDto>;
16
18
  refreshToken(req: Request, refreshTokenDto: RefreshTokenDto): Promise<{
17
19
  accessToken: string;
@@ -6,9 +6,13 @@ export declare class CompanySelectionController {
6
6
  select(dto: SelectDto): Promise<LoginResponseDto>;
7
7
  switchCompany(userId: string, dto: SwitchCompanyDto): Promise<LoginResponseDto>;
8
8
  getUserCompanies(userId: string): Promise<{
9
- companies: import("..").Company[];
9
+ success: boolean;
10
+ message: string;
11
+ data: import("..").Company[];
10
12
  }>;
11
13
  getUserBranches(userId: string, companyId: string): Promise<{
12
- branches: import("..").CompanyBranch[];
14
+ success: boolean;
15
+ message: string;
16
+ data: import("..").CompanyBranch[];
13
17
  }>;
14
18
  }
@@ -1,8 +1,8 @@
1
- import { AssignUserCompanyDto, AssignUserBranchDto, UserCompaniesResponseDto, UserBranchesResponseDto } from '../dtos/user-permission.dto';
1
+ import { AssignUserBranchDto, AssignUserCompanyDto, UserBranchesResponseDto, UserCompaniesResponseDto } from '../dtos/user-permission.dto';
2
2
  import { UserPermissionService } from '../services/user-permission.service';
3
3
  export declare class UserPermissionController {
4
- private readonly userPermissionService;
5
- constructor(userPermissionService: UserPermissionService);
4
+ private readonly permissionService;
5
+ constructor(permissionService: UserPermissionService);
6
6
  assignUserCompany(dto: AssignUserCompanyDto): Promise<{
7
7
  success: boolean;
8
8
  message: string;
@@ -12,5 +12,6 @@ export declare class UserPermissionController {
12
12
  success: boolean;
13
13
  message: string;
14
14
  }>;
15
- getUserBranches(userId: string): Promise<UserBranchesResponseDto>;
15
+ getUserBranches(userId: string, companyId?: string): Promise<UserBranchesResponseDto>;
16
+ private processBatchPermissions;
16
17
  }