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

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 (55) hide show
  1. package/cjs/config/auth.constants.js +4 -0
  2. package/cjs/controllers/authentication.controller.js +167 -5
  3. package/cjs/controllers/user.controller.js +151 -0
  4. package/cjs/dtos/authentication.dto.js +106 -3
  5. package/cjs/entities/index.js +5 -1
  6. package/cjs/entities/pending-registration.entity.js +125 -0
  7. package/cjs/entities/user-token.entity.js +129 -0
  8. package/cjs/interfaces/auth-email-provider.interface.js +45 -0
  9. package/cjs/interfaces/index.js +2 -0
  10. package/cjs/interfaces/user-enricher.interface.js +4 -0
  11. package/cjs/modules/auth.module.js +8 -2
  12. package/cjs/services/auth-config.service.js +4 -0
  13. package/cjs/services/auth-datasource.provider.js +4 -2
  14. package/cjs/services/auth-email.service.js +452 -0
  15. package/cjs/services/authentication.service.js +156 -37
  16. package/cjs/services/index.js +1 -0
  17. package/cjs/services/user.service.js +147 -11
  18. package/config/auth.constants.d.ts +1 -0
  19. package/controllers/authentication.controller.d.ts +13 -9
  20. package/controllers/user.controller.d.ts +27 -0
  21. package/dtos/authentication.dto.d.ts +21 -3
  22. package/dtos/user.dto.d.ts +1 -0
  23. package/entities/index.d.ts +7 -3
  24. package/entities/pending-registration.entity.d.ts +17 -0
  25. package/entities/user-token.entity.d.ts +15 -0
  26. package/fesm/config/auth.constants.js +11 -0
  27. package/fesm/controllers/authentication.controller.js +168 -6
  28. package/fesm/controllers/user.controller.js +151 -0
  29. package/fesm/dtos/authentication.dto.js +92 -3
  30. package/fesm/entities/index.js +7 -1
  31. package/fesm/entities/pending-registration.entity.js +115 -0
  32. package/fesm/entities/user-token.entity.js +113 -0
  33. package/fesm/interfaces/auth-email-provider.interface.js +38 -0
  34. package/fesm/interfaces/authentication.interface.js +1 -1
  35. package/fesm/interfaces/index.js +2 -0
  36. package/fesm/interfaces/user-enricher.interface.js +26 -0
  37. package/fesm/modules/auth.module.js +9 -3
  38. package/fesm/services/auth-config.service.js +4 -0
  39. package/fesm/services/auth-datasource.provider.js +4 -2
  40. package/fesm/services/auth-email.service.js +401 -0
  41. package/fesm/services/authentication.service.js +156 -37
  42. package/fesm/services/index.js +1 -0
  43. package/fesm/services/user.service.js +149 -13
  44. package/interfaces/auth-email-provider.interface.d.ts +6 -0
  45. package/interfaces/auth-module-options.interface.d.ts +4 -1
  46. package/interfaces/authentication.interface.d.ts +14 -0
  47. package/interfaces/index.d.ts +2 -0
  48. package/interfaces/user-enricher.interface.d.ts +67 -0
  49. package/interfaces/user.interface.d.ts +1 -0
  50. package/package.json +3 -3
  51. package/services/auth-config.service.d.ts +1 -0
  52. package/services/auth-email.service.d.ts +50 -0
  53. package/services/authentication.service.d.ts +16 -4
  54. package/services/index.d.ts +1 -0
  55. package/services/user.service.d.ts +16 -3
@@ -19,6 +19,7 @@ const _authdatasourceprovider = require("./auth-datasource.provider");
19
19
  const _enums = require("../enums");
20
20
  const _config = require("../config");
21
21
  const _classes = require("@flusys/nestjs-shared/classes");
22
+ const _authemailservice = require("./auth-email.service");
22
23
  const _companybranchentity = require("../entities/company-branch.entity");
23
24
  const _companyentity = require("../entities/company.entity");
24
25
  const _usercompanypermissionentity = require("../entities/user-company-permission.entity");
