@flusys/nestjs-auth 1.0.0-beta → 1.0.0-rc

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/README.md +274 -291
  2. package/cjs/config/auth.constants.js +12 -0
  3. package/cjs/controllers/authentication.controller.js +219 -10
  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 +40 -25
  8. package/cjs/controllers/user.controller.js +211 -7
  9. package/cjs/dtos/authentication.dto.js +119 -13
  10. package/cjs/dtos/user-permission.dto.js +28 -0
  11. package/cjs/dtos/user.dto.js +19 -4
  12. package/cjs/entities/index.js +5 -1
  13. package/cjs/entities/pending-registration.entity.js +125 -0
  14. package/cjs/entities/user-company-permission.entity.js +0 -6
  15. package/cjs/entities/user-token.entity.js +129 -0
  16. package/cjs/guards/auth-throttler.guard.js +53 -0
  17. package/cjs/interceptors/clear-token.interceptor.js +3 -6
  18. package/cjs/interceptors/set-token.interceptor.js +38 -31
  19. package/cjs/interfaces/auth-email-provider.interface.js +45 -0
  20. package/cjs/interfaces/index.js +2 -0
  21. package/cjs/interfaces/user-enricher.interface.js +4 -0
  22. package/cjs/modules/auth.module.js +8 -20
  23. package/cjs/services/auth-config.service.js +23 -2
  24. package/cjs/services/auth-datasource.provider.js +5 -3
  25. package/cjs/services/auth-email.service.js +471 -0
  26. package/cjs/services/authentication.service.js +169 -77
  27. package/cjs/services/branch.service.js +6 -5
  28. package/cjs/services/company-selection-session.service.js +1 -1
  29. package/cjs/services/index.js +1 -0
  30. package/cjs/services/user-permission.service.js +0 -2
  31. package/cjs/services/user.service.js +213 -34
  32. package/cjs/validators/password.validator.js +45 -0
  33. package/config/auth.constants.d.ts +3 -0
  34. package/controllers/authentication.controller.d.ts +13 -9
  35. package/controllers/company-selection.controller.d.ts +6 -2
  36. package/controllers/user.controller.d.ts +33 -0
  37. package/dtos/authentication.dto.d.ts +21 -3
  38. package/dtos/company-branch.dto.d.ts +0 -1
  39. package/dtos/company.dto.d.ts +0 -1
  40. package/dtos/user-permission.dto.d.ts +4 -0
  41. package/dtos/user.dto.d.ts +1 -2
  42. package/entities/index.d.ts +7 -3
  43. package/entities/pending-registration.entity.d.ts +17 -0
  44. package/entities/user-company-permission.entity.d.ts +0 -2
  45. package/entities/user-token.entity.d.ts +15 -0
  46. package/fesm/config/auth.constants.js +6 -2
  47. package/fesm/controllers/authentication.controller.js +221 -12
  48. package/fesm/controllers/branch.controller.js +45 -2
  49. package/fesm/controllers/company-selection.controller.js +28 -21
  50. package/fesm/controllers/company.controller.js +45 -2
  51. package/fesm/controllers/user-permission.controller.js +41 -26
  52. package/fesm/controllers/user.controller.js +214 -10
  53. package/fesm/dtos/authentication.dto.js +106 -14
  54. package/fesm/dtos/user-permission.dto.js +28 -0
  55. package/fesm/dtos/user.dto.js +20 -5
  56. package/fesm/entities/index.js +7 -1
  57. package/fesm/entities/pending-registration.entity.js +115 -0
  58. package/fesm/entities/user-company-permission.entity.js +0 -6
  59. package/fesm/entities/user-token.entity.js +113 -0
  60. package/fesm/guards/auth-throttler.guard.js +43 -0
  61. package/fesm/interceptors/clear-token.interceptor.js +2 -5
  62. package/fesm/interceptors/set-token.interceptor.js +37 -30
  63. package/fesm/interfaces/auth-email-provider.interface.js +38 -0
  64. package/fesm/interfaces/authentication.interface.js +1 -1
  65. package/fesm/interfaces/index.js +2 -0
  66. package/fesm/interfaces/user-enricher.interface.js +26 -0
  67. package/fesm/modules/auth.module.js +10 -22
  68. package/fesm/services/auth-config.service.js +24 -3
  69. package/fesm/services/auth-datasource.provider.js +6 -4
  70. package/fesm/services/auth-email.service.js +420 -0
  71. package/fesm/services/authentication.service.js +169 -77
  72. package/fesm/services/branch.service.js +6 -5
  73. package/fesm/services/company-selection-session.service.js +1 -1
  74. package/fesm/services/index.js +1 -0
  75. package/fesm/services/user-permission.service.js +1 -3
  76. package/fesm/services/user.service.js +215 -36
  77. package/fesm/validators/password.validator.js +48 -0
  78. package/guards/auth-throttler.guard.d.ts +7 -0
  79. package/interceptors/set-token.interceptor.d.ts +1 -0
  80. package/interfaces/auth-email-provider.interface.d.ts +6 -0
  81. package/interfaces/auth-module-options.interface.d.ts +4 -1
  82. package/interfaces/authentication.interface.d.ts +14 -0
  83. package/interfaces/index.d.ts +2 -0
  84. package/interfaces/user-enricher.interface.d.ts +64 -0
  85. package/interfaces/user.interface.d.ts +1 -0
  86. package/modules/auth.module.d.ts +0 -1
  87. package/package.json +4 -4
  88. package/services/auth-config.service.d.ts +5 -1
  89. package/services/auth-email.service.d.ts +51 -0
  90. package/services/authentication.service.d.ts +16 -11
  91. package/services/company-selection-session.service.d.ts +1 -1
  92. package/services/index.d.ts +1 -0
  93. package/services/user-permission.service.d.ts +0 -1
  94. package/services/user.service.d.ts +19 -3
  95. package/validators/password.validator.d.ts +10 -0
