@nauth-toolkit/mfa-passkey 0.1.14 → 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/passkey-mfa.module.d.ts +22 -0
- package/dist/nestjs/passkey-mfa.module.d.ts.map +1 -1
- package/dist/nestjs/passkey-mfa.module.js +29 -1
- package/dist/nestjs/passkey-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/passkey-mfa-provider.service.d.ts +103 -1
- package/dist/src/passkey-mfa-provider.service.d.ts.map +1 -1
- package/dist/src/passkey-mfa-provider.service.js +133 -2
- package/dist/src/passkey-mfa-provider.service.js.map +1 -1
- package/dist/src/passkey.service.d.ts +62 -0
- package/dist/src/passkey.service.d.ts.map +1 -1
- package/dist/src/passkey.service.js +83 -2
- package/dist/src/passkey.service.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -3,13 +3,115 @@ import { BaseMFADevice, BaseUser, IUser, NAuthConfig, NAuthLogger, MFAMethod } f
|
|
|
3
3
|
import { BaseMFAProviderService } from '@nauth-toolkit/core/internal';
|
|
4
4
|
import { PasskeyService } from './passkey.service';
|
|
5
5
|
import { SetupPasskeyResponseDTO, GetPasskeyChallengeResponseDTO } from './dto/mfa.dto';
|
|
6
|
+
/**
|
|
7
|
+
* Passkey MFA Provider Service
|
|
8
|
+
*
|
|
9
|
+
* Implements Passkey/WebAuthn as a Multi-Factor Authentication (MFA) method.
|
|
10
|
+
* Extends BaseMFAProviderService to provide Passkey-specific functionality.
|
|
11
|
+
*
|
|
12
|
+
* Note: Passkeys are currently implemented exclusively as MFA (secondary factor
|
|
13
|
+
* after password authentication). Users can enroll multiple passkey devices
|
|
14
|
+
* (e.g., phone biometric, browser-based, hardware security keys) for redundancy.
|
|
15
|
+
* Passwordless passkey login is planned for a future release.
|
|
16
|
+
*
|
|
17
|
+
* This service handles:
|
|
18
|
+
* - WebAuthn registration (passkey setup) - supports multiple devices per user
|
|
19
|
+
* - WebAuthn authentication (passkey verification) - works with any registered device
|
|
20
|
+
* - Challenge generation for setup and authentication
|
|
21
|
+
* - MFA device creation for Passkeys
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* @Module({
|
|
26
|
+
* imports: [PasskeyMFAModule],
|
|
27
|
+
* })
|
|
28
|
+
* export class AppModule {}
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
6
31
|
export declare class PasskeyMFAProviderService extends BaseMFAProviderService {
|
|
7
32
|
private readonly passkeyService;
|
|
8
33
|
readonly methodName = MFAMethod.PASSKEY;
|
|
9
|
-
constructor(mfaDeviceRepository: Repository<BaseMFADevice>, userRepository: Repository<BaseUser>, config: NAuthConfig, logger: NAuthLogger, passwordService: unknown, passkeyService: PasskeyService, challengeService?: unknown,
|
|
34
|
+
constructor(mfaDeviceRepository: Repository<BaseMFADevice>, userRepository: Repository<BaseUser>, config: NAuthConfig, logger: NAuthLogger, passwordService: unknown, passkeyService: PasskeyService, challengeService?: unknown, // ChallengeService (optional)
|
|
35
|
+
auditService?: unknown, // AuthAuditService (optional)
|
|
36
|
+
clientInfoService?: unknown);
|
|
37
|
+
/**
|
|
38
|
+
* Setup Passkey for user
|
|
39
|
+
*
|
|
40
|
+
* Generates WebAuthn registration options.
|
|
41
|
+
* Client must pass these to navigator.credentials.create().
|
|
42
|
+
*
|
|
43
|
+
* @param user - User setting up Passkey
|
|
44
|
+
* @param _setupData - Not used for Passkey
|
|
45
|
+
* @returns WebAuthn registration options
|
|
46
|
+
* @throws {NAuthException} If Passkey is not enabled
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* const options = await provider.setup(user);
|
|
51
|
+
* // Client calls navigator.credentials.create({ publicKey: options.options })
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
10
54
|
setup(user: IUser, _setupData?: unknown): Promise<SetupPasskeyResponseDTO>;
|
|
55
|
+
/**
|
|
56
|
+
* Verify and complete Passkey setup
|
|
57
|
+
*
|
|
58
|
+
* Validates the WebAuthn credential and stores the device if valid.
|
|
59
|
+
*
|
|
60
|
+
* **Race Condition Safety:**
|
|
61
|
+
* Device creation uses transaction with pessimistic locking to prevent duplicates.
|
|
62
|
+
* If device already exists (e.g., from concurrent request), returns existing device.
|
|
63
|
+
* Database unique constraint (userId, type) provides final safety net.
|
|
64
|
+
*
|
|
65
|
+
* @param user - User completing Passkey setup
|
|
66
|
+
* @param verificationData - Verification data (must be { credential: VerifyPasskeySetupDTO, expectedChallenge: string })
|
|
67
|
+
* @param deviceName - Optional device name override
|
|
68
|
+
* @returns MFA device ID (created or existing)
|
|
69
|
+
* @throws {NAuthException} If verification fails
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* const deviceId = await provider.verifySetup(user, {
|
|
74
|
+
* credential: { credential: webAuthnCredential, expectedChallenge: challenge }
|
|
75
|
+
* }, 'iPhone 15 Pro');
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
11
78
|
verifySetup(user: IUser, verificationData: unknown, deviceName?: string): Promise<number>;
|
|
79
|
+
/**
|
|
80
|
+
* Verify Passkey during authentication
|
|
81
|
+
*
|
|
82
|
+
* Validates WebAuthn assertion against stored public key.
|
|
83
|
+
*
|
|
84
|
+
* @param user - User being authenticated
|
|
85
|
+
* @param code - Verification data (must be { credential: AuthenticationResponseJSON, expectedChallenge: string })
|
|
86
|
+
* @param deviceId - Optional device ID to verify against
|
|
87
|
+
* @returns True if verification succeeds
|
|
88
|
+
* @throws {NAuthException} If device not found or verification fails
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```typescript
|
|
92
|
+
* const isValid = await provider.verify(user, {
|
|
93
|
+
* credential: webAuthnCredential,
|
|
94
|
+
* expectedChallenge: challenge
|
|
95
|
+
* });
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
12
98
|
verify(user: IUser, code: unknown, deviceId?: number): Promise<boolean>;
|
|
99
|
+
/**
|
|
100
|
+
* Send Passkey challenge for MFA verification
|
|
101
|
+
*
|
|
102
|
+
* Generates WebAuthn authentication options for login.
|
|
103
|
+
* Client must pass these to navigator.credentials.get().
|
|
104
|
+
*
|
|
105
|
+
* @param user - User requesting Passkey challenge
|
|
106
|
+
* @returns WebAuthn authentication options
|
|
107
|
+
* @throws {NAuthException} If no Passkey device registered
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```typescript
|
|
111
|
+
* const options = await provider.sendChallenge(user);
|
|
112
|
+
* // Client calls navigator.credentials.get({ publicKey: options.options })
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
13
115
|
sendChallenge(user: IUser): Promise<GetPasskeyChallengeResponseDTO>;
|
|
14
116
|
}
|
|
15
117
|
//# sourceMappingURL=passkey-mfa-provider.service.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passkey-mfa-provider.service.d.ts","sourceRoot":"","sources":["../../src/passkey-mfa-provider.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErC,OAAO,EACL,aAAa,EACb,QAAQ,EACR,KAAK,EAEL,WAAW,EACX,WAAW,EAGX,SAAS,EACV,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,uBAAuB,EAAyB,8BAA8B,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"passkey-mfa-provider.service.d.ts","sourceRoot":"","sources":["../../src/passkey-mfa-provider.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErC,OAAO,EACL,aAAa,EACb,QAAQ,EACR,KAAK,EAEL,WAAW,EACX,WAAW,EAGX,SAAS,EACV,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,uBAAuB,EAAyB,8BAA8B,EAAE,MAAM,eAAe,CAAC;AAG/G;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,qBAAa,yBAA0B,SAAQ,sBAAsB;IASjE,OAAO,CAAC,QAAQ,CAAC,cAAc;IARjC,QAAQ,CAAC,UAAU,qBAAqB;gBAGtC,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,cAAc,EAAE,cAAc,EAC/C,gBAAgB,CAAC,EAAE,OAAO,EAAE,8BAA8B;IAC1D,YAAY,CAAC,EAAE,OAAO,EAAE,8BAA8B;IACtD,iBAAiB,CAAC,EAAE,OAAO;IAc7B;;;;;;;;;;;;;;;;OAgBG;IACG,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,UAAU,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,uBAAuB,CAAC;IA0BhF;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACG,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAsG/F;;;;;;;;;;;;;;;;;;OAkBG;IACG,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAgE7E;;;;;;;;;;;;;;;OAeG;IACG,aAAa,CAAC,IAAI,EAAE,KAAK,GAAG,OAAO,CAAC,8BAA8B,CAAC;CAoB1E"}
|
|
@@ -1,29 +1,103 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.PasskeyMFAProviderService = 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
|
+
* Passkey MFA Provider Service
|
|
10
|
+
*
|
|
11
|
+
* Implements Passkey/WebAuthn as a Multi-Factor Authentication (MFA) method.
|
|
12
|
+
* Extends BaseMFAProviderService to provide Passkey-specific functionality.
|
|
13
|
+
*
|
|
14
|
+
* Note: Passkeys are currently implemented exclusively as MFA (secondary factor
|
|
15
|
+
* after password authentication). Users can enroll multiple passkey devices
|
|
16
|
+
* (e.g., phone biometric, browser-based, hardware security keys) for redundancy.
|
|
17
|
+
* Passwordless passkey login is planned for a future release.
|
|
18
|
+
*
|
|
19
|
+
* This service handles:
|
|
20
|
+
* - WebAuthn registration (passkey setup) - supports multiple devices per user
|
|
21
|
+
* - WebAuthn authentication (passkey verification) - works with any registered device
|
|
22
|
+
* - Challenge generation for setup and authentication
|
|
23
|
+
* - MFA device creation for Passkeys
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* @Module({
|
|
28
|
+
* imports: [PasskeyMFAModule],
|
|
29
|
+
* })
|
|
30
|
+
* export class AppModule {}
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
6
33
|
class PasskeyMFAProviderService extends internal_1.BaseMFAProviderService {
|
|
7
34
|
passkeyService;
|
|
8
35
|
methodName = core_1.MFAMethod.PASSKEY;
|
|
9
|
-
constructor(mfaDeviceRepository, userRepository, config, logger, passwordService, passkeyService, challengeService,
|
|
36
|
+
constructor(mfaDeviceRepository, userRepository, config, logger, passwordService, passkeyService, challengeService, // ChallengeService (optional)
|
|
37
|
+
auditService, // AuthAuditService (optional)
|
|
38
|
+
clientInfoService) {
|
|
10
39
|
super(mfaDeviceRepository, userRepository, config, logger, passwordService, challengeService, auditService, clientInfoService);
|
|
11
40
|
this.passkeyService = passkeyService;
|
|
12
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Setup Passkey for user
|
|
44
|
+
*
|
|
45
|
+
* Generates WebAuthn registration options.
|
|
46
|
+
* Client must pass these to navigator.credentials.create().
|
|
47
|
+
*
|
|
48
|
+
* @param user - User setting up Passkey
|
|
49
|
+
* @param _setupData - Not used for Passkey
|
|
50
|
+
* @returns WebAuthn registration options
|
|
51
|
+
* @throws {NAuthException} If Passkey is not enabled
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```typescript
|
|
55
|
+
* const options = await provider.setup(user);
|
|
56
|
+
* // Client calls navigator.credentials.create({ publicKey: options.options })
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
13
59
|
async setup(user, _setupData) {
|
|
14
60
|
this.logger?.log?.(`Setting up passkey for user: ${user.sub}`);
|
|
61
|
+
// Check if passkey is allowed
|
|
15
62
|
if (!this.isMethodAllowed()) {
|
|
16
63
|
throw new core_1.NAuthException(core_1.AuthErrorCode.VALIDATION_FAILED, 'Passkey is not enabled', { feature: 'passkey' });
|
|
17
64
|
}
|
|
65
|
+
// Get existing passkeys to exclude
|
|
18
66
|
const userEntity = user;
|
|
19
67
|
const userId = userEntity.id;
|
|
20
68
|
const existingDevices = await this.getUserDevices(userId);
|
|
69
|
+
// Generate registration options
|
|
21
70
|
const options = await this.passkeyService.generateRegistrationOptions(user.sub, user.email, `${userEntity.firstName || ''} ${userEntity.lastName || ''}`.trim() || user.email, existingDevices);
|
|
22
71
|
this.logger?.log?.(`Passkey registration options generated for user: ${user.sub}`);
|
|
23
72
|
return options;
|
|
24
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Verify and complete Passkey setup
|
|
76
|
+
*
|
|
77
|
+
* Validates the WebAuthn credential and stores the device if valid.
|
|
78
|
+
*
|
|
79
|
+
* **Race Condition Safety:**
|
|
80
|
+
* Device creation uses transaction with pessimistic locking to prevent duplicates.
|
|
81
|
+
* If device already exists (e.g., from concurrent request), returns existing device.
|
|
82
|
+
* Database unique constraint (userId, type) provides final safety net.
|
|
83
|
+
*
|
|
84
|
+
* @param user - User completing Passkey setup
|
|
85
|
+
* @param verificationData - Verification data (must be { credential: VerifyPasskeySetupDTO, expectedChallenge: string })
|
|
86
|
+
* @param deviceName - Optional device name override
|
|
87
|
+
* @returns MFA device ID (created or existing)
|
|
88
|
+
* @throws {NAuthException} If verification fails
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```typescript
|
|
92
|
+
* const deviceId = await provider.verifySetup(user, {
|
|
93
|
+
* credential: { credential: webAuthnCredential, expectedChallenge: challenge }
|
|
94
|
+
* }, 'iPhone 15 Pro');
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
25
97
|
async verifySetup(user, verificationData, deviceName) {
|
|
26
98
|
this.logger?.log?.(`Verifying passkey setup for user: ${user.sub}`);
|
|
99
|
+
// Extract credential and challenge from verificationData
|
|
100
|
+
// verificationData should be { credential: VerifyPasskeySetupDTO, expectedChallenge: string }
|
|
27
101
|
let dto;
|
|
28
102
|
let expectedChallenge;
|
|
29
103
|
let transports;
|
|
@@ -31,13 +105,16 @@ class PasskeyMFAProviderService extends internal_1.BaseMFAProviderService {
|
|
|
31
105
|
typeof verificationData === 'object' &&
|
|
32
106
|
'credential' in verificationData &&
|
|
33
107
|
'expectedChallenge' in verificationData) {
|
|
108
|
+
// Wrapped with challenge and optional transports
|
|
34
109
|
const wrapped = verificationData;
|
|
35
110
|
dto = wrapped.credential;
|
|
36
111
|
expectedChallenge = wrapped.expectedChallenge;
|
|
37
112
|
transports = wrapped.transports;
|
|
113
|
+
// Validate challenge is present
|
|
38
114
|
if (!expectedChallenge || typeof expectedChallenge !== 'string' || expectedChallenge.trim().length === 0) {
|
|
39
115
|
throw new core_1.NAuthException(core_1.AuthErrorCode.VALIDATION_FAILED, 'Expected challenge is required and must be a non-empty string');
|
|
40
116
|
}
|
|
117
|
+
// Validate credential structure
|
|
41
118
|
if (!dto.credential || typeof dto.credential !== 'object') {
|
|
42
119
|
throw new core_1.NAuthException(core_1.AuthErrorCode.VALIDATION_FAILED, 'Credential is required and must be an object');
|
|
43
120
|
}
|
|
@@ -53,13 +130,23 @@ class PasskeyMFAProviderService extends internal_1.BaseMFAProviderService {
|
|
|
53
130
|
else {
|
|
54
131
|
throw new core_1.NAuthException(core_1.AuthErrorCode.VALIDATION_FAILED, 'Passkey verification requires { credential, expectedChallenge }. Use MFAService.verifySetup or provide both.');
|
|
55
132
|
}
|
|
133
|
+
// Verify passkey registration with transports (if provided)
|
|
134
|
+
// VerifyPasskeySetupDTO.credential should match RegistrationResponseJSON
|
|
135
|
+
// Transports help identify authenticator type (platform vs cross-platform)
|
|
56
136
|
const verified = await this.passkeyService.verifyRegistration(dto.credential, expectedChallenge, transports);
|
|
57
137
|
if (!verified.verified) {
|
|
58
138
|
throw new core_1.NAuthException(core_1.AuthErrorCode.VALIDATION_FAILED, 'Passkey verification failed');
|
|
59
139
|
}
|
|
140
|
+
// Get user entity for MFA status check
|
|
60
141
|
const userEntity = user;
|
|
61
142
|
const userId = userEntity.id;
|
|
62
143
|
const userMfaEnabled = userEntity.mfaEnabled || false;
|
|
144
|
+
// ============================================================================
|
|
145
|
+
// Create MFA device (transaction-safe with duplicate prevention)
|
|
146
|
+
// ============================================================================
|
|
147
|
+
// createDevice() uses pessimistic locking to prevent race conditions
|
|
148
|
+
// If device already exists, returns existing device instead of creating duplicate
|
|
149
|
+
// Database unique constraint (userId, type) provides additional safety
|
|
63
150
|
const device = await this.createDevice(userId, {
|
|
64
151
|
name: deviceName || dto.deviceName || 'Passkey Device',
|
|
65
152
|
credentialId: verified.credentialId,
|
|
@@ -67,14 +154,35 @@ class PasskeyMFAProviderService extends internal_1.BaseMFAProviderService {
|
|
|
67
154
|
counter: verified.counter,
|
|
68
155
|
transports: verified.transports,
|
|
69
156
|
isActive: true,
|
|
70
|
-
isPrimary: !userMfaEnabled,
|
|
157
|
+
isPrimary: !userMfaEnabled, // First device becomes primary
|
|
71
158
|
});
|
|
159
|
+
// Enable MFA if not already enabled
|
|
72
160
|
await this.enableMFAForUser(user);
|
|
73
161
|
this.logger?.log?.(`Passkey setup completed for user: ${user.sub}`);
|
|
74
162
|
return device.id;
|
|
75
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* Verify Passkey during authentication
|
|
166
|
+
*
|
|
167
|
+
* Validates WebAuthn assertion against stored public key.
|
|
168
|
+
*
|
|
169
|
+
* @param user - User being authenticated
|
|
170
|
+
* @param code - Verification data (must be { credential: AuthenticationResponseJSON, expectedChallenge: string })
|
|
171
|
+
* @param deviceId - Optional device ID to verify against
|
|
172
|
+
* @returns True if verification succeeds
|
|
173
|
+
* @throws {NAuthException} If device not found or verification fails
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```typescript
|
|
177
|
+
* const isValid = await provider.verify(user, {
|
|
178
|
+
* credential: webAuthnCredential,
|
|
179
|
+
* expectedChallenge: challenge
|
|
180
|
+
* });
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
76
183
|
async verify(user, code, deviceId) {
|
|
77
184
|
this.logger?.log?.(`Verifying passkey for user: ${user.sub}`);
|
|
185
|
+
// Extract credential and challenge
|
|
78
186
|
let credential;
|
|
79
187
|
let expectedChallenge;
|
|
80
188
|
if (code && typeof code === 'object' && 'credential' in code && 'expectedChallenge' in code) {
|
|
@@ -86,8 +194,10 @@ class PasskeyMFAProviderService extends internal_1.BaseMFAProviderService {
|
|
|
86
194
|
this.logger?.warn?.('Invalid passkey verification data format');
|
|
87
195
|
return false;
|
|
88
196
|
}
|
|
197
|
+
// Get user entity
|
|
89
198
|
const userEntity = user;
|
|
90
199
|
const userId = userEntity.id;
|
|
200
|
+
// Find device by credential ID
|
|
91
201
|
const device = deviceId
|
|
92
202
|
? await this.findDevice(userId, deviceId)
|
|
93
203
|
: await this.mfaDeviceRepository.findOne({
|
|
@@ -103,8 +213,11 @@ class PasskeyMFAProviderService extends internal_1.BaseMFAProviderService {
|
|
|
103
213
|
return false;
|
|
104
214
|
}
|
|
105
215
|
try {
|
|
216
|
+
// Verify passkey
|
|
217
|
+
// Cast device to IMFADevice since it came from repository and matches interface structure
|
|
106
218
|
const result = await this.passkeyService.verifyAuthentication(credential, expectedChallenge, device);
|
|
107
219
|
if (result.verified) {
|
|
220
|
+
// Update device counter and usage
|
|
108
221
|
const deviceEntity = device;
|
|
109
222
|
deviceEntity.counter = result.newCounter;
|
|
110
223
|
deviceEntity.lastUsedAt = new Date();
|
|
@@ -119,8 +232,25 @@ class PasskeyMFAProviderService extends internal_1.BaseMFAProviderService {
|
|
|
119
232
|
}
|
|
120
233
|
return false;
|
|
121
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* Send Passkey challenge for MFA verification
|
|
237
|
+
*
|
|
238
|
+
* Generates WebAuthn authentication options for login.
|
|
239
|
+
* Client must pass these to navigator.credentials.get().
|
|
240
|
+
*
|
|
241
|
+
* @param user - User requesting Passkey challenge
|
|
242
|
+
* @returns WebAuthn authentication options
|
|
243
|
+
* @throws {NAuthException} If no Passkey device registered
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* ```typescript
|
|
247
|
+
* const options = await provider.sendChallenge(user);
|
|
248
|
+
* // Client calls navigator.credentials.get({ publicKey: options.options })
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
122
251
|
async sendChallenge(user) {
|
|
123
252
|
this.logger?.log?.(`Generating passkey challenge for user: ${user.sub}`);
|
|
253
|
+
// Get user's passkey devices
|
|
124
254
|
const userEntity = user;
|
|
125
255
|
const userId = userEntity.id;
|
|
126
256
|
const devices = await this.getUserDevices(userId);
|
|
@@ -128,6 +258,7 @@ class PasskeyMFAProviderService extends internal_1.BaseMFAProviderService {
|
|
|
128
258
|
if (passkeyDevices.length === 0) {
|
|
129
259
|
throw new core_1.NAuthException(core_1.AuthErrorCode.NOT_FOUND, 'No passkey devices registered', { deviceType: 'passkey' });
|
|
130
260
|
}
|
|
261
|
+
// Generate authentication options
|
|
131
262
|
const options = await this.passkeyService.generateAuthenticationOptions(passkeyDevices);
|
|
132
263
|
this.logger?.log?.(`Passkey challenge generated for user: ${user.sub}`);
|
|
133
264
|
return options;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passkey-mfa-provider.service.js","sourceRoot":"","sources":["../../src/passkey-mfa-provider.service.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"passkey-mfa-provider.service.js","sourceRoot":"","sources":["../../src/passkey-mfa-provider.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,13 +1,49 @@
|
|
|
1
1
|
import type { RegistrationResponseJSON, AuthenticationResponseJSON } from '@simplewebauthn/types';
|
|
2
2
|
import { NAuthConfig, NAuthLogger, IMFADevice } from '@nauth-toolkit/core';
|
|
3
3
|
import { SetupPasskeyResponseDTO, GetPasskeyChallengeResponseDTO } from './dto/mfa.dto';
|
|
4
|
+
/**
|
|
5
|
+
* Passkey Service (WebAuthn/FIDO2)
|
|
6
|
+
*
|
|
7
|
+
* Handles passkey authentication with proper platform authenticator support.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const options = await passkeyService.generateRegistrationOptions(user, existingDevices);
|
|
12
|
+
* const verified = await passkeyService.verifyRegistration(credential, challenge, transports);
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
4
15
|
export declare class PasskeyService {
|
|
5
16
|
private readonly config;
|
|
6
17
|
private readonly logger;
|
|
7
18
|
private readonly defaultConfig;
|
|
8
19
|
constructor(config: NAuthConfig, logger: NAuthLogger);
|
|
20
|
+
/**
|
|
21
|
+
* Get passkey configuration with defaults
|
|
22
|
+
*
|
|
23
|
+
* @returns Complete passkey configuration
|
|
24
|
+
* @throws {NAuthException} If required config is missing
|
|
25
|
+
* @private
|
|
26
|
+
*/
|
|
9
27
|
private getPasskeyConfig;
|
|
28
|
+
/**
|
|
29
|
+
* Generate WebAuthn registration options for passkey setup
|
|
30
|
+
*
|
|
31
|
+
* @param userId - User ID (will be encoded as user.id)
|
|
32
|
+
* @param userEmail - User's email
|
|
33
|
+
* @param userName - User's display name
|
|
34
|
+
* @param existingDevices - User's existing passkey devices (to exclude from registration)
|
|
35
|
+
* @returns Registration options for WebAuthn API
|
|
36
|
+
*/
|
|
10
37
|
generateRegistrationOptions(userId: string, userEmail: string, userName: string, existingDevices?: IMFADevice[]): Promise<SetupPasskeyResponseDTO>;
|
|
38
|
+
/**
|
|
39
|
+
* Verify passkey registration response
|
|
40
|
+
*
|
|
41
|
+
* @param credential - WebAuthn registration response from client
|
|
42
|
+
* @param expectedChallenge - Expected challenge from registration options
|
|
43
|
+
* @param transports - Optional transports from client-side credential.getTransports()
|
|
44
|
+
* @returns Verified credential data for storage
|
|
45
|
+
* @throws {NAuthException} If verification fails
|
|
46
|
+
*/
|
|
11
47
|
verifyRegistration(credential: RegistrationResponseJSON, expectedChallenge: string, transports?: string[]): Promise<{
|
|
12
48
|
verified: boolean;
|
|
13
49
|
credentialId: string;
|
|
@@ -15,12 +51,38 @@ export declare class PasskeyService {
|
|
|
15
51
|
counter: number;
|
|
16
52
|
transports: string[];
|
|
17
53
|
}>;
|
|
54
|
+
/**
|
|
55
|
+
* Generate WebAuthn authentication options for MFA challenge
|
|
56
|
+
*
|
|
57
|
+
* @param userDevices - User's registered passkey devices
|
|
58
|
+
* @returns Authentication options for WebAuthn API
|
|
59
|
+
*/
|
|
18
60
|
generateAuthenticationOptions(userDevices: IMFADevice[]): Promise<GetPasskeyChallengeResponseDTO>;
|
|
61
|
+
/**
|
|
62
|
+
* Verify passkey authentication response
|
|
63
|
+
*
|
|
64
|
+
* @param credential - WebAuthn authentication response from client
|
|
65
|
+
* @param expectedChallenge - Expected challenge from authentication options
|
|
66
|
+
* @param device - MFA device with stored public key and counter
|
|
67
|
+
* @returns Verification result with new counter
|
|
68
|
+
* @throws {NAuthException} If verification fails
|
|
69
|
+
*/
|
|
19
70
|
verifyAuthentication(credential: AuthenticationResponseJSON, expectedChallenge: string, device: IMFADevice): Promise<{
|
|
20
71
|
verified: boolean;
|
|
21
72
|
newCounter: number;
|
|
22
73
|
}>;
|
|
74
|
+
/**
|
|
75
|
+
* Check if passkey is supported by configuration
|
|
76
|
+
*
|
|
77
|
+
* @returns True if passkey is properly configured
|
|
78
|
+
*/
|
|
23
79
|
isSupported(): boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Mask credential ID for display
|
|
82
|
+
*
|
|
83
|
+
* @param credentialId - Base64url encoded credential ID
|
|
84
|
+
* @returns Masked credential ID
|
|
85
|
+
*/
|
|
24
86
|
maskCredentialId(credentialId: string): string;
|
|
25
87
|
}
|
|
26
88
|
//# sourceMappingURL=passkey.service.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passkey.service.d.ts","sourceRoot":"","sources":["../../src/passkey.service.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,wBAAwB,EACxB,0BAA0B,EAE3B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,WAAW,EAEX,WAAW,EAIX,UAAU,EACX,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,uBAAuB,EAAE,8BAA8B,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"passkey.service.d.ts","sourceRoot":"","sources":["../../src/passkey.service.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,wBAAwB,EACxB,0BAA0B,EAE3B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,WAAW,EAEX,WAAW,EAIX,UAAU,EACX,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,uBAAuB,EAAE,8BAA8B,EAAE,MAAM,eAAe,CAAC;AAExF;;;;;;;;;;GAUG;AAEH,qBAAa,cAAc;IAOvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAPzB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAG5B;gBAGiB,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,WAAW;IAGtC;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;IAoBxB;;;;;;;;OAQG;IACG,2BAA2B,CAC/B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,eAAe,GAAE,UAAU,EAAO,GACjC,OAAO,CAAC,uBAAuB,CAAC;IAsCnC;;;;;;;;OAQG;IACG,kBAAkB,CACtB,UAAU,EAAE,wBAAwB,EACpC,iBAAiB,EAAE,MAAM,EACzB,UAAU,CAAC,EAAE,MAAM,EAAE,GACpB,OAAO,CAAC;QACT,QAAQ,EAAE,OAAO,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,EAAE,CAAC;KACtB,CAAC;IAwEF;;;;;OAKG;IACG,6BAA6B,CAAC,WAAW,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,8BAA8B,CAAC;IA+CvG;;;;;;;;OAQG;IACG,oBAAoB,CACxB,UAAU,EAAE,0BAA0B,EACtC,iBAAiB,EAAE,MAAM,EACzB,MAAM,EAAE,UAAU,GACjB,OAAO,CAAC;QACT,QAAQ,EAAE,OAAO,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IAsEF;;;;OAIG;IACH,WAAW,IAAI,OAAO;IAStB;;;;;OAKG;IACH,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM;CAM/C"}
|