@nauth-toolkit/core 0.1.36 → 0.1.39

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 (46) hide show
  1. package/dist/dto/auth-response.dto.d.ts +60 -54
  2. package/dist/dto/auth-response.dto.d.ts.map +1 -1
  3. package/dist/dto/auth-response.dto.js +23 -11
  4. package/dist/dto/auth-response.dto.js.map +1 -1
  5. package/dist/dto/index.d.ts +1 -0
  6. package/dist/dto/index.d.ts.map +1 -1
  7. package/dist/dto/index.js +1 -0
  8. package/dist/dto/index.js.map +1 -1
  9. package/dist/dto/update-verified-status-request.dto.d.ts +70 -0
  10. package/dist/dto/update-verified-status-request.dto.d.ts.map +1 -0
  11. package/dist/dto/update-verified-status-request.dto.js +107 -0
  12. package/dist/dto/update-verified-status-request.dto.js.map +1 -0
  13. package/dist/interfaces/config.interface.d.ts +23 -6
  14. package/dist/interfaces/config.interface.d.ts.map +1 -1
  15. package/dist/interfaces/hooks.interface.d.ts +152 -117
  16. package/dist/interfaces/hooks.interface.d.ts.map +1 -1
  17. package/dist/services/adaptive-mfa-decision.service.d.ts.map +1 -1
  18. package/dist/services/adaptive-mfa-decision.service.js +2 -2
  19. package/dist/services/adaptive-mfa-decision.service.js.map +1 -1
  20. package/dist/services/auth-challenge-helper.service.d.ts.map +1 -1
  21. package/dist/services/auth-challenge-helper.service.js +2 -11
  22. package/dist/services/auth-challenge-helper.service.js.map +1 -1
  23. package/dist/services/auth.service.d.ts +37 -0
  24. package/dist/services/auth.service.d.ts.map +1 -1
  25. package/dist/services/auth.service.js +237 -17
  26. package/dist/services/auth.service.js.map +1 -1
  27. package/dist/services/email-verification.service.d.ts +3 -1
  28. package/dist/services/email-verification.service.d.ts.map +1 -1
  29. package/dist/services/email-verification.service.js +77 -1
  30. package/dist/services/email-verification.service.js.map +1 -1
  31. package/dist/services/hook-registry.service.d.ts +34 -63
  32. package/dist/services/hook-registry.service.d.ts.map +1 -1
  33. package/dist/services/hook-registry.service.js +53 -66
  34. package/dist/services/hook-registry.service.js.map +1 -1
  35. package/dist/services/phone-verification.service.d.ts +3 -1
  36. package/dist/services/phone-verification.service.d.ts.map +1 -1
  37. package/dist/services/phone-verification.service.js +80 -1
  38. package/dist/services/phone-verification.service.js.map +1 -1
  39. package/dist/services/social-auth-base.service.d.ts +2 -1
  40. package/dist/services/social-auth-base.service.d.ts.map +1 -1
  41. package/dist/services/social-auth-base.service.js +6 -3
  42. package/dist/services/social-auth-base.service.js.map +1 -1
  43. package/dist/utils/setup/init-services.d.ts.map +1 -1
  44. package/dist/utils/setup/init-services.js +2 -2
  45. package/dist/utils/setup/init-services.js.map +1 -1
  46. package/package.json +1 -1
@@ -47,7 +47,9 @@ const enable_user_dto_1 = require("../dto/enable-user.dto");
47
47
  const login_dto_1 = require("../dto/login.dto");
48
48
  const change_password_request_dto_1 = require("../dto/change-password-request.dto");
49
49
  const update_user_attributes_request_dto_1 = require("../dto/update-user-attributes-request.dto");
50
+ const update_verified_status_request_dto_1 = require("../dto/update-verified-status-request.dto");
50
51
  const user_response_dto_1 = require("../dto/user-response.dto");
52
+ const auth_response_dto_1 = require("../dto/auth-response.dto");
51
53
  const auth_challenge_dto_1 = require("../dto/auth-challenge.dto");
52
54
  const respond_challenge_dto_1 = require("../dto/respond-challenge.dto");
53
55
  const get_user_by_email_dto_1 = require("../dto/get-user-by-email.dto");