@@ -130,7 +131,14 @@ let AuthenticationService = class AuthenticationService {
130
131
  if (!user.isActive) {
131
132
  throw new _common.UnauthorizedException('User account is deactivated');
132
133
  }
133
- // If company feature not enabled, return simple token
134
+ // Check email verification if email service is enabled
135
+ if (this.authEmailService?.isEmailEnabled() && !user.emailVerified) {
136
+ return {
137
+ requiresEmailVerification: true,
138
+ email: user.email,
139
+ message: 'Please verify your email address before logging in.'
140
+ };
141
+ }
134
142
  if (!this.authConfig.isCompanyFeatureEnabled()) {
135
143
  return this.generateLoginResponse(user);
136
144
  }
@@ -187,10 +195,8 @@ let AuthenticationService = class AuthenticationService {
187
195
  return await this.generateCompanySelectionResponse(user, companies, branches);
188
196
  }
189
197
  // Registration
190
- /** Register a new user. If company feature enabled, optionally join/create company. */ async register(dto) {
198
+ /** Register a new user. If email enabled, creates pending registration. Otherwise creates user directly. */ async register(dto, verifyUrlBase) {
191
199
  const userRepository = await this.getUserRepository();
192
- // Check if user exists
193
- // Note: Generic error message to prevent email enumeration attacks
194
200
  const existingUser = await userRepository.findOne({
195
201
  where: {
196
202
  email: dto.email
@@ -199,7 +205,33 @@ let AuthenticationService = class AuthenticationService {
199
205
  if (existingUser) {
200
206
  throw new _common.BadRequestException('Registration failed. Please try again or contact support.');
201
207
  }
202
- // Create user
208
+ // If email verification is enabled, create pending registration instead of user
209
+ if (this.authEmailService?.isEmailEnabled() && verifyUrlBase) {
210
+ const companyData = this.authConfig.isCompanyFeatureEnabled() ? {
211
+ companySlug: dto.companySlug,
212
+ branchSlug: dto.branchSlug,
213
+ newCompanyName: dto.newCompanyName,
214
+ newCompanyPhone: dto.newCompanyPhone,
215
+ newCompanyAddress: dto.newCompanyAddress
216
+ } : undefined;
217
+ const result = await this.authEmailService.createPendingRegistration({
218
+ email: dto.email,
219
+ name: dto.name,
220
+ phone: dto.phone,
221
+ password: dto.password,
222
+ companyData,
223
+ verifyUrlBase
224
+ });
225
+ return {
226
+ requiresEmailVerification: true,
227
+ message: result.message
228
+ };
229
+ }
230
+ // No email verification - create user directly
231
+ return this.createUserDirectly(dto);
232
+ }
233
+ /** Create user directly (no email verification) */ async createUserDirectly(dto) {
234
+ const userRepository = await this.getUserRepository();
203
235
  const hashedPassword = await _bcrypt.hash(dto.password, _config.BCRYPT_SALT_ROUNDS);
204
236
  const user = userRepository.create({
205
237
  name: dto.name,
@@ -211,38 +243,68 @@ let AuthenticationService = class AuthenticationService {
211
243
  phoneVerified: false
212
244
  });
213
245
  await userRepository.save(user);
214
- // If company feature not enabled, return simple response
215
246
  if (!this.authConfig.isCompanyFeatureEnabled()) {
216
- return this.generateLoginResponse(user, false);
247
+ return this.generateRegistrationResponse(user, false, false);
248
+ }
249
+ return this.handleCompanyRegistration(user, dto, false);
250
+ }
251
+ /** Create user from pending registration (after email verification) */ async createUserFromPendingRegistration(pending) {
252
+ const userRepository = await this.getUserRepository();
253
+ // Check if user already exists (race condition protection)
254
+ const existingUser = await userRepository.findOne({
255
+ where: {
256
+ email: pending.email
257
+ }
258
+ });
259
+ if (existingUser) {
260
+ throw new _common.BadRequestException('This email has already been registered.');
261
+ }
262
+ // Create user with verified email
263
+ const user = userRepository.create({
264
+ name: pending.name,
265
+ email: pending.email,
266
+ phone: pending.phone,
267
+ password: pending.passwordHash,
268
+ isActive: true,
269
+ emailVerified: true,
270
+ phoneVerified: false
271
+ });
272
+ await userRepository.save(user);
273
+ // Handle company feature if enabled
274
+ if (this.authConfig.isCompanyFeatureEnabled() && pending.companyData) {
275
+ return this.handleCompanyRegistrationFromPending(user, pending.companyData);
217
276
  }
218
- // Handle company feature
219
- return this.handleCompanyRegistration(user, dto);
277
+ return this.generateRegistrationResponse(user, this.authConfig.isCompanyFeatureEnabled(), false);
278
+ }
279
+ /** Handle company registration from pending registration data */ async handleCompanyRegistrationFromPending(user, companyData) {
280
+ return this.processCompanyRegistration(user, companyData, false);
281
+ }
282
+ /** Handle company-related registration logic */ async handleCompanyRegistration(user, dto, requiresEmailVerification) {
283
+ return this.processCompanyRegistration(user, dto, requiresEmailVerification);
220
284
  }
221
- /** Handle company-related registration logic */ async handleCompanyRegistration(user, dto) {
285
+ /** Shared logic for company registration (join existing or create new) */ async processCompanyRegistration(user, data, requiresEmailVerification) {
222
286
  const companyRepository = await this.getCompanyRepository();
223
287
  const branchRepository = await this.getBranchRepository();
224
288
  const permissionRepository = await this.getPermissionRepository();
225
289
  if (!companyRepository || !branchRepository || !permissionRepository) {
226
- return this.generateLoginResponse(user, true);
290
+ return this.generateRegistrationResponse(user, true, requiresEmailVerification);
227
291
  }
228
292
  let company = null;
229
293
  let branch = null;
230
- // Case 1: Join existing company by slug
231
- if (dto.companySlug) {
294
+ if (data.companySlug) {
232
295
  company = await companyRepository.findOne({
233
296
  where: {
234
- slug: dto.companySlug,
297
+ slug: data.companySlug,
235
298
  isActive: true
236
299
  }
237
300
  });
238
301
  if (!company) {
239
302
  throw new _common.BadRequestException('Company not found or inactive');
240
303
  }
241
- // Find branch by slug or get default
242
- if (dto.branchSlug) {
304
+ if (data.branchSlug) {
243
305
  branch = await branchRepository.findOne({
244
306
  where: {
245
- slug: dto.branchSlug,
307
+ slug: data.branchSlug,
246
308
  companyId: company.id,
247
309
  isActive: true
248
310
  }
@@ -251,7 +313,6 @@ let AuthenticationService = class AuthenticationService {
251
313
  throw new _common.BadRequestException('Branch not found or inactive');
252
314
  }
253
315
  } else {
254
- // Get first active branch
255
316
  branch = await branchRepository.findOne({
256
317
  where: {
257
318
  companyId: company.id,
@@ -263,9 +324,8 @@ let AuthenticationService = class AuthenticationService {
263
324
  }
264
325
  });
265
326
  }
266
- } else if (dto.newCompanyName) {
267
- const slug = this.generateSlug(dto.newCompanyName);
268
- // Check if slug exists
327
+ } else if (data.newCompanyName) {
328
+ const slug = this.generateSlug(data.newCompanyName);
269
329
  const existingCompany = await companyRepository.findOne({
270
330
  where: {
271
331
  slug
@@ -274,16 +334,14 @@ let AuthenticationService = class AuthenticationService {
274
334
  if (existingCompany) {
275
335
  throw new _common.BadRequestException('Company creation failed. Please try a different name.');
276
336
  }
277
- // Create company
278
337
  company = companyRepository.create({
279
- name: dto.newCompanyName,
338
+ name: data.newCompanyName,
280
339
  slug,
281
- phone: dto.newCompanyPhone,
282
- address: dto.newCompanyAddress,
340
+ phone: data.newCompanyPhone,
341
+ address: data.newCompanyAddress,
283
342
  isActive: true
284
343
  });
285
344
  await companyRepository.save(company);
286
- // Create default branch
287
345
  branch = branchRepository.create({
288
346
  name: 'Main Branch',
289
347
  slug: 'main-branch',
@@ -292,16 +350,13 @@ let AuthenticationService = class AuthenticationService {
292
350
  });
293
351
  await branchRepository.save(branch);
294
352
  }
295
- // Create permissions if company exists
296
353
  if (company) {
297
- // Create company permission
298
354
  const companyPermission = permissionRepository.create({
299
355
  userId: user.id,
300
356
  permissionType: _enums.PermissionType.COMPANY,
301
357
  targetId: company.id
302
358
  });
303
359
  await permissionRepository.save(companyPermission);
304
- // Create branch permission if branch exists
305
360
  if (branch) {
306
361
  const branchPermission = permissionRepository.create({
307
362
  userId: user.id,
@@ -310,10 +365,9 @@ let AuthenticationService = class AuthenticationService {
310
365
  });
311
366
  await permissionRepository.save(branchPermission);
312
367
  }
313
- return this.generateLoginResponseWithCompany(user, company, branch ?? undefined);
368
+ return this.generateRegistrationResponseWithCompany(user, company, branch ?? undefined, requiresEmailVerification);
314
369
  }
315
- // No company assigned
316
- return this.generateLoginResponse(user, true);
370
+ return this.generateRegistrationResponse(user, true, requiresEmailVerification);
317
371
  }
318
372
  /** Generate URL-friendly slug from name */ generateSlug(name) {
319
373
  return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').substring(0, 100);
@@ -666,6 +720,7 @@ let AuthenticationService = class AuthenticationService {
666
720
  id: user.id,
667
721
  email: user.email,
668
722
  name: user.name ?? undefined,
723
+ phone: user.phone ?? undefined,
669
724
  profilePictureId: user.profilePictureId ?? undefined
670
725
  }
671
726
  };
@@ -752,6 +807,7 @@ let AuthenticationService = class AuthenticationService {
752
807
  id: user.id,
753
808
  email: user.email,
754
809
  name: user.name ?? undefined,
810
+ phone: user.phone ?? null,
755
811
  profilePictureId: user.profilePictureId ?? null
756
812
  },
757
813
  companyFeatureEnabled
@@ -787,6 +843,7 @@ let AuthenticationService = class AuthenticationService {
787
843
  id: user.id,
788
844
  email: user.email,
789
845
  name: user.name ?? undefined,
846
+ phone: user.phone ?? null,
790
847
  profilePictureId: user.profilePictureId ?? null
791
848
  },
792
849
  company: {
@@ -832,18 +889,76 @@ let AuthenticationService = class AuthenticationService {
832
889
  id: user.id,
833
890
  email: user.email,
834
891
  name: user.name ?? undefined,
892
+ phone: user.phone ?? null,
835
893
  profilePictureId: user.profilePictureId ?? null
836
894
  },
837
895
  companies: companiesWithBranches
838
896
  };
839
897
  }
898
+ /** Generate registration response - tokens included only if email verification not required */ generateRegistrationResponse(user, companyFeatureEnabled, requiresEmailVerification) {
899
+ const userInfo = {
900
+ id: user.id,
901
+ email: user.email,
902
+ name: user.name ?? undefined,
903
+ phone: user.phone ?? null,
904
+ profilePictureId: user.profilePictureId ?? null
905
+ };
906
+ if (requiresEmailVerification) {
907
+ return {
908
+ user: userInfo,
909
+ companyFeatureEnabled,
910
+ requiresEmailVerification: true
911
+ };
912
+ }
913
+ const loginResponse = this.generateLoginResponse(user, companyFeatureEnabled);
914
+ return {
915
+ ...loginResponse,
916
+ requiresEmailVerification: false
917
+ };
918
+ }
919
+ /** Generate registration response with company - tokens included only if email verification not required */ generateRegistrationResponseWithCompany(user, company, branch, requiresEmailVerification = false) {
920
+ const userInfo = {
921
+ id: user.id,
922
+ email: user.email,
923
+ name: user.name ?? undefined,
924
+ phone: user.phone ?? null,
925
+ profilePictureId: user.profilePictureId ?? null
926
+ };
927
+ const companyInfo = {
928
+ id: company.id,
929
+ name: company.name,
930
+ slug: company.slug,
931
+ logoId: company.logoId ?? null
932
+ };
933
+ const branchInfo = branch ? {
934
+ id: branch.id,
935
+ name: branch.name,
936
+ slug: branch.slug,
937
+ logoId: branch.logoId ?? null
938
+ } : undefined;
939
+ if (requiresEmailVerification) {
940
+ return {
941
+ user: userInfo,
942
+ company: companyInfo,
943
+ branch: branchInfo,
944
+ companyFeatureEnabled: true,
945
+ requiresEmailVerification: true
946
+ };
947
+ }
948
+ const loginResponse = this.generateLoginResponseWithCompany(user, company, branch);
949
+ return {
950
+ ...loginResponse,
951
+ requiresEmailVerification: false
952
+ };
953
+ }
840
954
  // NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
841
- constructor(jwtService, authConfig, cacheManager, dataSourceProvider, sessionService, injectedUserRepository, injectedPermissionRepository, injectedCompanyRepository, injectedBranchRepository){
955
+ constructor(jwtService, authConfig, cacheManager, dataSourceProvider, sessionService, authEmailService, injectedUserRepository, injectedPermissionRepository, injectedCompanyRepository, injectedBranchRepository){
842
956
  _define_property(this, "jwtService", void 0);
843
957
  _define_property(this, "authConfig", void 0);
844
958
  _define_property(this, "cacheManager", void 0);
845
959
  _define_property(this, "dataSourceProvider", void 0);
846
960
  _define_property(this, "sessionService", void 0);
961
+ _define_property(this, "authEmailService", void 0);
847
962
  _define_property(this, "injectedUserRepository", void 0);
848
963
  _define_property(this, "injectedPermissionRepository", void 0);
849
964
  _define_property(this, "injectedCompanyRepository", void 0);
@@ -854,6 +969,7 @@ let AuthenticationService = class AuthenticationService {
854
969
  this.cacheManager = cacheManager;
855
970
  this.dataSourceProvider = dataSourceProvider;
856
971
  this.sessionService = sessionService;
972
+ this.authEmailService = authEmailService;
857
973
  this.injectedUserRepository = injectedUserRepository;
858
974
  this.injectedPermissionRepository = injectedPermissionRepository;
859
975
  this.injectedCompanyRepository = injectedCompanyRepository;
@@ -874,13 +990,15 @@ AuthenticationService = _ts_decorate([
874
990
  _ts_param(4, (0, _common.Optional)()),
875
991
  _ts_param(4, (0, _common.Inject)(_companyselectionsessionservice.CompanySelectionSessionService)),
876
992
  _ts_param(5, (0, _common.Optional)()),
877
- _ts_param(5, (0, _typeorm.InjectRepository)(_userentity.User)),
993
+ _ts_param(5, (0, _common.Inject)(_authemailservice.AuthEmailService)),
878
994
  _ts_param(6, (0, _common.Optional)()),
879
- _ts_param(6, (0, _typeorm.InjectRepository)(_usercompanypermissionentity.UserCompanyPermission)),
995
+ _ts_param(6, (0, _typeorm.InjectRepository)(_userentity.User)),
880
996
  _ts_param(7, (0, _common.Optional)()),
881
- _ts_param(7, (0, _typeorm.InjectRepository)(_companyentity.Company)),
997
+ _ts_param(7, (0, _typeorm.InjectRepository)(_usercompanypermissionentity.UserCompanyPermission)),
882
998
  _ts_param(8, (0, _common.Optional)()),
883
- _ts_param(8, (0, _typeorm.InjectRepository)(_companybranchentity.CompanyBranch)),
999
+ _ts_param(8, (0, _typeorm.InjectRepository)(_companyentity.Company)),
1000
+ _ts_param(9, (0, _common.Optional)()),
1001
+ _ts_param(9, (0, _typeorm.InjectRepository)(_companybranchentity.CompanyBranch)),
884
1002
  _ts_metadata("design:type", Function),
885
1003
  _ts_metadata("design:paramtypes", [
886
1004
  typeof _jwt.JwtService === "undefined" ? Object : _jwt.JwtService,
@@ -888,6 +1006,7 @@ AuthenticationService = _ts_decorate([
888
1006
  typeof _classes.HybridCache === "undefined" ? Object : _classes.HybridCache,
889
1007
  typeof _authdatasourceprovider.AuthDataSourceProvider === "undefined" ? Object : _authdatasourceprovider.AuthDataSourceProvider,
890
1008
  typeof _companyselectionsessionservice.CompanySelectionSessionService === "undefined" ? Object : _companyselectionsessionservice.CompanySelectionSessionService,
1009
+ typeof _authemailservice.AuthEmailService === "undefined" ? Object : _authemailservice.AuthEmailService,
891
1010
  typeof _typeorm1.Repository === "undefined" ? Object : _typeorm1.Repository,
892
1011
  typeof _typeorm1.Repository === "undefined" ? Object : _typeorm1.Repository,
893
1012
  typeof _typeorm1.Repository === "undefined" ? Object : _typeorm1.Repository,
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
4
  });
5
5
  _export_star(require("./authentication.service"), exports);
6
+ _export_star(require("./auth-email.service"), exports);
6
7
  _export_star(require("./company-selection-session.service"), exports);
7
8
  _export_star(require("./user.service"), exports);
8
9
  _export_star(require("./company.service"), exports);
@@ -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) {
@@ -170,8 +171,8 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
170
171
  /** Convert DTO to entity (hash password before saving) */ async convertSingleDtoToEntity(dto, user) {
171
172
  // Repository initialization is handled automatically by RequestScopedApiService
172
173
  const entity = await super.convertSingleDtoToEntity(dto, user);
173
- // Hash password if provided
174
- if (dto.password) {
174
+ // Hash password if provided (CreateUserDto has password field)
175
+ if ('password' in dto && dto.password) {
175
176
  entity.password = await _bcrypt.hash(dto.password, _config.BCRYPT_SALT_ROUNDS);
176
177
  }
177
178
  return entity;
@@ -213,7 +214,11 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
213
214
  }
214
215
  /** Add company permission-based isActive check when company feature enabled. Returns isRaw: true for computed fields. */ async getExtraManipulateQuery(query, filterAndPaginationDto, user) {
215
216
  // Repository initialization is handled automatically by RequestScopedApiService
216
- const result = await super.getExtraManipulateQuery(query, filterAndPaginationDto, user);
217
+ let result = await super.getExtraManipulateQuery(query, filterAndPaginationDto, user);
218
+ // Apply user enricher query modifications if available
219
+ if (this.userEnricher?.enrichListQuery) {
220
+ await this.userEnricher.enrichListQuery(query, user);
221
+ }
217
222
  // Only apply company filtering if:
218
223
  // 1. Company feature is enabled at bootstrap
219
224
  // 2. User has company context
@@ -333,16 +338,140 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
333
338
  await queryRunner.release();
334
339
  }
335
340
  }
336
- /** Update user profile with password handling */ async profile(dto, user) {
337
- // Repository initialization is handled automatically by RequestScopedApiService
341
+ /**
342
+ * Get user profile with extras from enricher
343
+ * Returns base user data plus any extra data provided by enricher (roles, permissions, etc.)
344
+ */ 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
+ }
354
+ const userData = this.convertEntityToResponseDto(userEntity, false);
355
+ // Get extras from enricher if available
356
+ let extras;
357
+ if (this.userEnricher?.getProfileExtras) {
358
+ extras = await this.userEnricher.getProfileExtras(userId, user);
359
+ }
360
+ return {
361
+ user: userData,
362
+ extras
363
+ };
364
+ }
365
+ /**
366
+ * Get enriched user list
367
+ * Applies enricher transformations after query execution
368
+ */ async getEnrichedList(users, user) {
369
+ if (this.userEnricher?.enrichListItems) {
370
+ return this.userEnricher.enrichListItems(users, user);
371
+ }
372
+ return users;
373
+ }
374
+ // ============================================
375
+ // Multi-Step Profile Methods (via Enricher)
376
+ // ============================================
377
+ /**
378
+ * Get profile section definitions from enricher
379
+ */ async getProfileSections() {
380
+ if (this.userEnricher?.getProfileSections) {
381
+ return this.userEnricher.getProfileSections();
382
+ }
383
+ return [];
384
+ }
385
+ /**
386
+ * Get specific profile section data
387
+ */ 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
+ }
398
+ if (this.userEnricher?.getProfileSectionData) {
399
+ return this.userEnricher.getProfileSectionData(userId, sectionId, user);
400
+ }
401
+ return null;
402
+ }
403
+ /**
404
+ * Update specific profile section
405
+ */ async updateProfileSection(userId, sectionId, data, user) {
406
+ await this.ensureRepositoryInitialized();
407
+ if (!this.dataSource) {
408
+ throw new _common.BadRequestException('DataSource not available');
409
+ }
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
+ if (!this.userEnricher?.updateProfileSection) {
420
+ throw new _common.BadRequestException('Profile sections not supported');
421
+ }
422
+ const queryRunner = this.dataSource.createQueryRunner();
423
+ await queryRunner.connect();
424
+ await queryRunner.startTransaction();
425
+ try {
426
+ await this.userEnricher.updateProfileSection(userId, sectionId, data, queryRunner);
427
+ await queryRunner.commitTransaction();
428
+ await this.clearCacheForId([
429
+ userEntity
430
+ ]);
431
+ } catch (error) {
432
+ await queryRunner.rollbackTransaction();
433
+ _utils.ErrorHandler.logError(this.logger, error, 'updateProfileSection', {
434
+ entity: this.entityName,
435
+ sectionId
436
+ });
437
+ _utils.ErrorHandler.rethrowError(error);
438
+ } finally{
439
+ await queryRunner.release();
440
+ }
441
+ }
442
+ /**
443
+ * Get profile completion percentage
444
+ */ 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
+ }
455
+ if (this.userEnricher?.calculateProfileCompletion) {
456
+ return this.userEnricher.calculateProfileCompletion(userId, user);
457
+ }
458
+ return 100; // Default to 100% if no enricher
459
+ }
460
+ /** Update user profile with password handling and extras */ async profile(dto, user) {
461
+ // Ensure repository and dataSource are initialized
462
+ await this.ensureRepositoryInitialized();
338
463
  if (!this.dataSource) {
339
464
  throw new _common.BadRequestException('DataSource not available');
340
465
  }
466
+ const userId = dto.id ?? user.id;
467
+ // Validate extras if enricher provides validation
468
+ if (dto.extras && this.userEnricher?.validateProfileExtras) {
469
+ await this.userEnricher.validateProfileExtras(userId, dto.extras, user);
470
+ }
341
471
  const queryRunner = this.dataSource.createQueryRunner();
342
472
  await queryRunner.connect();
343
473
  await queryRunner.startTransaction();
344
474
  try {
345
- const userId = dto.id ?? user.id;
346
475
  const findUser = await queryRunner.manager.findOne(this.repository.target, {
347
476
  where: {
348
477
  id: userId
@@ -361,10 +490,14 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
361
490
  const salt = await _bcrypt.genSalt();
362
491
  findUser.password = await _bcrypt.hash(dto.password, salt);
363
492
  }
364
- // Merge other fields (excluding password)
365
- const { password, ...restDto } = dto;
493
+ // Merge other fields (excluding password and extras)
494
+ const { password, extras, ...restDto } = dto;
366
495
  Object.assign(findUser, restDto);
367
496
  await queryRunner.manager.save(this.repository.target, findUser);
497
+ // Handle extras update via enricher
498
+ if (extras && this.userEnricher?.updateProfileExtras) {
499
+ await this.userEnricher.updateProfileExtras(userId, extras, queryRunner);
500
+ }
368
501
  await queryRunner.commitTransaction();
369
502
  await this.clearCacheForId([
370
503
  findUser
@@ -380,8 +513,8 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
380
513
  await queryRunner.release();
381
514
  }
382
515
  }
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;
516
+ constructor(cacheManager, utilsService, dataSourceProvider, authConfig, userEnricher){
517
+ 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
518
  }
386
519
  };
387
520
  UserService = _ts_decorate([
@@ -392,11 +525,14 @@ UserService = _ts_decorate([
392
525
  _ts_param(1, (0, _common.Inject)(_modules.UtilsService)),
393
526
  _ts_param(2, (0, _common.Inject)(_authdatasourceprovider.AuthDataSourceProvider)),
394
527
  _ts_param(3, (0, _common.Inject)(_authconfigservice.AuthConfigService)),
528
+ _ts_param(4, (0, _common.Optional)()),
529
+ _ts_param(4, (0, _common.Inject)(_config.USER_ENRICHER)),
395
530
  _ts_metadata("design:type", Function),
396
531
  _ts_metadata("design:paramtypes", [
397
532
  typeof _classes.HybridCache === "undefined" ? Object : _classes.HybridCache,
398
533
  typeof _modules.UtilsService === "undefined" ? Object : _modules.UtilsService,
399
534
  typeof _authdatasourceprovider.AuthDataSourceProvider === "undefined" ? Object : _authdatasourceprovider.AuthDataSourceProvider,
400
- typeof _authconfigservice.AuthConfigService === "undefined" ? Object : _authconfigservice.AuthConfigService
535
+ typeof _authconfigservice.AuthConfigService === "undefined" ? Object : _authconfigservice.AuthConfigService,
536
+ typeof _userenricherinterface.IUserEnricher === "undefined" ? Object : _userenricherinterface.IUserEnricher
401
537
  ])
402
538
  ], UserService);
@@ -1,4 +1,5 @@
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;
3
4
  export declare const SESSION_CACHE_PREFIX = "auth:session";
4
5
  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
  }