@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.
- package/dist/dto/auth-response.dto.d.ts +60 -54
- package/dist/dto/auth-response.dto.d.ts.map +1 -1
- package/dist/dto/auth-response.dto.js +23 -11
- package/dist/dto/auth-response.dto.js.map +1 -1
- package/dist/dto/index.d.ts +1 -0
- package/dist/dto/index.d.ts.map +1 -1
- package/dist/dto/index.js +1 -0
- package/dist/dto/index.js.map +1 -1
- package/dist/dto/update-verified-status-request.dto.d.ts +70 -0
- package/dist/dto/update-verified-status-request.dto.d.ts.map +1 -0
- package/dist/dto/update-verified-status-request.dto.js +107 -0
- package/dist/dto/update-verified-status-request.dto.js.map +1 -0
- package/dist/interfaces/config.interface.d.ts +23 -6
- package/dist/interfaces/config.interface.d.ts.map +1 -1
- package/dist/interfaces/hooks.interface.d.ts +152 -117
- package/dist/interfaces/hooks.interface.d.ts.map +1 -1
- package/dist/services/adaptive-mfa-decision.service.d.ts.map +1 -1
- package/dist/services/adaptive-mfa-decision.service.js +2 -2
- package/dist/services/adaptive-mfa-decision.service.js.map +1 -1
- package/dist/services/auth-challenge-helper.service.d.ts.map +1 -1
- package/dist/services/auth-challenge-helper.service.js +2 -11
- package/dist/services/auth-challenge-helper.service.js.map +1 -1
- package/dist/services/auth.service.d.ts +37 -0
- package/dist/services/auth.service.d.ts.map +1 -1
- package/dist/services/auth.service.js +237 -17
- package/dist/services/auth.service.js.map +1 -1
- package/dist/services/email-verification.service.d.ts +3 -1
- package/dist/services/email-verification.service.d.ts.map +1 -1
- package/dist/services/email-verification.service.js +77 -1
- package/dist/services/email-verification.service.js.map +1 -1
- package/dist/services/hook-registry.service.d.ts +34 -63
- package/dist/services/hook-registry.service.d.ts.map +1 -1
- package/dist/services/hook-registry.service.js +53 -66
- package/dist/services/hook-registry.service.js.map +1 -1
- package/dist/services/phone-verification.service.d.ts +3 -1
- package/dist/services/phone-verification.service.d.ts.map +1 -1
- package/dist/services/phone-verification.service.js +80 -1
- package/dist/services/phone-verification.service.js.map +1 -1
- package/dist/services/social-auth-base.service.d.ts +2 -1
- package/dist/services/social-auth-base.service.d.ts.map +1 -1
- package/dist/services/social-auth-base.service.js +6 -3
- package/dist/services/social-auth-base.service.js.map +1 -1
- package/dist/utils/setup/init-services.d.ts.map +1 -1
- package/dist/utils/setup/init-services.js +2 -2
- package/dist/utils/setup/init-services.js.map +1 -1
- 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:
|
|
328
|
+
// Lifecycle Hook: postSignup
|
|
327
329
|
// ============================================================================
|
|
328
|
-
// Execute
|
|
329
|
-
await this.hookRegistry.
|
|
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.
|
|
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
|
-
|
|
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
|
}
|