@@ -323,15 +325,24 @@ class AuthService {
323
325
  // This ensures proper sequential flow: email code first, then phone code after email is verified
324
326
  // This prevents user confusion from receiving multiple codes at once
325
327
  // ============================================================================
326
- // Lifecycle Hook: afterSignup
328
+ // Lifecycle Hook: postSignup
327
329
  // ============================================================================
328
- // Execute afterSignup hook immediately after account creation (non-blocking)
329
- await this.hookRegistry.executeAfterSignup(savedUser, {
330
+ // Execute postSignup hook immediately after account creation (non-blocking)
331
+ await this.hookRegistry.executePostSignup(savedUser, {
330
332
  requiresVerification: verificationMethod !== 'none',
331
333
  signupType: 'password',
332
334
  adminSignup: false,
333
335
  });
334
336
  // ============================================================================
337
+ // refresh user data in case post signup hook has modified the user
338
+ // ============================================================================
339
+ const refreshedUser = await this.userRepository.findOne({
340
+ where: { id: savedUser.id },
341
+ });
342
+ if (refreshedUser) {
343
+ savedUser = refreshedUser;
344
+ }
345
+ // ============================================================================
335
346
  // Challenge System: Determine if user needs to complete challenges
336
347
  // ============================================================================
337
348
  const response = await this.challengeHelper.determineAuthResponse({
@@ -544,7 +555,7 @@ class AuthService {
544
555
  // Lifecycle Hook: afterSignup
545
556
  // ============================================================================
546
557
  // Execute afterSignup hook immediately after account creation (non-blocking)
547
- await this.hookRegistry.executeAfterSignup(savedUser, {
558
+ await this.hookRegistry.executePostSignup(savedUser, {
548
559
  signupType: 'password',
549
560
  adminSignup: true,
550
561
  });
@@ -725,10 +736,16 @@ class AuthService {
725
736
  // Lifecycle Hook: afterSignup
726
737
  // ============================================================================
727
738
  // Execute afterSignup hook immediately after account creation (non-blocking)
728
- await this.hookRegistry.executeAfterSignup(savedUser, {
739
+ // Extract profile picture from social metadata if available
740
+ const profilePicture = dto.socialMetadata && typeof dto.socialMetadata === 'object' && 'picture' in dto.socialMetadata
741
+ ? dto.socialMetadata.picture
742
+ : null;
743
+ await this.hookRegistry.executePostSignup(savedUser, {
729
744
  signupType: 'social',
730
745
  provider: dto.provider,
731
746
  adminSignup: true,
747
+ socialMetadata: dto.socialMetadata || null,
748
+ profilePicture,
732
749
  });
733
750
  // ============================================================================
734
751
  // Audit: Record account creation by admin (social import)
@@ -1961,19 +1978,8 @@ class AuthService {
1961
1978
  // Note: deviceToken inclusion in response body is handled by CookieTokenInterceptor
1962
1979
  // which checks route-level @TokenDelivery decorator and global config
1963
1980
  // to decide whether to set as cookie and/or strip from body
1964
- const userDto = user_response_dto_1.UserResponseDto.fromEntity(user);
1965
1981
  const authResponse = {
1966
- user: {
1967
- sub: userDto.sub,
1968
- email: userDto.email,
1969
- firstName: userDto.firstName,
1970
- lastName: userDto.lastName,
1971
- phone: userDto.phone ?? undefined,
1972
- isEmailVerified: userDto.isEmailVerified,
1973
- isPhoneVerified: userDto.isPhoneVerified ?? undefined,
1974
- socialProviders: userDto.socialProviders && userDto.socialProviders.length > 0 ? userDto.socialProviders : undefined,
1975
- hasPasswordHash: userDto.hasPasswordHash,
1976
- },
1982
+ user: (0, auth_response_dto_1.toAuthResponseUser)(user),
1977
1983
  accessToken: tokens.accessToken,
1978
1984
  refreshToken: tokens.refreshToken,
1979
1985
  accessTokenExpiresAt: accessTokenValidation.payload?.exp || 0,
@@ -3968,6 +3974,220 @@ class AuthService {
3968
3974
  userId: user.id,
3969
3975
  });
3970
3976
  }
3977
+ // ============================================================================
3978
+ // Hook: Execute user profile updated hooks
3979
+ // ============================================================================
3980
+ try {
3981
+ // Build changed fields array with old/new values
3982
+ const changedFields = [];
3983
+ // Track all fields that were in updateFields
3984
+ for (const fieldName of Object.keys(updateFields)) {
3985
+ changedFields.push({
3986
+ fieldName,
3987
+ oldValue: user[fieldName],
3988
+ newValue: updateFields[fieldName],
3989
+ });
3990
+ }
3991
+ // Get client info from ClientInfoService
3992
+ const clientInfo = this.clientInfoService.get();
3993
+ // Execute hooks (non-blocking)
3994
+ await this.hookRegistry.executeUserProfileUpdated({
3995
+ user: updatedUser,
3996
+ changedFields,
3997
+ updateSource: 'user_request',
3998
+ clientInfo: {
3999
+ ipAddress: clientInfo.ipAddress,
4000
+ userAgent: clientInfo.userAgent,
4001
+ ipCountry: clientInfo.ipCountry,
4002
+ ipCity: clientInfo.ipCity,
4003
+ },
4004
+ });
4005
+ }
4006
+ catch (hookError) {
4007
+ // Non-blocking: Log but continue
4008
+ const errorMessage = hookError instanceof Error ? hookError.message : 'Unknown error';
4009
+ this.logger?.error?.(`Failed to execute userProfileUpdated hooks: ${errorMessage}`, {
4010
+ error: hookError,
4011
+ userId: user.id,
4012
+ });
4013
+ }
4014
+ // Return user response DTO
4015
+ return user_response_dto_1.UserResponseDto.fromEntity(updatedUser);
4016
+ }
4017
+ /**
4018
+ * Update email and/or phone verification status.
4019
+ *
4020
+ * Intended for admin use cases such as migration or offline validation.
4021
+ * Updates verification status without requiring actual verification codes.
4022
+ *
4023
+ * Validation:
4024
+ * - Cannot set verified=true if email/phone doesn't exist
4025
+ * - Can set verified=false even if email/phone doesn't exist (default state)
4026
+ * - Only updates provided fields (partial update)
4027
+ *
4028
+ * Audit:
4029
+ * - Records EMAIL_VERIFIED or PHONE_VERIFIED audit events
4030
+ * - Includes performedBy from authenticated admin context
4031
+ *
4032
+ * @param dto - Request DTO containing sub and verification status flags
4033
+ * @returns Updated user object
4034
+ * @throws {NAuthException} If user not found or trying to verify non-existent email/phone
4035
+ *
4036
+ * @example
4037
+ * ```typescript
4038
+ * // Update email verification only
4039
+ * await authService.updateVerifiedStatus({
4040
+ * sub: 'user-uuid',
4041
+ * isEmailVerified: true
4042
+ * });
4043
+ *
4044
+ * // Update both email and phone verification
4045
+ * await authService.updateVerifiedStatus({
4046
+ * sub: 'user-uuid',
4047
+ * isEmailVerified: true,
4048
+ * isPhoneVerified: false
4049
+ * });
4050
+ * ```
4051
+ */
4052
+ async updateVerifiedStatus(dto) {
4053
+ // Ensure DTO is validated (supports direct usage without framework validation)
4054
+ dto = await (0, dto_validator_1.ensureValidatedDto)(update_verified_status_request_dto_1.UpdateVerifiedStatusRequestDTO, dto);
4055
+ // Find user by sub (external identifier)
4056
+ const user = (await this.userRepository.findOne({ where: { sub: dto.sub } }));
4057
+ if (!user) {
4058
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.NOT_FOUND, 'User not found');
4059
+ }
4060
+ // Validate that email exists if trying to set isEmailVerified to true
4061
+ if (dto.isEmailVerified === true && !user.email) {
4062
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.VALIDATION_FAILED, 'Cannot set email verification to true: user does not have an email address');
4063
+ }
4064
+ // Validate that phone exists if trying to set isPhoneVerified to true
4065
+ if (dto.isPhoneVerified === true && !user.phone) {
4066
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.VALIDATION_FAILED, 'Cannot set phone verification to true: user does not have a phone number');
4067
+ }
4068
+ // Prepare update object - only include fields that were provided
4069
+ const updateFields = {};
4070
+ if (dto.isEmailVerified !== undefined) {
4071
+ updateFields.isEmailVerified = dto.isEmailVerified;
4072
+ }
4073
+ if (dto.isPhoneVerified !== undefined) {
4074
+ updateFields.isPhoneVerified = dto.isPhoneVerified;
4075
+ }
4076
+ // If no fields to update, return current user
4077
+ if (Object.keys(updateFields).length === 0) {
4078
+ return user_response_dto_1.UserResponseDto.fromEntity(user);
4079
+ }
4080
+ // Update user - use internal id for database update
4081
+ await this.userRepository.update(user.id, updateFields);
4082
+ // Reload user to get updated values
4083
+ const updatedUser = (await this.userRepository.findOne({ where: { id: user.id } }));
4084
+ if (!updatedUser) {
4085
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.INTERNAL_ERROR, 'Failed to reload user after update');
4086
+ }
4087
+ // ============================================================================
4088
+ // Audit: Record verification status changes
4089
+ // ============================================================================
4090
+ if (this.auditService) {
4091
+ // Record email verification change if provided
4092
+ if (dto.isEmailVerified !== undefined) {
4093
+ try {
4094
+ await this.auditService.recordEvent({
4095
+ userId: user.id,
4096
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.EMAIL_VERIFIED,
4097
+ eventStatus: dto.isEmailVerified ? 'SUCCESS' : 'INFO',
4098
+ description: dto.isEmailVerified
4099
+ ? 'Email verification status set to verified (admin action)'
4100
+ : 'Email verification status set to unverified (admin action)',
4101
+ reason: 'admin_verification_update',
4102
+ metadata: {
4103
+ previousStatus: user.isEmailVerified,
4104
+ newStatus: dto.isEmailVerified,
4105
+ updateMethod: 'admin_direct',
4106
+ // Client info automatically included from context (performedBy auto-populated)
4107
+ },
4108
+ });
4109
+ }
4110
+ catch (auditError) {
4111
+ // Non-blocking: Log but continue
4112
+ const errorMessage = auditError instanceof Error ? auditError.message : 'Unknown error';
4113
+ this.logger?.error?.(`Failed to record EMAIL_VERIFIED audit event: ${errorMessage}`, {
4114
+ error: auditError,
4115
+ userId: user.id,
4116
+ });
4117
+ }
4118
+ }
4119
+ // Record phone verification change if provided
4120
+ if (dto.isPhoneVerified !== undefined) {
4121
+ try {
4122
+ await this.auditService.recordEvent({
4123
+ userId: user.id,
4124
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.PHONE_VERIFIED,
4125
+ eventStatus: dto.isPhoneVerified ? 'SUCCESS' : 'INFO',
4126
+ description: dto.isPhoneVerified
4127
+ ? 'Phone verification status set to verified (admin action)'
4128
+ : 'Phone verification status set to unverified (admin action)',
4129
+ reason: 'admin_verification_update',
4130
+ metadata: {
4131
+ previousStatus: user.isPhoneVerified,
4132
+ newStatus: dto.isPhoneVerified,
4133
+ updateMethod: 'admin_direct',
4134
+ // Client info automatically included from context (performedBy auto-populated)
4135
+ },
4136
+ });
4137
+ }
4138
+ catch (auditError) {
4139
+ // Non-blocking: Log but continue
4140
+ const errorMessage = auditError instanceof Error ? auditError.message : 'Unknown error';
4141
+ this.logger?.error?.(`Failed to record PHONE_VERIFIED audit event: ${errorMessage}`, {
4142
+ error: auditError,
4143
+ userId: user.id,
4144
+ });
4145
+ }
4146
+ }
4147
+ }
4148
+ // ============================================================================
4149
+ // Hook: Execute user profile updated hooks
4150
+ // ============================================================================
4151
+ try {
4152
+ // Build changed fields array with old/new values
4153
+ const changedFields = [];
4154
+ if (dto.isEmailVerified !== undefined) {
4155
+ changedFields.push({
4156
+ fieldName: 'isEmailVerified',
4157
+ oldValue: user.isEmailVerified,
4158
+ newValue: dto.isEmailVerified,
4159
+ });
4160
+ }
4161
+ if (dto.isPhoneVerified !== undefined) {
4162
+ changedFields.push({
4163
+ fieldName: 'isPhoneVerified',
4164
+ oldValue: user.isPhoneVerified,
4165
+ newValue: dto.isPhoneVerified,
4166
+ });
4167
+ }
4168
+ // Get client info from ClientInfoService
4169
+ const clientInfo = this.clientInfoService.get();
4170
+ // Execute hooks (non-blocking)
4171
+ await this.hookRegistry.executeUserProfileUpdated({
4172
+ user: updatedUser,
4173
+ changedFields,
4174
+ updateSource: 'admin_action',
4175
+ clientInfo: {
4176
+ ipAddress: clientInfo.ipAddress,
4177
+ userAgent: clientInfo.userAgent,
4178
+ ipCountry: clientInfo.ipCountry,
4179
+ ipCity: clientInfo.ipCity,
4180
+ },
4181
+ });
4182
+ }
4183
+ catch (hookError) {
4184
+ // Non-blocking: Log but continue
4185
+ const errorMessage = hookError instanceof Error ? hookError.message : 'Unknown error';
4186
+ this.logger?.error?.(`Failed to execute userProfileUpdated hooks: ${errorMessage}`, {
4187
+ error: hookError,
4188
+ userId: user.id,
4189
+ });
4190
+ }
3971
4191
  // Return user response DTO
3972
4192
  return user_response_dto_1.UserResponseDto.fromEntity(updatedUser);
3973
4193
  }