@nauth-toolkit/mfa-sms 0.1.13 → 0.1.17
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/nestjs/index.d.ts +5 -0
- package/dist/nestjs/index.d.ts.map +1 -1
- package/dist/nestjs/index.js +6 -0
- package/dist/nestjs/index.js.map +1 -1
- package/dist/nestjs/sms-mfa.module.d.ts +23 -0
- package/dist/nestjs/sms-mfa.module.d.ts.map +1 -1
- package/dist/nestjs/sms-mfa.module.js +29 -1
- package/dist/nestjs/sms-mfa.module.js.map +1 -1
- package/dist/src/dto/mfa.dto.d.ts +476 -0
- package/dist/src/dto/mfa.dto.d.ts.map +1 -1
- package/dist/src/dto/mfa.dto.js +9 -0
- package/dist/src/dto/mfa.dto.js.map +1 -1
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +6 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/sms-mfa-provider.service.d.ts +101 -1
- package/dist/src/sms-mfa-provider.service.d.ts.map +1 -1
- package/dist/src/sms-mfa-provider.service.js +154 -3
- package/dist/src/sms-mfa-provider.service.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -1,18 +1,118 @@
|
|
|
1
1
|
import { Repository } from 'typeorm';
|
|
2
2
|
import { BaseMFADevice, BaseUser, IUser, NAuthConfig, NAuthLogger, PhoneVerificationService, MFAMethod } from '@nauth-toolkit/core';
|
|
3
3
|
import { BaseMFAProviderService } from '@nauth-toolkit/core/internal';
|
|
4
|
+
/**
|
|
5
|
+
* SMS MFA Provider Service
|
|
6
|
+
*
|
|
7
|
+
* Implements SMS-based MFA method.
|
|
8
|
+
* Extends BaseMFAProviderService to provide SMS-specific functionality.
|
|
9
|
+
*
|
|
10
|
+
* This service handles:
|
|
11
|
+
* - SMS code sending during setup and authentication
|
|
12
|
+
* - SMS code verification
|
|
13
|
+
* - MFA device creation for SMS
|
|
14
|
+
*
|
|
15
|
+
* Requires PhoneVerificationService from core (available when SMS provider is configured).
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* @Module({
|
|
20
|
+
* imports: [SMSMFAModule],
|
|
21
|
+
* })
|
|
22
|
+
* export class AppModule {}
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
4
25
|
export declare class SMSMFAProviderService extends BaseMFAProviderService {
|
|
5
26
|
private readonly phoneVerificationService?;
|
|
6
27
|
readonly methodName = MFAMethod.SMS;
|
|
7
|
-
constructor(mfaDeviceRepository: Repository<BaseMFADevice>, userRepository: Repository<BaseUser>, config: NAuthConfig, logger: NAuthLogger, passwordService: unknown, phoneVerificationService?: PhoneVerificationService | undefined, challengeService?: unknown,
|
|
28
|
+
constructor(mfaDeviceRepository: Repository<BaseMFADevice>, userRepository: Repository<BaseUser>, config: NAuthConfig, logger: NAuthLogger, passwordService: unknown, phoneVerificationService?: PhoneVerificationService | undefined, challengeService?: unknown, // ChallengeService (optional)
|
|
29
|
+
auditService?: unknown, // AuthAuditService (optional)
|
|
30
|
+
clientInfoService?: unknown);
|
|
31
|
+
/**
|
|
32
|
+
* Setup SMS MFA for user
|
|
33
|
+
*
|
|
34
|
+
* Sends verification code to phone number, or auto-completes setup if phone is already verified.
|
|
35
|
+
* User must verify code to complete setup (unless phone is already verified).
|
|
36
|
+
*
|
|
37
|
+
* @param user - User setting up SMS MFA
|
|
38
|
+
* @param setupData - Setup data (must be SetupSMSMFADTO)
|
|
39
|
+
* @returns Setup result with deviceId if auto-completed, or maskedPhone if code was sent
|
|
40
|
+
* @throws {NAuthException} If SMS is not enabled or phone verification service unavailable
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* const result = await provider.setup(user, {
|
|
45
|
+
* phoneNumber: '+1234567890',
|
|
46
|
+
* deviceName: 'My Phone'
|
|
47
|
+
* });
|
|
48
|
+
* // If phone verified: { deviceId: 123, autoCompleted: true }
|
|
49
|
+
* // If phone not verified: { maskedPhone: '***-***-7890' } (SMS code sent)
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
8
52
|
setup(user: IUser, setupData?: unknown): Promise<{
|
|
9
53
|
deviceId: number;
|
|
10
54
|
autoCompleted: true;
|
|
11
55
|
} | {
|
|
12
56
|
maskedPhone: string;
|
|
13
57
|
}>;
|
|
58
|
+
/**
|
|
59
|
+
* Verify and complete SMS MFA setup
|
|
60
|
+
*
|
|
61
|
+
* Validates the SMS code and stores the device if valid.
|
|
62
|
+
* Enables MFA for user if this is their first device.
|
|
63
|
+
*
|
|
64
|
+
* **Race Condition Safety:**
|
|
65
|
+
* Device creation uses transaction with pessimistic locking to prevent duplicates.
|
|
66
|
+
* If device already exists (e.g., from concurrent request), returns existing device.
|
|
67
|
+
* Database unique constraint (userId, type) provides final safety net.
|
|
68
|
+
*
|
|
69
|
+
* @param user - User completing SMS MFA setup
|
|
70
|
+
* @param verificationData - Verification data (must be VerifySMSMFASetupDTO)
|
|
71
|
+
* @param deviceName - Optional device name override
|
|
72
|
+
* @returns MFA device ID (created or existing)
|
|
73
|
+
* @throws {NAuthException} If code is invalid
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```typescript
|
|
77
|
+
* const deviceId = await provider.verifySetup(user, {
|
|
78
|
+
* phoneNumber: '+1234567890',
|
|
79
|
+
* code: '123456'
|
|
80
|
+
* });
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
14
83
|
verifySetup(user: IUser, verificationData: unknown, deviceName?: string): Promise<number>;
|
|
84
|
+
/**
|
|
85
|
+
* Verify SMS code during authentication
|
|
86
|
+
*
|
|
87
|
+
* Validates the SMS code for an existing device.
|
|
88
|
+
*
|
|
89
|
+
* @param user - User being authenticated
|
|
90
|
+
* @param code - SMS code (string)
|
|
91
|
+
* @param deviceId - Optional device ID to verify against
|
|
92
|
+
* @returns True if verification succeeds
|
|
93
|
+
* @throws {NAuthException} If device not found or verification fails
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```typescript
|
|
97
|
+
* const isValid = await provider.verify(user, '123456');
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
15
100
|
verify(user: IUser, code: unknown, deviceId?: number): Promise<boolean>;
|
|
101
|
+
/**
|
|
102
|
+
* Send SMS code for MFA verification
|
|
103
|
+
*
|
|
104
|
+
* Called during login MFA challenge to send code to registered phone.
|
|
105
|
+
*
|
|
106
|
+
* @param user - User requesting SMS code
|
|
107
|
+
* @returns Masked phone number where code was sent
|
|
108
|
+
* @throws {NAuthException} If no SMS device registered or phone verification service unavailable
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```typescript
|
|
112
|
+
* const maskedPhone = await provider.sendChallenge(user);
|
|
113
|
+
* // Returns: '***-***-1234'
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
16
116
|
sendChallenge(user: IUser): Promise<string>;
|
|
17
117
|
}
|
|
18
118
|
//# sourceMappingURL=sms-mfa-provider.service.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sms-mfa-provider.service.d.ts","sourceRoot":"","sources":["../../src/sms-mfa-provider.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErC,OAAO,EACL,aAAa,EACb,QAAQ,EACR,KAAK,EACL,WAAW,EACX,WAAW,EAGX,wBAAwB,EACxB,SAAS,EAGV,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"sms-mfa-provider.service.d.ts","sourceRoot":"","sources":["../../src/sms-mfa-provider.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErC,OAAO,EACL,aAAa,EACb,QAAQ,EACR,KAAK,EACL,WAAW,EACX,WAAW,EAGX,wBAAwB,EACxB,SAAS,EAGV,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAGtE;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,qBAAa,qBAAsB,SAAQ,sBAAsB;IAS7D,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAC;IAR5C,QAAQ,CAAC,UAAU,iBAAiB;gBAGlC,mBAAmB,EAAE,UAAU,CAAC,aAAa,CAAC,EAC9C,cAAc,EAAE,UAAU,CAAC,QAAQ,CAAC,EACpC,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,WAAW,EACnB,eAAe,EAAE,OAAO,EACP,wBAAwB,CAAC,EAAE,wBAAwB,YAAA,EACpE,gBAAgB,CAAC,EAAE,OAAO,EAAE,8BAA8B;IAC1D,YAAY,CAAC,EAAE,OAAO,EAAE,8BAA8B;IACtD,iBAAiB,CAAC,EAAE,OAAO;IAc7B;;;;;;;;;;;;;;;;;;;;OAoBG;IACG,KAAK,CACT,IAAI,EAAE,KAAK,EACX,SAAS,CAAC,EAAE,OAAO,GAClB,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,IAAI,CAAA;KAAE,GAAG;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IAyE/E;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACG,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAwE/F;;;;;;;;;;;;;;;OAeG;IACG,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAmE7E;;;;;;;;;;;;;;OAcG;IACG,aAAa,CAAC,IAAI,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC;CA2ClD"}
|
|
@@ -1,38 +1,95 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SMSMFAProviderService = void 0;
|
|
4
|
+
// Public API imports
|
|
4
5
|
const core_1 = require("@nauth-toolkit/core");
|
|
6
|
+
// Internal API imports (for provider implementations)
|
|
5
7
|
const internal_1 = require("@nauth-toolkit/core/internal");
|
|
8
|
+
/**
|
|
9
|
+
* SMS MFA Provider Service
|
|
10
|
+
*
|
|
11
|
+
* Implements SMS-based MFA method.
|
|
12
|
+
* Extends BaseMFAProviderService to provide SMS-specific functionality.
|
|
13
|
+
*
|
|
14
|
+
* This service handles:
|
|
15
|
+
* - SMS code sending during setup and authentication
|
|
16
|
+
* - SMS code verification
|
|
17
|
+
* - MFA device creation for SMS
|
|
18
|
+
*
|
|
19
|
+
* Requires PhoneVerificationService from core (available when SMS provider is configured).
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* @Module({
|
|
24
|
+
* imports: [SMSMFAModule],
|
|
25
|
+
* })
|
|
26
|
+
* export class AppModule {}
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
6
29
|
class SMSMFAProviderService extends internal_1.BaseMFAProviderService {
|
|
7
30
|
phoneVerificationService;
|
|
8
31
|
methodName = core_1.MFAMethod.SMS;
|
|
9
|
-
constructor(mfaDeviceRepository, userRepository, config, logger, passwordService, phoneVerificationService, challengeService,
|
|
32
|
+
constructor(mfaDeviceRepository, userRepository, config, logger, passwordService, phoneVerificationService, challengeService, // ChallengeService (optional)
|
|
33
|
+
auditService, // AuthAuditService (optional)
|
|
34
|
+
clientInfoService) {
|
|
10
35
|
super(mfaDeviceRepository, userRepository, config, logger, passwordService, challengeService, auditService, clientInfoService);
|
|
11
36
|
this.phoneVerificationService = phoneVerificationService;
|
|
12
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Setup SMS MFA for user
|
|
40
|
+
*
|
|
41
|
+
* Sends verification code to phone number, or auto-completes setup if phone is already verified.
|
|
42
|
+
* User must verify code to complete setup (unless phone is already verified).
|
|
43
|
+
*
|
|
44
|
+
* @param user - User setting up SMS MFA
|
|
45
|
+
* @param setupData - Setup data (must be SetupSMSMFADTO)
|
|
46
|
+
* @returns Setup result with deviceId if auto-completed, or maskedPhone if code was sent
|
|
47
|
+
* @throws {NAuthException} If SMS is not enabled or phone verification service unavailable
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* const result = await provider.setup(user, {
|
|
52
|
+
* phoneNumber: '+1234567890',
|
|
53
|
+
* deviceName: 'My Phone'
|
|
54
|
+
* });
|
|
55
|
+
* // If phone verified: { deviceId: 123, autoCompleted: true }
|
|
56
|
+
* // If phone not verified: { maskedPhone: '***-***-7890' } (SMS code sent)
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
13
59
|
async setup(user, setupData) {
|
|
14
60
|
this.logger?.log?.(`Setting up SMS MFA for user: ${user.sub}`);
|
|
61
|
+
// Check if SMS is allowed
|
|
15
62
|
if (!this.isMethodAllowed()) {
|
|
16
63
|
throw new core_1.NAuthException(core_1.AuthErrorCode.VALIDATION_FAILED, 'SMS MFA is not enabled', { feature: 'sms-mfa' });
|
|
17
64
|
}
|
|
18
65
|
const dto = setupData;
|
|
19
66
|
const userEntity = user;
|
|
20
67
|
const isPhoneVerified = userEntity.isPhoneVerified || false;
|
|
68
|
+
// Get phone number from setupData or user object
|
|
21
69
|
const phoneNumber = dto?.phoneNumber || userEntity.phone;
|
|
22
70
|
if (!phoneNumber) {
|
|
23
71
|
throw new core_1.NAuthException(core_1.AuthErrorCode.PHONE_REQUIRED, 'Phone number is required for SMS MFA setup. Please provide a phone number.');
|
|
24
72
|
}
|
|
73
|
+
// ============================================================================
|
|
74
|
+
// Special case: If phone is already verified, auto-complete MFA setup
|
|
75
|
+
// No SMS code needed - create device directly
|
|
76
|
+
// ============================================================================
|
|
25
77
|
if (isPhoneVerified) {
|
|
26
78
|
this.logger?.log?.(`Phone already verified for user ${user.sub}, auto-completing SMS MFA setup`);
|
|
79
|
+
// Auto-create MFA device without code verification
|
|
27
80
|
const deviceId = await this.verifySetup(user, {
|
|
28
81
|
phoneNumber,
|
|
29
|
-
code: '',
|
|
82
|
+
code: '', // Code not needed when phone is verified
|
|
30
83
|
}, dto?.deviceName);
|
|
31
84
|
return { deviceId, autoCompleted: true };
|
|
32
85
|
}
|
|
86
|
+
// Phone not verified - send SMS verification code
|
|
87
|
+
// Check if phone verification service is available
|
|
33
88
|
if (!this.phoneVerificationService) {
|
|
34
89
|
throw new core_1.NAuthException(core_1.AuthErrorCode.VALIDATION_FAILED, 'Phone verification service is not available. SMS provider must be configured.');
|
|
35
90
|
}
|
|
91
|
+
// Send SMS verification code (phone not verified, so code is required)
|
|
92
|
+
// Link verification token to challenge session if provided in setupData
|
|
36
93
|
const sendDto = new core_1.SendVerificationSMSDTO();
|
|
37
94
|
sendDto.sub = user.sub;
|
|
38
95
|
const setupDataWithSession = setupData;
|
|
@@ -46,8 +103,34 @@ class SMSMFAProviderService extends internal_1.BaseMFAProviderService {
|
|
|
46
103
|
await this.phoneVerificationService.sendVerificationSMS(sendDto);
|
|
47
104
|
const maskedPhone = this.maskPhone(phoneNumber);
|
|
48
105
|
this.logger?.log?.(`SMS MFA code sent to: ${maskedPhone}`);
|
|
106
|
+
// Return masked phone for frontend display
|
|
49
107
|
return { maskedPhone };
|
|
50
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Verify and complete SMS MFA setup
|
|
111
|
+
*
|
|
112
|
+
* Validates the SMS code and stores the device if valid.
|
|
113
|
+
* Enables MFA for user if this is their first device.
|
|
114
|
+
*
|
|
115
|
+
* **Race Condition Safety:**
|
|
116
|
+
* Device creation uses transaction with pessimistic locking to prevent duplicates.
|
|
117
|
+
* If device already exists (e.g., from concurrent request), returns existing device.
|
|
118
|
+
* Database unique constraint (userId, type) provides final safety net.
|
|
119
|
+
*
|
|
120
|
+
* @param user - User completing SMS MFA setup
|
|
121
|
+
* @param verificationData - Verification data (must be VerifySMSMFASetupDTO)
|
|
122
|
+
* @param deviceName - Optional device name override
|
|
123
|
+
* @returns MFA device ID (created or existing)
|
|
124
|
+
* @throws {NAuthException} If code is invalid
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```typescript
|
|
128
|
+
* const deviceId = await provider.verifySetup(user, {
|
|
129
|
+
* phoneNumber: '+1234567890',
|
|
130
|
+
* code: '123456'
|
|
131
|
+
* });
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
51
134
|
async verifySetup(user, verificationData, deviceName) {
|
|
52
135
|
this.logger?.log?.(`Verifying SMS MFA setup for user: ${user.sub}`);
|
|
53
136
|
const dto = verificationData;
|
|
@@ -55,7 +138,12 @@ class SMSMFAProviderService extends internal_1.BaseMFAProviderService {
|
|
|
55
138
|
const userId = userEntity.id;
|
|
56
139
|
const userMfaEnabled = userEntity.mfaEnabled || false;
|
|
57
140
|
const isPhoneVerified = userEntity.isPhoneVerified || false;
|
|
141
|
+
// ============================================================================
|
|
142
|
+
// Special case: If phone is already verified, skip code verification
|
|
143
|
+
// This improves UX by avoiding redundant SMS verification after phone verification
|
|
144
|
+
// ============================================================================
|
|
58
145
|
if (!isPhoneVerified) {
|
|
146
|
+
// Phone not verified - verify SMS code (this will also mark phone as verified)
|
|
59
147
|
if (!this.phoneVerificationService) {
|
|
60
148
|
throw new core_1.NAuthException(core_1.AuthErrorCode.VALIDATION_FAILED, 'Phone verification service is not available. SMS provider must be configured.');
|
|
61
149
|
}
|
|
@@ -63,6 +151,7 @@ class SMSMFAProviderService extends internal_1.BaseMFAProviderService {
|
|
|
63
151
|
throw new core_1.NAuthException(core_1.AuthErrorCode.VALIDATION_FAILED, 'Verification code is required');
|
|
64
152
|
}
|
|
65
153
|
try {
|
|
154
|
+
// Verify phone with code - this will mark phone as verified in the database
|
|
66
155
|
const verifyDto = new core_1.VerifyPhoneWithCodeBySubDTO();
|
|
67
156
|
verifyDto.sub = user.sub;
|
|
68
157
|
verifyDto.code = dto.code;
|
|
@@ -70,26 +159,52 @@ class SMSMFAProviderService extends internal_1.BaseMFAProviderService {
|
|
|
70
159
|
this.logger?.log?.(`Phone verified during SMS MFA setup for user ${user.sub} - phone is now marked as verified in database`);
|
|
71
160
|
}
|
|
72
161
|
catch (error) {
|
|
162
|
+
// Re-throw with more specific error message
|
|
73
163
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
74
164
|
this.logger?.error?.(`Failed to verify phone during SMS MFA setup for user ${user.sub}: ${errorMessage}`, error);
|
|
75
165
|
throw new core_1.NAuthException(core_1.AuthErrorCode.VERIFICATION_CODE_INVALID, 'Invalid SMS code');
|
|
76
166
|
}
|
|
77
167
|
}
|
|
78
168
|
else {
|
|
169
|
+
// Phone already verified - skip code verification
|
|
79
170
|
this.logger?.log?.(`Phone already verified for user ${user.sub}, skipping SMS code verification during MFA setup`);
|
|
80
171
|
}
|
|
172
|
+
// ============================================================================
|
|
173
|
+
// Create MFA device (transaction-safe with duplicate prevention)
|
|
174
|
+
// ============================================================================
|
|
175
|
+
// createDevice() uses pessimistic locking to prevent race conditions
|
|
176
|
+
// If device already exists, returns existing device instead of creating duplicate
|
|
177
|
+
// Database unique constraint (userId, type) provides additional safety
|
|
81
178
|
const device = await this.createDevice(userId, {
|
|
82
179
|
name: deviceName || 'SMS Phone',
|
|
83
180
|
phoneNumber: dto.phoneNumber,
|
|
84
181
|
isActive: true,
|
|
85
|
-
isPrimary: !userMfaEnabled,
|
|
182
|
+
isPrimary: !userMfaEnabled, // First device becomes primary
|
|
86
183
|
});
|
|
184
|
+
// Enable MFA if not already enabled
|
|
87
185
|
await this.enableMFAForUser(user);
|
|
88
186
|
this.logger?.log?.(`SMS MFA setup completed for user: ${user.sub}`);
|
|
89
187
|
return device.id;
|
|
90
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* Verify SMS code during authentication
|
|
191
|
+
*
|
|
192
|
+
* Validates the SMS code for an existing device.
|
|
193
|
+
*
|
|
194
|
+
* @param user - User being authenticated
|
|
195
|
+
* @param code - SMS code (string)
|
|
196
|
+
* @param deviceId - Optional device ID to verify against
|
|
197
|
+
* @returns True if verification succeeds
|
|
198
|
+
* @throws {NAuthException} If device not found or verification fails
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* ```typescript
|
|
202
|
+
* const isValid = await provider.verify(user, '123456');
|
|
203
|
+
* ```
|
|
204
|
+
*/
|
|
91
205
|
async verify(user, code, deviceId) {
|
|
92
206
|
this.logger?.log?.(`Verifying SMS code for user: ${user.sub}`);
|
|
207
|
+
// Check if phone verification service is available
|
|
93
208
|
if (!this.phoneVerificationService) {
|
|
94
209
|
this.logger?.warn?.('SMS verification attempted but phone verification service is not available');
|
|
95
210
|
return false;
|
|
@@ -99,11 +214,21 @@ class SMSMFAProviderService extends internal_1.BaseMFAProviderService {
|
|
|
99
214
|
this.logger?.warn?.('Invalid SMS code format');
|
|
100
215
|
return false;
|
|
101
216
|
}
|
|
217
|
+
// Get user entity
|
|
102
218
|
const userEntity = user;
|
|
103
219
|
const userId = userEntity.id;
|
|
104
220
|
const isPhoneVerified = userEntity.isPhoneVerified || false;
|
|
221
|
+
// Find device (optional - SMS verification uses phone verification service)
|
|
105
222
|
const device = deviceId ? await this.findDevice(userId, deviceId) : null;
|
|
223
|
+
// Verify SMS code using phone verification service
|
|
106
224
|
try {
|
|
225
|
+
// ============================================================================
|
|
226
|
+
// MFA Verification: Verify SMS code for authentication
|
|
227
|
+
// Note: verifyPhoneWithCodeBySub only marks phone as verified if not already verified.
|
|
228
|
+
// This avoids unnecessary database writes and updatedAt timestamp changes.
|
|
229
|
+
// 1. During MFA setup (verifySetup): We check isPhoneVerified first, so it only marks if not verified
|
|
230
|
+
// 2. During MFA login (verify): If phone is already verified, no user table update occurs
|
|
231
|
+
// ============================================================================
|
|
107
232
|
const verifyDto = new core_1.VerifyPhoneWithCodeBySubDTO();
|
|
108
233
|
verifyDto.sub = user.sub;
|
|
109
234
|
verifyDto.code = smsCode;
|
|
@@ -114,6 +239,7 @@ class SMSMFAProviderService extends internal_1.BaseMFAProviderService {
|
|
|
114
239
|
else {
|
|
115
240
|
this.logger?.log?.(`SMS code verified for MFA (phone already verified) for user: ${user.sub}`);
|
|
116
241
|
}
|
|
242
|
+
// Update device usage if device found
|
|
117
243
|
if (device) {
|
|
118
244
|
await this.updateDeviceUsage(device.id);
|
|
119
245
|
}
|
|
@@ -121,30 +247,55 @@ class SMSMFAProviderService extends internal_1.BaseMFAProviderService {
|
|
|
121
247
|
return true;
|
|
122
248
|
}
|
|
123
249
|
catch (error) {
|
|
250
|
+
// Re-throw NAuthException to preserve specific error codes (e.g., VERIFY_TOO_MANY_ATTEMPTS)
|
|
251
|
+
// This allows the calling code to handle rate limiting and other specific errors correctly
|
|
124
252
|
if (error instanceof core_1.NAuthException) {
|
|
125
253
|
throw error;
|
|
126
254
|
}
|
|
255
|
+
// For unexpected errors, log and return false (generic failure)
|
|
127
256
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
128
257
|
const errorCode = error?.code || 'UNKNOWN';
|
|
129
258
|
this.logger?.warn?.(`SMS code verification failed for user: ${user.sub}, code: ${smsCode}, error: ${errorCode} - ${errorMessage}`);
|
|
130
259
|
return false;
|
|
131
260
|
}
|
|
132
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* Send SMS code for MFA verification
|
|
264
|
+
*
|
|
265
|
+
* Called during login MFA challenge to send code to registered phone.
|
|
266
|
+
*
|
|
267
|
+
* @param user - User requesting SMS code
|
|
268
|
+
* @returns Masked phone number where code was sent
|
|
269
|
+
* @throws {NAuthException} If no SMS device registered or phone verification service unavailable
|
|
270
|
+
*
|
|
271
|
+
* @example
|
|
272
|
+
* ```typescript
|
|
273
|
+
* const maskedPhone = await provider.sendChallenge(user);
|
|
274
|
+
* // Returns: '***-***-1234'
|
|
275
|
+
* ```
|
|
276
|
+
*/
|
|
133
277
|
async sendChallenge(user) {
|
|
134
278
|
this.logger?.log?.(`Sending SMS MFA code for user: ${user.sub}`);
|
|
279
|
+
// Get user entity
|
|
135
280
|
const userEntity = user;
|
|
136
281
|
const userId = userEntity.id;
|
|
282
|
+
// Find active SMS device
|
|
137
283
|
const device = await this.findDevice(userId);
|
|
138
284
|
if (!device) {
|
|
139
285
|
throw new core_1.NAuthException(core_1.AuthErrorCode.NOT_FOUND, 'No SMS device registered', { deviceType: 'sms' });
|
|
140
286
|
}
|
|
287
|
+
// Get phone number from device or fall back to user phone
|
|
288
|
+
// Fallback handles legacy devices where phoneNumber field might be null
|
|
141
289
|
const phoneNumber = device.phoneNumber || userEntity.phone;
|
|
142
290
|
if (!phoneNumber) {
|
|
143
291
|
throw new core_1.NAuthException(core_1.AuthErrorCode.VALIDATION_FAILED, 'No phone number found for SMS MFA. Please update your profile or re-setup SMS MFA.', { deviceType: 'sms' });
|
|
144
292
|
}
|
|
293
|
+
// Check if phone verification service is available
|
|
145
294
|
if (!this.phoneVerificationService) {
|
|
146
295
|
throw new core_1.NAuthException(core_1.AuthErrorCode.VALIDATION_FAILED, 'Phone verification service is not available. SMS provider must be configured.');
|
|
147
296
|
}
|
|
297
|
+
// Send SMS code for MFA verification
|
|
298
|
+
// Always send codes for MFA verification (even if phone is already verified)
|
|
148
299
|
const sendDto = new core_1.SendVerificationSMSDTO();
|
|
149
300
|
sendDto.sub = user.sub;
|
|
150
301
|
sendDto.skipAlreadyVerifiedCheck = true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sms-mfa-provider.service.js","sourceRoot":"","sources":["../../src/sms-mfa-provider.service.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"sms-mfa-provider.service.js","sourceRoot":"","sources":["../../src/sms-mfa-provider.service.ts"],"names":[],"mappings":"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"}
|