@@ -17,6 +17,7 @@ const _authconfigservice = require("./auth-config.service");
17
17
  const _authdatasourceprovider = require("./auth-datasource.provider");
18
18
  const _config = require("../config");
19
19
  const _enums = require("../enums");
20
+ const _userenricherinterface = require("../interfaces/user-enricher.interface");
20
21
  const _usercompanypermissionentity = require("../entities/user-company-permission.entity");
21
22
  const _userentity = require("../entities/user.entity");
22
23
  function _define_property(obj, key, value) {
@@ -109,6 +110,14 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
109
110
  }
110
111
  }
111
112
  // Custom Methods
113
+ /**
114
+ * Validate that the requesting user owns the target profile.
115
+ * Users can only access/modify their own profile data.
116
+ */ validateProfileOwnership(targetUserId, requestingUser) {
117
+ if (targetUserId !== requestingUser.id) {
118
+ throw new _common.ForbiddenException('Cannot access another user\'s profile');
119
+ }
120
+ }
112
121
  /** Verify email */ async verifyEmail(id) {
113
122
  // Ensure repository is initialized for custom method
114
123
  await this.ensureRepositoryInitialized();
@@ -170,33 +179,58 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
170
179
  /** Convert DTO to entity (hash password before saving) */ async convertSingleDtoToEntity(dto, user) {
171
180
  // Repository initialization is handled automatically by RequestScopedApiService
172
181
  const entity = await super.convertSingleDtoToEntity(dto, user);
173
- // Hash password if provided
174
- if (dto.password) {
182
+ // Hash password if provided (CreateUserDto has password field)
183
+ if ('password' in dto && dto.password) {
175
184
  entity.password = await _bcrypt.hash(dto.password, _config.BCRYPT_SALT_ROUNDS);
176
185
  }
177
186
  return entity;
178
187
  }
179
188
  /** Convert entity to response DTO (removes password). In raw mode with company feature, isActive comes from permission table. */ convertEntityToResponseDto(entity, isRaw) {
189
+ if (isRaw) {
190
+ return this.convertRawToResponseDto(entity);
191
+ }
192
+ return this.convertEntityToDto(entity);
193
+ }
194
+ /** Convert raw query result to response DTO */ convertRawToResponseDto(raw) {
180
195
  // When raw mode is used with company feature, isActive comes from computed field
181
- // Raw mode uses snake_case column names (due to SnakeNamingStrategy)
182
- const isActive = isRaw && entity.user_companyIsActive !== undefined ? Boolean(entity.user_companyIsActive) : isRaw ? entity.user_is_active : entity.isActive;
196
+ const isActive = raw.user_companyIsActive !== undefined ? Boolean(raw.user_companyIsActive) : raw.user_is_active;
183
197
  return {
184
- id: isRaw ? entity.user_id : entity.id,
185
- name: isRaw ? entity.user_name : entity.name,
186
- phone: isRaw ? entity.user_phone : entity.phone,
187
- email: isRaw ? entity.user_email : entity.email,
188
- profilePictureId: isRaw ? entity.user_profile_picture_id : entity.profilePictureId,
198
+ id: raw.user_id,
199
+ name: raw.user_name,
200
+ phone: raw.user_phone,
201
+ email: raw.user_email,
202
+ profilePictureId: raw.user_profile_picture_id,
189
203
  isActive,
190
- emailVerified: isRaw ? entity.user_email_verified : entity.emailVerified,
191
- phoneVerified: isRaw ? entity.user_phone_verified : entity.phoneVerified,
192
- lastLoginAt: isRaw ? entity.user_last_login_at : entity.lastLoginAt,
193
- additionalFields: isRaw ? entity.user_additional_fields : entity.additionalFields,
194
- createdAt: isRaw ? entity.user_created_at : entity.createdAt,
195
- updatedAt: isRaw ? entity.user_updated_at : entity.updatedAt,
196
- deletedAt: isRaw ? entity.user_deleted_at : entity.deletedAt,
197
- createdById: isRaw ? entity.user_created_by_id : entity.createdById,
198
- updatedById: isRaw ? entity.user_updated_by_id : entity.updatedById,
199
- deletedById: isRaw ? entity.user_deleted_by_id : entity.deletedById
204
+ emailVerified: raw.user_email_verified,
205
+ phoneVerified: raw.user_phone_verified,
206
+ lastLoginAt: raw.user_last_login_at,
207
+ additionalFields: raw.user_additional_fields,
208
+ createdAt: raw.user_created_at,
209
+ updatedAt: raw.user_updated_at,
210
+ deletedAt: raw.user_deleted_at,
211
+ createdById: raw.user_created_by_id,
212
+ updatedById: raw.user_updated_by_id,
213
+ deletedById: raw.user_deleted_by_id
214
+ };
215
+ }
216
+ /** Convert entity to response DTO */ convertEntityToDto(entity) {
217
+ return {
218
+ id: entity.id,
219
+ name: entity.name,
220
+ phone: entity.phone,
221
+ email: entity.email,
222
+ profilePictureId: entity.profilePictureId,
223
+ isActive: entity.isActive,
224
+ emailVerified: entity.emailVerified,
225
+ phoneVerified: entity.phoneVerified,
226
+ lastLoginAt: entity.lastLoginAt,
227
+ additionalFields: entity.additionalFields,
228
+ createdAt: entity.createdAt,
229
+ updatedAt: entity.updatedAt,
230
+ deletedAt: entity.deletedAt,
231
+ createdById: entity.createdById,
232
+ updatedById: entity.updatedById,
233
+ deletedById: entity.deletedById
200
234
  };
201
235
  }
202
236
  /** Convert entity list to response DTO list */ convertEntityListToResponseListDto(entities, isRaw) {
@@ -213,7 +247,11 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
213
247
  }
214
248
  /** Add company permission-based isActive check when company feature enabled. Returns isRaw: true for computed fields. */ async getExtraManipulateQuery(query, filterAndPaginationDto, user) {
215
249
  // Repository initialization is handled automatically by RequestScopedApiService
216
- const result = await super.getExtraManipulateQuery(query, filterAndPaginationDto, user);
250
+ let result = await super.getExtraManipulateQuery(query, filterAndPaginationDto, user);
251
+ // Apply user enricher query modifications if available
252
+ if (this.userEnricher?.enrichListQuery) {
253
+ await this.userEnricher.enrichListQuery(query, user);
254
+ }
217
255
  // Only apply company filtering if:
218
256
  // 1. Company feature is enabled at bootstrap
219
257
  // 2. User has company context
@@ -333,16 +371,144 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
333
371
  await queryRunner.release();
334
372
  }
335
373
  }
336
- /** Update user profile with password handling */ async profile(dto, user) {
337
- // Repository initialization is handled automatically by RequestScopedApiService
374
+ /**
375
+ * Get user profile with extras from enricher
376
+ * Returns base user data plus any extra data provided by enricher (roles, permissions, etc.)
377
+ */ async getProfileWithExtras(userId, user) {
378
+ this.validateProfileOwnership(userId, user);
379
+ await this.ensureRepositoryInitialized();
380
+ const userEntity = await this.repository.findOne({
381
+ where: {
382
+ id: userId
383
+ }
384
+ });
385
+ if (!userEntity) {
386
+ throw new _common.NotFoundException('User not found');
387
+ }
388
+ const userData = this.convertEntityToResponseDto(userEntity, false);
389
+ // Get extras from enricher if available
390
+ let extras;
391
+ if (this.userEnricher?.getProfileExtras) {
392
+ extras = await this.userEnricher.getProfileExtras(userId, user);
393
+ }
394
+ return {
395
+ user: userData,
396
+ extras
397
+ };
398
+ }
399
+ /**
400
+ * Get enriched user list
401
+ * Applies enricher transformations after query execution
402
+ */ async getEnrichedList(users, user) {
403
+ if (this.userEnricher?.enrichListItems) {
404
+ return this.userEnricher.enrichListItems(users, user);
405
+ }
406
+ return users;
407
+ }
408
+ // ============================================
409
+ // Multi-Step Profile Methods (via Enricher)
410
+ // ============================================
411
+ /**
412
+ * Get profile section definitions from enricher
413
+ */ async getProfileSections() {
414
+ if (this.userEnricher?.getProfileSections) {
415
+ return this.userEnricher.getProfileSections();
416
+ }
417
+ return [];
418
+ }
419
+ /**
420
+ * Get specific profile section data
421
+ */ async getProfileSectionData(userId, sectionId, user) {
422
+ this.validateProfileOwnership(userId, user);
423
+ await this.ensureRepositoryInitialized();
424
+ // Verify user exists
425
+ const userEntity = await this.repository.findOne({
426
+ where: {
427
+ id: userId
428
+ }
429
+ });
430
+ if (!userEntity) {
431
+ throw new _common.NotFoundException('User not found');
432
+ }
433
+ if (this.userEnricher?.getProfileSectionData) {
434
+ return this.userEnricher.getProfileSectionData(userId, sectionId, user);
435
+ }
436
+ return null;
437
+ }
438
+ /**
439
+ * Update specific profile section
440
+ */ async updateProfileSection(userId, sectionId, data, user) {
441
+ this.validateProfileOwnership(userId, user);
442
+ await this.ensureRepositoryInitialized();
338
443
  if (!this.dataSource) {
339
444
  throw new _common.BadRequestException('DataSource not available');
340
445
  }
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
+ }
455
+ if (!this.userEnricher?.updateProfileSection) {
456
+ throw new _common.BadRequestException('Profile sections not supported');
457
+ }
458
+ const queryRunner = this.dataSource.createQueryRunner();
459
+ await queryRunner.connect();
460
+ await queryRunner.startTransaction();
461
+ try {
462
+ await this.userEnricher.updateProfileSection(userId, sectionId, data, queryRunner);
463
+ await queryRunner.commitTransaction();
464
+ await this.clearCacheForId([
465
+ userEntity
466
+ ]);
467
+ } catch (error) {
468
+ await queryRunner.rollbackTransaction();
469
+ _utils.ErrorHandler.logError(this.logger, error, 'updateProfileSection', {
470
+ entity: this.entityName,
471
+ sectionId
472
+ });
473
+ _utils.ErrorHandler.rethrowError(error);
474
+ } finally{
475
+ await queryRunner.release();
476
+ }
477
+ }
478
+ /**
479
+ * Get profile completion percentage
480
+ */ async getProfileCompletion(userId, user) {
481
+ this.validateProfileOwnership(userId, user);
482
+ await this.ensureRepositoryInitialized();
483
+ // Verify user exists
484
+ const userEntity = await this.repository.findOne({
485
+ where: {
486
+ id: userId
487
+ }
488
+ });
489
+ if (!userEntity) {
490
+ throw new _common.NotFoundException('User not found');
491
+ }
492
+ if (this.userEnricher?.calculateProfileCompletion) {
493
+ return this.userEnricher.calculateProfileCompletion(userId, user);
494
+ }
495
+ return 100; // Default to 100% if no enricher
496
+ }
497
+ /** Update user profile with password handling and extras */ async profile(dto, user) {
498
+ // Ensure repository and dataSource are initialized
499
+ await this.ensureRepositoryInitialized();
500
+ if (!this.dataSource) {
501
+ throw new _common.BadRequestException('DataSource not available');
502
+ }
503
+ const userId = dto.id ?? user.id;
504
+ // Validate extras if enricher provides validation
505
+ if (dto.extras && this.userEnricher?.validateProfileExtras) {
506
+ await this.userEnricher.validateProfileExtras(userId, dto.extras, user);
507
+ }
341
508
  const queryRunner = this.dataSource.createQueryRunner();
342
509
  await queryRunner.connect();
343
510
  await queryRunner.startTransaction();
344
511
  try {
345
- const userId = dto.id ?? user.id;
346
512
  const findUser = await queryRunner.manager.findOne(this.repository.target, {
347
513
  where: {
348
514
  id: userId
@@ -353,18 +519,28 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
353
519
  }
354
520
  // Handle password update
355
521
  if (dto.password) {
356
- // If updating own profile, require old password
357
- if (!dto.id || dto.id === user.id) {
358
- // This would need oldPassword in DTO - for now, skip if not provided
359
- // In real implementation, add oldPassword to UpdateUserDto
522
+ // If updating own profile, require old password verification
523
+ const isOwnProfile = !dto.id || dto.id === user.id;
524
+ if (isOwnProfile) {
525
+ if (!dto.oldPassword) {
526
+ throw new _common.BadRequestException('Current password is required to change your password');
527
+ }
528
+ const isPasswordValid = await _bcrypt.compare(dto.oldPassword, findUser.password);
529
+ if (!isPasswordValid) {
530
+ throw new _common.BadRequestException('Current password is incorrect');
531
+ }
360
532
  }
361
- const salt = await _bcrypt.genSalt();
362
- findUser.password = await _bcrypt.hash(dto.password, salt);
533
+ findUser.password = await _bcrypt.hash(dto.password, _config.BCRYPT_SALT_ROUNDS);
363
534
  }
364
- // Merge other fields (excluding password)
365
- const { password, ...restDto } = dto;
535
+ // Merge other fields (excluding password, oldPassword, extras, and sensitive flags)
536
+ // Users cannot self-modify emailVerified, phoneVerified, or isActive via profile update
537
+ const { password, oldPassword, extras, emailVerified, phoneVerified, isActive, ...restDto } = dto;
366
538
  Object.assign(findUser, restDto);
367
539
  await queryRunner.manager.save(this.repository.target, findUser);
540
+ // Handle extras update via enricher
541
+ if (extras && this.userEnricher?.updateProfileExtras) {
542
+ await this.userEnricher.updateProfileExtras(userId, extras, queryRunner);
543
+ }
368
544
  await queryRunner.commitTransaction();
369
545
  await this.clearCacheForId([
370
546
  findUser
@@ -380,8 +556,8 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
380
556
  await queryRunner.release();
381
557
  }
382
558
  }
383
- constructor(cacheManager, utilsService, dataSourceProvider, authConfig){
384
- super('user', null, cacheManager, utilsService, UserService.name, true), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "dataSourceProvider", void 0), _define_property(this, "authConfig", void 0), _define_property(this, "permissionRepository", void 0), _define_property(this, "dataSource", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.dataSourceProvider = dataSourceProvider, this.authConfig = authConfig;
559
+ constructor(cacheManager, utilsService, dataSourceProvider, authConfig, userEnricher){
560
+ super('user', null, cacheManager, utilsService, UserService.name, true), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "dataSourceProvider", void 0), _define_property(this, "authConfig", void 0), _define_property(this, "userEnricher", void 0), _define_property(this, "permissionRepository", void 0), _define_property(this, "dataSource", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.dataSourceProvider = dataSourceProvider, this.authConfig = authConfig, this.userEnricher = userEnricher;
385
561
  }
386
562
  };
387
563
  UserService = _ts_decorate([
@@ -392,11 +568,14 @@ UserService = _ts_decorate([
392
568
  _ts_param(1, (0, _common.Inject)(_modules.UtilsService)),
393
569
  _ts_param(2, (0, _common.Inject)(_authdatasourceprovider.AuthDataSourceProvider)),
394
570
  _ts_param(3, (0, _common.Inject)(_authconfigservice.AuthConfigService)),
571
+ _ts_param(4, (0, _common.Optional)()),
572
+ _ts_param(4, (0, _common.Inject)(_config.USER_ENRICHER)),
395
573
  _ts_metadata("design:type", Function),
396
574
  _ts_metadata("design:paramtypes", [
397
575
  typeof _classes.HybridCache === "undefined" ? Object : _classes.HybridCache,
398
576
  typeof _modules.UtilsService === "undefined" ? Object : _modules.UtilsService,
399
577
  typeof _authdatasourceprovider.AuthDataSourceProvider === "undefined" ? Object : _authdatasourceprovider.AuthDataSourceProvider,
400
- typeof _authconfigservice.AuthConfigService === "undefined" ? Object : _authconfigservice.AuthConfigService
578
+ typeof _authconfigservice.AuthConfigService === "undefined" ? Object : _authconfigservice.AuthConfigService,
579
+ typeof _userenricherinterface.IUserEnricher === "undefined" ? Object : _userenricherinterface.IUserEnricher
401
580
  ])
402
581
  ], UserService);
@@ -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,4 +1,7 @@
1
1
  export declare const AUTH_MODULE_OPTIONS = "AUTH_MODULE_OPTIONS";
2
+ export declare const USER_ENRICHER = "USER_ENRICHER";
2
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;
3
6
  export declare const SESSION_CACHE_PREFIX = "auth:session";
4
7
  export declare const TOKEN_REVOKED_PREFIX = "auth:revoked";
@@ -1,22 +1,26 @@
1
1
  import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
2
2
  import { Request } from 'express';
3
- import { ChangePasswordDto, CompanySelectionResponseDto, LoginDto, LoginResponseDto, MeResponseDto, RefreshTokenDto, RegistrationDto, RegistrationResponseDto } from '../dtos/authentication.dto';
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
5
  import { AuthConfigService } from '../services/auth-config.service';
6
+ import { AuthEmailService } from '../services/auth-email.service';
5
7
  import { AuthenticationService } from '../services/authentication.service';
6
8
  export declare class AuthenticationController {
7
9
  private readonly authService;
8
10
  private readonly authConfig?;
9
- constructor(authService: AuthenticationService, authConfig?: AuthConfigService);
11
+ private readonly authEmailService?;
12
+ constructor(authService: AuthenticationService, authConfig?: AuthConfigService, authEmailService?: AuthEmailService);
13
+ private buildAuthUrl;
10
14
  login(loginDto: LoginDto): Promise<LoginResponseDto | CompanySelectionResponseDto>;
11
- register(registrationDto: RegistrationDto): Promise<RegistrationResponseDto>;
15
+ register(req: Request, registrationDto: RegistrationDto): Promise<RegistrationResponseDto>;
12
16
  refreshToken(req: Request, refreshTokenDto: RefreshTokenDto): Promise<{
13
17
  accessToken: string;
14
18
  }>;
15
19
  getCurrentUser(user: ILoggedUserInfo): Promise<MeResponseDto>;
16
- logout(userId: string): Promise<{
17
- message: string;
18
- }>;
19
- changePassword(userId: string, dto: ChangePasswordDto): Promise<{
20
- message: string;
21
- }>;
20
+ logout(userId: string): Promise<MessageResponseDto>;
21
+ changePassword(userId: string, dto: ChangePasswordDto): Promise<MessageResponseDto>;
22
+ forgotPassword(req: Request, dto: ForgotPasswordDto): Promise<MessageResponseDto>;
23
+ resetPassword(dto: ResetPasswordDto): Promise<MessageResponseDto>;
24
+ resendVerification(req: Request, dto: ResendVerificationDto): Promise<MessageResponseDto>;
25
+ verifyEmail(dto: VerifyEmailDto): Promise<MessageResponseDto | RegistrationResponseDto>;
22
26
  }
@@ -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,5 +1,6 @@
1
1
  import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
2
2
  import { CreateUserDto, UpdateUserDto, UserResponseDto } from '../dtos/user.dto';
3
+ import { IUser } from '../interfaces/user.interface';
3
4
  import { UserService } from '../services/user.service';
4
5
  declare const UserController_base: abstract new (service: UserService) => {
5
6
  service: UserService;
@@ -14,18 +15,50 @@ declare const UserController_base: abstract new (service: UserService) => {
14
15
  export declare class UserController extends UserController_base {
15
16
  service: UserService;
16
17
  constructor(service: UserService);
18
+ getProfileWithExtras(id: string, user: ILoggedUserInfo): Promise<{
19
+ success: boolean;
20
+ data: {
21
+ user: IUser;
22
+ extras?: import("..").IProfileExtras;
23
+ };
24
+ }>;
17
25
  verifyEmail(id: string): Promise<{
26
+ success: boolean;
18
27
  message: string;
19
28
  }>;
20
29
  verifyPhone(id: string): Promise<{
30
+ success: boolean;
21
31
  message: string;
22
32
  }>;
23
33
  updateStatus(id: string, isActive: boolean, user: ILoggedUserInfo): Promise<{
34
+ success: boolean;
24
35
  message: string;
25
36
  }>;
26
37
  profile(dto: UpdateUserDto, user: ILoggedUserInfo): Promise<{
38
+ success: boolean;
27
39
  message: string;
28
40
  data: string;
29
41
  }>;
42
+ getProfileSections(): Promise<{
43
+ success: boolean;
44
+ message: string;
45
+ data: import("..").IProfileSection[];
46
+ }>;
47
+ getProfileSectionData(id: string, sectionId: string, user: ILoggedUserInfo): Promise<{
48
+ success: boolean;
49
+ message: string;
50
+ data: import("..").IProfileSectionData;
51
+ }>;
52
+ updateProfileSection(id: string, sectionId: string, data: Record<string, any>, user: ILoggedUserInfo): Promise<{
53
+ success: boolean;
54
+ message: string;
55
+ }>;
56
+ getProfileCompletion(id: string, user: ILoggedUserInfo): Promise<{
57
+ success: boolean;
58
+ message: string;
59
+ data: {
60
+ completionPercentage: number;
61
+ };
62
+ }>;
30
63
  }
31
64
  export {};
@@ -71,12 +71,13 @@ export declare class LoginResponseDto {
71
71
  companyFeatureEnabled?: boolean;
72
72
  }
73
73
  export declare class RegistrationResponseDto {
74
- accessToken: string;
75
- refreshToken: string;
74
+ accessToken?: string;
75
+ refreshToken?: string;
76
76
  user: UserInfoDto;
77
77
  company?: CompanyInfoDto;
78
78
  branch?: BranchInfoDto;
79
- companyFeatureEnabled: boolean;
79
+ companyFeatureEnabled?: boolean;
80
+ requiresEmailVerification?: boolean;
80
81
  }
81
82
  export declare class MeResponseDto {
82
83
  user: UserInfoDto;
@@ -90,3 +91,20 @@ export declare class CompanySelectionResponseDto {
90
91
  user: UserInfoDto;
91
92
  companies: CompanyWithBranchesDto[];
92
93
  }
94
+ export declare class LoginResponseRequiresEmailVerificationDto {
95
+ requiresEmailVerification: true;
96
+ email: string;
97
+ message: string;
98
+ }
99
+ export declare class ForgotPasswordDto {
100
+ email: string;
101
+ }
102
+ export declare class ResetPasswordDto {
103
+ token: string;
104
+ newPassword: string;
105
+ }
106
+ export declare class VerifyEmailDto {
107
+ token: string;
108
+ }
109
+ export declare class ResendVerificationDto extends ForgotPasswordDto {
110
+ }
@@ -1,5 +1,4 @@
1
1
  export declare class CreateCompanyBranchDto {
2
- [key: string]: unknown;
3
2
  name: string;
4
3
  slug: string;
5
4
  companyId: string;
@@ -1,5 +1,4 @@
1
1
  export declare class CreateCompanyDto {
2
- [key: string]: unknown;
3
2
  name: string;
4
3
  slug: string;
5
4
  logoId?: string;
@@ -26,8 +26,12 @@ export declare class UserBranchPermissionDto {
26
26
  companyId: string;
27
27
  }
28
28
  export declare class UserCompaniesResponseDto {
29
+ success: boolean;
30
+ message: string;
29
31
  data: UserCompanyPermissionDto[];
30
32
  }
31
33
  export declare class UserBranchesResponseDto {
34
+ success: boolean;
35
+ message: string;
32
36
  data: UserBranchPermissionDto[];
33
37
  }
@@ -1,5 +1,4 @@
1
1
  export declare class CreateUserDto {
2
- [key: string]: unknown;
3
2
  name: string;
4
3
  phone?: string;
5
4
  email: string;
@@ -16,6 +15,7 @@ export declare class UpdateUserDto {
16
15
  phone?: string;
17
16
  email?: string;
18
17
  password?: string;
18
+ oldPassword?: string;
19
19
  profilePictureId?: string;
20
20
  isActive?: boolean;
21
21
  emailVerified?: boolean;
@@ -23,7 +23,6 @@ export declare class UpdateUserDto {
23
23
  additionalFields?: Record<string, any>;
24
24
  }
25
25
  export declare class UserResponseDto {
26
- [key: string]: unknown;
27
26
  id: string;
28
27
  name: string;
29
28
  phone?: string | null;
@@ -1,12 +1,16 @@
1
1
  export * from './company-branch.entity';
2
2
  export * from './company.entity';
3
+ export * from './pending-registration.entity';
3
4
  export { PermissionType, UserCompanyPermission } from './user-company-permission.entity';
4
5
  export * from './user.entity';
6
+ export * from './user-token.entity';
5
7
  import { CompanyBranch } from './company-branch.entity';
6
8
  import { Company } from './company.entity';
9
+ import { PendingRegistration } from './pending-registration.entity';
7
10
  import { UserCompanyPermission } from './user-company-permission.entity';
8
11
  import { User } from './user.entity';
9
- export declare const CoreEntities: (typeof User)[];
12
+ import { UserToken } from './user-token.entity';
13
+ export declare const CoreEntities: (typeof PendingRegistration | typeof User | typeof UserToken)[];
10
14
  export declare const CompanyEntities: (typeof Company | typeof CompanyBranch | typeof UserCompanyPermission)[];
11
- export declare const AllEntities: (typeof Company | typeof CompanyBranch | typeof User | typeof UserCompanyPermission)[];
12
- export declare function getEntitiesByConfig(enableCompanyFeature: boolean): (typeof Company | typeof CompanyBranch | typeof User | typeof UserCompanyPermission)[];
15
+ export declare const AllEntities: (typeof Company | typeof CompanyBranch | typeof PendingRegistration | typeof User | typeof UserCompanyPermission | typeof UserToken)[];
16
+ export declare function getEntitiesByConfig(enableCompanyFeature: boolean): (typeof Company | typeof CompanyBranch | typeof PendingRegistration | typeof User | typeof UserCompanyPermission | typeof UserToken)[];
@@ -0,0 +1,17 @@
1
+ export declare class PendingRegistration {
2
+ id: string;
3
+ email: string;
4
+ name: string | null;
5
+ phone: string | null;
6
+ passwordHash: string;
7
+ token: string;
8
+ expiresAt: Date;
9
+ companyData: {
10
+ companySlug?: string;
11
+ branchSlug?: string;
12
+ newCompanyName?: string;
13
+ newCompanyPhone?: string;
14
+ newCompanyAddress?: string;
15
+ } | null;
16
+ createdAt: Date;
17
+ }
@@ -15,6 +15,4 @@ export declare class UserCompanyPermission {
15
15
  branch?: CompanyBranch;
16
16
  createdAt: Date;
17
17
  updatedAt: Date;
18
- isCompanyPermission(): boolean;
19
- isBranchPermission(): boolean;
20
18
  }
@@ -0,0 +1,15 @@
1
+ export declare enum TokenType {
2
+ PASSWORD_RESET = "password_reset",
3
+ EMAIL_VERIFICATION = "email_verification",
4
+ EMAIL_CHANGE = "email_change"
5
+ }
6
+ export declare class UserToken {
7
+ id: string;
8
+ userId: string;
9
+ type: TokenType;
10
+ token: string;
11
+ expiresAt: Date;
12
+ usedAt: Date | null;
13
+ metadata: string | null;
14
+ createdAt: Date;
15
+ }
@@ -1,7 +1,11 @@
1
1
  // Injection Tokens
2
2
  export const AUTH_MODULE_OPTIONS = 'AUTH_MODULE_OPTIONS';
3
- // Security Configuration
4
- /** Bcrypt salt rounds (12 = ~250ms hashing time) */ export const BCRYPT_SALT_ROUNDS = 12;
3
+ export const USER_ENRICHER = 'USER_ENRICHER';
4
+ // Security
5
+ export const BCRYPT_SALT_ROUNDS = 12;
6
+ // Cookie
7
+ export const DEFAULT_COOKIE_NAME = 'fsn_refresh_token';
8
+ export const MAX_COOKIE_BYTES = 3500;
5
9
  // Cache Key Prefixes
6
10
  export const SESSION_CACHE_PREFIX = 'auth:session';
7
11
  export const TOKEN_REVOKED_PREFIX = 'auth:revoked';