@ideem/zsm-client-sdk 2.8.1 → 2.9.1
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/CHANGELOG.md +12 -1
- package/ErrorHandler.js +1 -154
- package/EventCoordinator.js +1 -295
- package/FIDO2Client.js +1 -15
- package/FIDO2ClientBase.js +3 -337
- package/GlobalScoping.js +1 -8
- package/IdentityIndexing.js +1 -51
- package/PKPUtils.js +1 -61
- package/PasskeysPlusClient.js +1 -125
- package/PluginManager.js +1 -112
- package/RelyingParty.js +1 -15
- package/RelyingPartyBase.js +3 -371
- package/UMFAClient.js +1 -15
- package/UMFAClientBase.js +1 -356
- package/Utils.js +1 -44
- package/WASMRustInterface.js +29 -22
- package/WebAuthnClient.js +1 -29
- package/WebAuthnClientBase.js +1 -763
- package/ZSMClientSDK.js +1 -43
- package/encodeUserIdentifier.js +1 -17
- package/package.json +1 -1
package/FIDO2ClientBase.js
CHANGED
|
@@ -1,337 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
// [J] The lines modified during the concatenation of the PKP Plugin into the Core SDK have been marked with a
|
|
6
|
-
// [J] `[J]` comment, to make it easier to identify during code review and for cross-platform porting purposes.
|
|
7
|
-
// [J] They can all be removed en masse by replacing `/ *\/\/ [J].*/gm` with `""` (and SHOULD be pre-publish).
|
|
8
|
-
|
|
9
|
-
class FIDO2ClientBase {
|
|
10
|
-
/**
|
|
11
|
-
* @constructor
|
|
12
|
-
* @param {Object} config - The configuration for the FIDO2Client
|
|
13
|
-
*/
|
|
14
|
-
constructor(config) {
|
|
15
|
-
eventCoordinator.reset();
|
|
16
|
-
eventCoordinator.update('FIDO2ClientBase');
|
|
17
|
-
this.config = config;
|
|
18
|
-
const WebAuthnClient = zsmPluginManager.classes('WEBAUTHNCLIENT');
|
|
19
|
-
this.zsmAPI = new WebAuthnClient(config);
|
|
20
|
-
|
|
21
|
-
this.checkEnrollment = this.checkEnrollment.bind(this);
|
|
22
|
-
this.checkIdentity = this.checkIdentity.bind(this);
|
|
23
|
-
this.webauthnRetrieve = this.webauthnRetrieve.bind(this);
|
|
24
|
-
this.webauthnDelete = this.webauthnDelete.bind(this);
|
|
25
|
-
this.resetDevice = this.resetDevice.bind(this);
|
|
26
|
-
this.webauthnCreate = this.webauthnCreate.bind(this);
|
|
27
|
-
this.webauthnGet = this.webauthnGet.bind(this);
|
|
28
|
-
// [J] Note that only EXPOSED methods are BOUND in the constructor
|
|
29
|
-
this.checkAllEnrollments = this.checkAllEnrollments.bind(this); // [J] Migrated from PKP Plugin
|
|
30
|
-
this.webauthnPasskeyPlusCreate = this.webauthnPasskeyPlusCreate.bind(this); // [J] Migrated from PKP Plugin
|
|
31
|
-
this.webauthnPasskeyPlusGet = this.webauthnPasskeyPlusGet.bind(this); // [J] Migrated from PKP Plugin
|
|
32
|
-
|
|
33
|
-
eventCoordinator.update('FIDO2ClientBase', 'READY');
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
get userIdentifier() { return this.zsmAPI.userIdentifier; }
|
|
37
|
-
get credentialID() { return this.zsmAPI.credentialID; }
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* @name typeChecks
|
|
41
|
-
* @description Performs type and value checks on the userIdentifier and userVerified parameters for the webauthnCreate and webauthnGet methods, and normalizes the userVerified value
|
|
42
|
-
* @param {string} methodName The name of the method calling typeChecks, used for error messages
|
|
43
|
-
* @param {string} userIdentifier The identifier for the user, required to be a non-empty string
|
|
44
|
-
* @param {string|boolean|null} userVerified The level of user verification required for authentication, can be a boolean or a string ("preferred", "prevented", "required", "discouraged")
|
|
45
|
-
* @param {string} addlUVStates Additional acceptable user verification states to be added to the check against the userVerified parameter, passed as a comma-separated string (e.g. "zsm,re-enroll,reenroll")
|
|
46
|
-
* @returns {Object} An object containing the validated and normalized userIdentifier and userVerified values
|
|
47
|
-
* @throws {TypeError} If the userIdentifier is not provided, is empty, or is not a string
|
|
48
|
-
* @throws {TypeError} If the userVerified value is not a boolean or an accepted string value
|
|
49
|
-
* @memberOf FIDO2ClientBase
|
|
50
|
-
*/
|
|
51
|
-
typeChecks(methodName, userIdentifier, userVerified=null, addlUVStates="") {
|
|
52
|
-
if(!userIdentifier) throw new TypeError(`[FIDO2Client] :: ${methodName} :: A userIdentifier String is required! Received: ${userIdentifier} (type: ${typeof userIdentifier}).`);
|
|
53
|
-
let enrollmentDetails;
|
|
54
|
-
if(typeof userIdentifier === 'object') {
|
|
55
|
-
enrollmentDetails = {...userIdentifier};
|
|
56
|
-
userIdentifier = userIdentifier.userIdentifier;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if(userVerified != null){
|
|
60
|
-
if(typeof userVerified === 'boolean') userVerified = (userVerified === true) ? "preferred" : "prevented";
|
|
61
|
-
else if(typeof userVerified === 'string') userVerified = userVerified.toLowerCase();
|
|
62
|
-
else throw new TypeError(`[FIDO2Client] :: ${methodName} :: userVerified must be a boolean or string. Received type: ${typeof userVerified}.`);
|
|
63
|
-
|
|
64
|
-
const uvList = ['preferred', 'required', 'discouraged', 'prevented', ...addlUVStates.split(/, ?/g).filter(Boolean)];
|
|
65
|
-
const uvRE = new RegExp(`^(${uvList.join('|')})$`);
|
|
66
|
-
if(!uvRE.test(userVerified)) throw new TypeError(`[FIDO2Client] :: ${methodName} :: userVerified must be one of the following Strings: "${uvList.join('", "')}"). Received: ${userVerified}.`);
|
|
67
|
-
}
|
|
68
|
-
userIdentifier = encodeUserIdentifier(userIdentifier); // [J] Encode the userIdentifier before it is used in any downstream operations (no-op stub pending hash algorithm selection)
|
|
69
|
-
let retVal = { userIdentifier, userVerified };
|
|
70
|
-
if(enrollmentDetails) retVal = { enrollmentDetails, ...retVal };
|
|
71
|
-
return retVal;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* @name checkIdentity
|
|
76
|
-
* @description Pass-through method to call WebAuthnClient's checkIdentity
|
|
77
|
-
* @param {string} userIdentifier The identifier for the user
|
|
78
|
-
* @param {boolean} primeEnroll Whether to create a new identity if it doesn't exist
|
|
79
|
-
* @returns {Promise<Object>} Returns the results of zsmAPI.checkIdentity
|
|
80
|
-
* @memberOf FIDO2ClientBase, WebAuthnClient
|
|
81
|
-
*/
|
|
82
|
-
async checkIdentity (userIdentifier, primeEnroll) { return this.zsmAPI.checkIdentity(encodeUserIdentifier(userIdentifier), primeEnroll); }
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* @name checkEnrollment
|
|
86
|
-
* @description Checks if a ZSM credential is enrolled on the device for the specified user
|
|
87
|
-
* @param {string} userIdentifier The identifier for the user
|
|
88
|
-
* @returns {Promise<Object>|boolean} Resolves with the enrollment details or false if not enrolled, or an error if checking fails
|
|
89
|
-
* @throws {Error} If the userIdentifier is not provided or is empty
|
|
90
|
-
* @throws {Error} If an error occurs while checking enrollment
|
|
91
|
-
* @memberOf FIDO2ClientBase
|
|
92
|
-
*/
|
|
93
|
-
async checkEnrollment(userIdentifier) { // [J] Removing now-useless second parameter, as the checkAllEnrollments method returns all details; checkEnrollment returns ONLY the credential ID
|
|
94
|
-
({ userIdentifier } = this.typeChecks('checkEnrollment', userIdentifier));
|
|
95
|
-
const enrollmentStatus = await this.zsmAPI.webauthnRetrieve(userIdentifier);
|
|
96
|
-
|
|
97
|
-
return enrollmentStatus;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* @name checkAllEnrollments
|
|
102
|
-
* @description Pass-through method to call WebAuthnClient's webauthnRetrieveAll
|
|
103
|
-
* @param {string} userIdentifier The identifier for the user
|
|
104
|
-
* @returns {Promise<Object>} Returns the full enrollment details for the user
|
|
105
|
-
* @memberOf FIDO2ClientBase
|
|
106
|
-
*/
|
|
107
|
-
async checkAllEnrollments(userIdentifier, forceRemoteCheck=false) { return this.zsmAPI.webauthnRetrieveAll(encodeUserIdentifier(userIdentifier), forceRemoteCheck); } // [J] Migrated from PKP Plugin
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* @name webauthnRetrieve
|
|
111
|
-
* @description Pass-through method to call WebAuthnClient's webauthnRetrieve
|
|
112
|
-
* @param {string} userIdentifier The identifier for the user
|
|
113
|
-
* @returns {Promise<Object>} Returns the results of zsmAPI.webauthnRetrieve
|
|
114
|
-
* @memberOf FIDO2ClientBase, WebAuthnClient
|
|
115
|
-
*/
|
|
116
|
-
webauthnRetrieve (userIdentifier) { return this.zsmAPI.webauthnRetrieve(encodeUserIdentifier(userIdentifier)); }
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* @name webauthnDelete
|
|
120
|
-
* @description Pass-through method to call WebAuthnClient's unbindFromDevice
|
|
121
|
-
* @param {string} userIdentifier The identifier for the user
|
|
122
|
-
* @returns {Promise<Object>} Returns the results of zsmAPI.unbindFromDevice
|
|
123
|
-
* @memberOf FIDO2ClientBase, WebAuthnClient
|
|
124
|
-
*/
|
|
125
|
-
webauthnDelete (userIdentifier) { return this.zsmAPI.unbindFromDevice(encodeUserIdentifier(userIdentifier)); }
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* @name resetDevice
|
|
129
|
-
* @description Pass-through method to call WebAuthnClient's webauthnReset
|
|
130
|
-
* @returns {Promise<void>} No results are returned
|
|
131
|
-
* @memberOf FIDO2ClientBase, WebAuthnClient
|
|
132
|
-
*/
|
|
133
|
-
async resetDevice () { this.zsmAPI.webauthnReset(); }
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* @name webauthnCreate
|
|
137
|
-
* @description Enrolls a user by creating a ZSM credential on the device
|
|
138
|
-
* @param {string} userIdentifier The identifier for the user
|
|
139
|
-
* @param {string} userVerified The level of user verification required for authentication (true/"preferred", false/"prevented", "required", "discouraged")
|
|
140
|
-
* @returns {Promise<Object>|Error} Resolves with the created credential, false if user is already enrolled, or an error if enrollment fails
|
|
141
|
-
* @throws {Error} If the userIdentifier is not provided or is empty
|
|
142
|
-
* @throws {Error} If an error occurs during enrollment
|
|
143
|
-
* @memberOf FIDO2ClientBase
|
|
144
|
-
*/
|
|
145
|
-
async webauthnCreate (userIdentifier, userVerified="prevented") { // [J] This method has been merged with the PKP Plugin's __webauthnCreate method
|
|
146
|
-
({ userIdentifier, userVerified } = this.typeChecks('webauthnCreate', userIdentifier, userVerified, 'zsm,re-enroll,reenroll'));
|
|
147
|
-
const isPasskeyReenrollment = /^re-?enroll$/.test(userVerified); // Is this a forced PKP re-enrollment?
|
|
148
|
-
const attemptPasskeyPlusEnroll = isPasskeyReenrollment || /^(preferred|required)$/.test(userVerified); // [J] Does the userVerified value indicate that Passkeys+ enrollment should be attempted alongside ZSM enrollment?
|
|
149
|
-
const continueOnPasskeyFailure = (userVerified === 'preferred');
|
|
150
|
-
|
|
151
|
-
let pkpEnrollmentResult, enrollmentDetails;
|
|
152
|
-
if(attemptPasskeyPlusEnroll) {
|
|
153
|
-
enrollmentDetails = await this.checkAllEnrollments(userIdentifier);
|
|
154
|
-
try {
|
|
155
|
-
if(!enrollmentDetails.passkeySupported) { // If Passkeys+ is not supported, but required, throw an error immediately
|
|
156
|
-
if(!continueOnPasskeyFailure) // ...and if the userVerified is "required", fail the entire enrollment operation...
|
|
157
|
-
return new Error(`[FIDO2Client] :: webauthnCreate :: Passkeys+ is required for enrollment (userVerified set to "required"), but this device does not support Passkeys+. User ${userIdentifier} cannot be enrolled. Please use a Passkeys+-compatible device to enroll.`);
|
|
158
|
-
else
|
|
159
|
-
console.warn(`[FIDO2Client] :: webauthnCreate :: Passkeys+ enrollment has been requested, but this device does not support Passkeys+. User ${userIdentifier} will be enrolled with ZSM-only credentials. Please use a Passkeys+-compatible device to enroll with Passkeys+ in the future.`);
|
|
160
|
-
} else if (isPasskeyReenrollment) { // Current state doesn't matter. User has specified a new passkey is to be added...
|
|
161
|
-
if (!enrollmentDetails.zsmCredentialID && !enrollmentDetails.hasLocalPasskey) // ... so, unless the user doesn't have credentials AT ALL...
|
|
162
|
-
return new Error(`[FIDO2Client] :: webauthnCreate :: Cannot RE-enroll user's passkey; user ${userIdentifier} is not currently enrolled with either ZSM or Passkeys+ credentials. To enroll the user, call webauthnCreate with a userVerified value of "preferred", "required", or "discouraged".`);
|
|
163
|
-
pkpEnrollmentResult = await this.webauthnPasskeyPlusCreate(enrollmentDetails, 'required'); // ... we trigger the Passkeys+ enrollment flow. Failure mode behavior is static: either passkey works or boot them entirely.
|
|
164
|
-
|
|
165
|
-
} else if (enrollmentDetails.zsmCredentialID && enrollmentDetails.hasLocalPasskey) { // ZSM: Y | PKP: Y | Remote PKP: Y - User already fully-registered; fail the operation.
|
|
166
|
-
return new Error(`[FIDO2Client] :: webauthnCreate :: User ${userIdentifier} is already enrolled with both ZSM and Passkeys+ credentials. To authenticate one or both credential, call the webauthnGet method.`);
|
|
167
|
-
|
|
168
|
-
} else if ((enrollmentDetails instanceof Error || enrollmentDetails === false) ||
|
|
169
|
-
(!enrollmentDetails.zsmCredentialID && !enrollmentDetails.hasLocalPasskey)) { // ZSM: N | PKP: N | Remote PKP: N - Dual enrollment for both ZSM and PKP...
|
|
170
|
-
pkpEnrollmentResult = await this.webauthnPasskeyPlusCreate(enrollmentDetails, userVerified); // ...so we attempt Passkeys+ enrollment. Failure mode behavior is dictated by userVerified
|
|
171
|
-
|
|
172
|
-
} else if (enrollmentDetails.zsmCredentialID && !enrollmentDetails.hasLocalPasskey) { // ZSM: Y | PKP: N | Remote PKP: N - User has ZSM only...
|
|
173
|
-
pkpEnrollmentResult = await this.webauthnBindPasskeyToZSM(enrollmentDetails, userVerified); // ...so we attempt to add Passkeys+ to existing ZSM credential. Failure mode behavior is dictated by userVerified
|
|
174
|
-
|
|
175
|
-
} else if (!enrollmentDetails.zsmCredentialID && enrollmentDetails.pkpCredential) { // ZSM: N | PKP: Y | Remote PKP: Y - User ONLY has a (remote) passkey (which the checkAllEnrollments bootstrapped down to local)...
|
|
176
|
-
pkpEnrollmentResult = await this.webauthnBindZSMToPasskey(enrollmentDetails, userVerified); // ...so we attempt to add ZSM credential to existing Passkeys+ credential. Failure mode behavior is dictated by userVerified
|
|
177
|
-
}
|
|
178
|
-
return (pkpEnrollmentResult === undefined)
|
|
179
|
-
? new Error(`[FIDO2Client] :: webauthnCreate :: Unexpected PKP flow: PKP enrollment attempted but no result was returned for user ${userIdentifier}.`)
|
|
180
|
-
: pkpEnrollmentResult;
|
|
181
|
-
//TODO: Determine the cause of the passkey failure and tailor the below error message accordingly (currently only handles NotAllowedError: user cancellation/timeout/failure).
|
|
182
|
-
} catch (pkpError) { // If the passkey portion of the operation fails (for ANY reason)...
|
|
183
|
-
console.error('ENROLL FLOW :: PASSKEY OPERATION FAILED ::', pkpError);
|
|
184
|
-
if (!continueOnPasskeyFailure) // ...and the userVerified is "required", return an error to fail the entire enrollment operation...
|
|
185
|
-
return new Error(`[FIDO2Client] :: webauthnCreate :: The passkey portion of the Passkeys+ enrollment failed for user ${userIdentifier} with error: ${pkpError.message || pkpError}. Since userVerified is set to "${userVerified}", the entire enrollment process has failed. You may: retry, downgrade the userVerified mandate to "preferred" or "discouraged" to allow for ZSM-only enrollment.`);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// The code below handles ZSM-only enrollments, as well as providing the "fallback-to-ZSM-only" behavior for acceptable Passkeys+ failures (e.g. userVerified === "preferred")
|
|
190
|
-
let localZSMCredentialID;
|
|
191
|
-
if(enrollmentDetails?.zsmCredentialID) localZSMCredentialID = enrollmentDetails?.zsmCredentialID // No sense in hitting the ZSM credential check if we already have the enrollment details from the pkp section, above...
|
|
192
|
-
else localZSMCredentialID = await this.checkEnrollment(userIdentifier); // ...but if we didn't, let's check the local-only operation for the credential ID
|
|
193
|
-
|
|
194
|
-
if(typeof localZSMCredentialID === 'string') { // If the user is ALREADY enrolled with a ZSM credential, fail the operation
|
|
195
|
-
return new Error(`[FIDO2Client] :: webauthnCreate :: User ${userIdentifier} is already enrolled with a ZSM credential. To authenticate it, call the webauthnGet method.`);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
await this.zsmAPI.checkIdentity(userIdentifier, true); // [J] This checkIdentity call "primes" the enrollment on the server & sets credentialID/userIdentifier on the zsmAPI instance
|
|
199
|
-
const createResult = await this.zsmAPI.webauthnCreate(userIdentifier); // Perform the vanilla ZSM enrollment.
|
|
200
|
-
if(createResult instanceof Error) // Should the operation fail...
|
|
201
|
-
return new Error(`[FIDO2Client] :: webauthnCreate :: Unable to enroll ${userIdentifier}!\nDetails:\n${createResult?.message || createResult}`, {cause: createResult?.stack});
|
|
202
|
-
return createResult; // ...otherwise, all operations either succeeded or failed acceptably, so we return the result of the ZSM enrollment
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* @name webauthnGet
|
|
207
|
-
* @description Retrieves the ZSM credential for the specified user
|
|
208
|
-
* @param {string} userIdentifier The identifier for the user
|
|
209
|
-
* @param {string} userVerified The level of user verification required for authentication (true/"preferred", false/"prevented", "required", "discouraged")
|
|
210
|
-
* @returns {Promise<Object>} The ZSM credential for the user
|
|
211
|
-
* @throws {Error} If the userIdentifier is not provided or is empty
|
|
212
|
-
* @throws {Error} If an error occurs during credential retrieval
|
|
213
|
-
* @memberOf FIDO2ClientBase
|
|
214
|
-
*/
|
|
215
|
-
async webauthnGet (userIdentifier, userVerified="prevented") { // [J] This method has been merged with the PKP Plugin's __webauthnGet method
|
|
216
|
-
({ userIdentifier, userVerified } = this.typeChecks('webauthnGet', userIdentifier, userVerified, 'zsm'));
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const attemptPasskeyPlusAuth = /^(preferred|required)$/.test(userVerified); // [J] Does the userVerified value indicate that Passkeys+ authentication should be attempted alongside ZSM authentication?
|
|
220
|
-
const continueOnPasskeyFailure = (userVerified === 'preferred');
|
|
221
|
-
|
|
222
|
-
// Passkeys+ Operations
|
|
223
|
-
let pkpAuthResult, enrollmentDetails;
|
|
224
|
-
if(attemptPasskeyPlusAuth) {
|
|
225
|
-
enrollmentDetails = await this.checkAllEnrollments(userIdentifier);
|
|
226
|
-
|
|
227
|
-
if(!enrollmentDetails.zsmCredentialID) { // [J] Field names updated to match cleanFinalData
|
|
228
|
-
|
|
229
|
-
return new Error(`[FIDO2Client] :: webauthnGet :: User ${userIdentifier} must be enrolled with a ZSM credential before authenticating. Please enroll the user first.`);
|
|
230
|
-
} else if (!enrollmentDetails.passkeySupported) {
|
|
231
|
-
|
|
232
|
-
return new Error(`[FIDO2Client] :: webauthnGet :: User ${userIdentifier} is not eligible for Passkeys+ authentication; their device does not support passkey authentication. To authenticate, call the webauthnGet method with userVerified set to "preferred", "discouraged", or "prevented"/"zsm". To enroll with Passkeys+, call webauthnCreate with userVerified set to "preferred" or "required" on a Passkeys+-compatible device.`);
|
|
233
|
-
} else if(!enrollmentDetails.hasLocalPasskey) {
|
|
234
|
-
|
|
235
|
-
return new Error(`[FIDO2Client] :: webauthnGet :: User ${userIdentifier} must be enrolled with both ZSM and Passkeys+ credentials to authenticate using Passkeys+ (userVerified set to a non-prevented value).`);
|
|
236
|
-
} else if(attemptPasskeyPlusAuth) {
|
|
237
|
-
|
|
238
|
-
try {
|
|
239
|
-
pkpAuthResult = await this.webauthnPasskeyPlusGet(enrollmentDetails, userVerified);
|
|
240
|
-
|
|
241
|
-
} catch (pkpError) {
|
|
242
|
-
if (!continueOnPasskeyFailure) return new Error(`[FIDO2Client] :: webauthnGet :: The passkey portion of the Passkeys+ authentication failed for user ${userIdentifier} with error: ${pkpError.message || pkpError}. Since userVerified is set to "${userVerified}", the entire authentication process has failed. You may: retry or downgrade the userVerified mandate to "preferred" or "discouraged" to allow for ZSM-only authentication.`);
|
|
243
|
-
}
|
|
244
|
-
return (pkpAuthResult === undefined)
|
|
245
|
-
? new Error(`[FIDO2Client] :: webauthnGet :: Unexpected PKP flow: PKP authentication attempted but no result was returned for user ${userIdentifier}.`)
|
|
246
|
-
: pkpAuthResult;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// The code below handles ZSM-only authentications, as well as providing the "fallback-to-ZSM-only" behavior for acceptable Passkeys+ failures (e.g. userVerified === "preferred")
|
|
251
|
-
let localZSMCredentialID;
|
|
252
|
-
if(enrollmentDetails?.zsmCredentialID) localZSMCredentialID = enrollmentDetails?.zsmCredentialID // No sense in hitting the ZSM credential check if we already have the enrollment details from the pkp section, above...
|
|
253
|
-
else localZSMCredentialID = await this.checkEnrollment(userIdentifier); // ...but if we didn't, let's check the local-only operation for the credential ID
|
|
254
|
-
|
|
255
|
-
const userIsEnrolled = typeof localZSMCredentialID === 'string';
|
|
256
|
-
if(userIsEnrolled === false) return new Error(`[FIDO2Client] :: webauthnGet :: ${userIdentifier} is not enrolled. To authenticate the user, please enroll them first by calling the webauthnCreate method.`);
|
|
257
|
-
|
|
258
|
-
const authCredential = await this.zsmAPI.webauthnPartialGet(userIdentifier);
|
|
259
|
-
return authCredential?.credential??authCredential;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
//! PASSKEYS PLUS FIDO2 CLIENT METHODS ==============================================================================================================================
|
|
263
|
-
// [J] These were migrated from PKP Plugin with minimal adjustments to fit into the FIDO2ClientBase class structure and flow
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* @name webauthnPasskeyPlusCreate
|
|
267
|
-
* @description Creates a Passkeys+ credential for the user, then creates a ZSM credential
|
|
268
|
-
* @param {string} userIdentifier The identifier for the user
|
|
269
|
-
* @param {string} userVerified Accepts "required", "preferred", "discouraged", to be passed into the Passkey ceremony
|
|
270
|
-
* @returns {Promise<Object>} The results of the call to WebAuthnClient's pkpCreate method or an Error
|
|
271
|
-
* @throws {Error} If the userIdentifier is not provided or is empty
|
|
272
|
-
* @throws {Error} If an error occurs during Passkeys+ creation
|
|
273
|
-
* @memberOf FIDO2ClientBase
|
|
274
|
-
*/
|
|
275
|
-
async webauthnPasskeyPlusCreate (userIdentifier, userVerified="preferred") {
|
|
276
|
-
({ userIdentifier, userVerified } = this.typeChecks('webauthnPasskeyPlusCreate', userIdentifier, userVerified, 're-enroll,reenroll')); // [J] Replaced manual check with typeChecks
|
|
277
|
-
|
|
278
|
-
const pkpCreateResult = await this.zsmAPI.pkpCreate(userIdentifier, userVerified);
|
|
279
|
-
|
|
280
|
-
return pkpCreateResult;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* @name webauthnPasskeyPlusGet
|
|
285
|
-
* @description Authenticates a ZSM credential on the device for the specified user
|
|
286
|
-
* @param {string} userIdentifier The identifier for the user
|
|
287
|
-
* @param {string} userVerified Accepts "required", "preferred", "discouraged", to be passed into the Passkey ceremony
|
|
288
|
-
* @returns {Promise<Object>} The results of the call to WebAuthnClient's pkpAuthenticate method or an Error
|
|
289
|
-
* @throws {Error} If the userIdentifier is not provided or is empty
|
|
290
|
-
* @throws {Error} If an error occurs during Passkeys+ authentication
|
|
291
|
-
* @memberOf FIDO2ClientBase
|
|
292
|
-
*/
|
|
293
|
-
async webauthnPasskeyPlusGet (userIdentifier, userVerified="preferred") {
|
|
294
|
-
|
|
295
|
-
let enrollmentDetails = null;
|
|
296
|
-
({ userIdentifier, userVerified, enrollmentDetails } = this.typeChecks('webauthnPasskeyPlusGet', userIdentifier, userVerified, 're-enroll,reenroll'));
|
|
297
|
-
if(!enrollmentDetails) enrollmentDetails = await this.checkAllEnrollments(userIdentifier, true);
|
|
298
|
-
|
|
299
|
-
const pkpAuthResult = await this.zsmAPI.pkpAuthenticate(enrollmentDetails, userVerified, !enrollmentDetails.zsmCredentialID); // [J] Field name updated to match cleanFinalData
|
|
300
|
-
return pkpAuthResult;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* @name webauthnBindZSMToPasskey
|
|
305
|
-
* @description Adds a ZSM credential for a user who already has Passkeys+ credentials registered on another device.
|
|
306
|
-
* @param {string} userIdentifier The identifier for the user.
|
|
307
|
-
* @returns {Promise<Object>} Completed ZSM + Passkeys+ authentication result.
|
|
308
|
-
* @throws {Error} If no Passkeys+ credentials are found for the user on another device.
|
|
309
|
-
* @memberOf FIDO2ClientBase
|
|
310
|
-
*/
|
|
311
|
-
async webauthnBindZSMToPasskey (userIdentifier, userVerified) {
|
|
312
|
-
({ userIdentifier } = this.typeChecks('webauthnBindZSMToPasskey', userIdentifier));
|
|
313
|
-
|
|
314
|
-
const passkeyPreauth = await this.webauthnPasskeyPlusGet(userIdentifier);
|
|
315
|
-
return passkeyPreauth;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* @name webauthnBindPasskeyToZSM
|
|
320
|
-
* @description Adds a Passkeys+ credential for a user who already has a local ZSM credential.
|
|
321
|
-
* @param {string} userIdentifier The identifier for the user.
|
|
322
|
-
* @param {string} userVerified Accepts "required", "preferred", "discouraged", to be passed into the Passkey ceremony.
|
|
323
|
-
* @returns {Promise<Object>} Completed ZSM + Passkeys+ authentication result.
|
|
324
|
-
* @throws {Error} If no ZSM credentials are found for the user on another device.
|
|
325
|
-
* @memberOf FIDO2ClientBase
|
|
326
|
-
*/
|
|
327
|
-
async webauthnBindPasskeyToZSM (userIdentifier, userVerified="preferred") {
|
|
328
|
-
let enrollmentDetails = null;
|
|
329
|
-
({ userIdentifier, userVerified, enrollmentDetails } = this.typeChecks('webauthnBindPasskeyToZSM', userIdentifier, userVerified, 're-enroll,reenroll')); // [J] Replaced manual check with typeChecks
|
|
330
|
-
|
|
331
|
-
const pkpAddPasskeyResult = await this.zsmAPI.pkpAddPasskeyCredential((enrollmentDetails || userIdentifier), userVerified);
|
|
332
|
-
|
|
333
|
-
return pkpAddPasskeyResult;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
export default FIDO2ClientBase;
|
|
1
|
+
import e from"./EventCoordinator.js";import{zsmPluginManager as t}from"./PluginManager.js";import{encodeUserIdentifier as r}from"./encodeUserIdentifier.js";export default class{constructor(r){e.reset(),e.update("FIDO2ClientBase"),this.config=r;const s=t.classes("WEBAUTHNCLIENT");this.zsmAPI=new s(r),this.checkEnrollment=this.checkEnrollment.bind(this),this.checkIdentity=this.checkIdentity.bind(this),this.webauthnRetrieve=this.webauthnRetrieve.bind(this),this.webauthnDelete=this.webauthnDelete.bind(this),this.resetDevice=this.resetDevice.bind(this),this.webauthnCreate=this.webauthnCreate.bind(this),this.webauthnGet=this.webauthnGet.bind(this),this.checkAllEnrollments=this.checkAllEnrollments.bind(this),this.webauthnPasskeyPlusCreate=this.webauthnPasskeyPlusCreate.bind(this),this.webauthnPasskeyPlusGet=this.webauthnPasskeyPlusGet.bind(this),e.update("FIDO2ClientBase","READY")}get userIdentifier(){return this.zsmAPI.userIdentifier}get credentialID(){return this.zsmAPI.credentialID}typeChecks(e,t,s=null,n=""){if(!t)throw new TypeError(`[FIDO2Client] :: ${e} :: A userIdentifier String is required! Received: ${t} (type: ${typeof t}).`);let i;if("object"==typeof t&&(i={...t},t=t.userIdentifier),null!=s){if("boolean"==typeof s)s=!0===s?"preferred":"prevented";else{if("string"!=typeof s)throw new TypeError(`[FIDO2Client] :: ${e} :: userVerified must be a boolean or string. Received type: ${typeof s}.`);s=s.toLowerCase()}const t=["preferred","required","discouraged","prevented",...n.split(/, ?/g).filter(Boolean)];if(!new RegExp(`^(${t.join("|")})$`).test(s))throw new TypeError(`[FIDO2Client] :: ${e} :: userVerified must be one of the following Strings: "${t.join('", "')}"). Received: ${s}.`)}let a={userIdentifier:t=r(t),userVerified:s};return i&&(a={enrollmentDetails:i,...a}),a}async checkIdentity(e,t){return this.zsmAPI.checkIdentity(r(e),t)}async checkEnrollment(e){({userIdentifier:e}=this.typeChecks("checkEnrollment",e));return await this.zsmAPI.webauthnRetrieve(e)}async checkAllEnrollments(e,t=!1){return this.zsmAPI.webauthnRetrieveAll(r(e),t)}webauthnRetrieve(e){return this.zsmAPI.webauthnRetrieve(r(e))}webauthnDelete(e){return this.zsmAPI.unbindFromDevice(r(e))}async resetDevice(){this.zsmAPI.webauthnReset()}async webauthnCreate(e,t="prevented"){({userIdentifier:e,userVerified:t}=this.typeChecks("webauthnCreate",e,t,"zsm,re-enroll,reenroll"));const r=/^re-?enroll$/.test(t),s="preferred"===t;let n,i,a;if(r||/^(preferred|required)$/.test(t)){i=await this.checkAllEnrollments(e);try{if(i.passkeySupported)if(r){if(!i.zsmCredentialID&&!i.hasLocalPasskey)return new Error(`[FIDO2Client] :: webauthnCreate :: Cannot RE-enroll user's passkey; user ${e} is not currently enrolled with either ZSM or Passkeys+ credentials. To enroll the user, call webauthnCreate with a userVerified value of "preferred", "required", or "discouraged".`);n=await this.webauthnPasskeyPlusCreate(i,"required")}else{if(i.zsmCredentialID&&i.hasLocalPasskey)return new Error(`[FIDO2Client] :: webauthnCreate :: User ${e} is already enrolled with both ZSM and Passkeys+ credentials. To authenticate one or both credential, call the webauthnGet method.`);i instanceof Error||!1===i||!i.zsmCredentialID&&!i.hasLocalPasskey?n=await this.webauthnPasskeyPlusCreate(i,t):i.zsmCredentialID&&!i.hasLocalPasskey?n=await this.webauthnBindPasskeyToZSM(i,t):!i.zsmCredentialID&&i.pkpCredential&&(n=await this.webauthnBindZSMToPasskey(i,t))}else{if(!s)return new Error(`[FIDO2Client] :: webauthnCreate :: Passkeys+ is required for enrollment (userVerified set to "required"), but this device does not support Passkeys+. User ${e} cannot be enrolled. Please use a Passkeys+-compatible device to enroll.`);console.warn(`[FIDO2Client] :: webauthnCreate :: Passkeys+ enrollment has been requested, but this device does not support Passkeys+. User ${e} will be enrolled with ZSM-only credentials. Please use a Passkeys+-compatible device to enroll with Passkeys+ in the future.`)}return void 0===n?new Error(`[FIDO2Client] :: webauthnCreate :: Unexpected PKP flow: PKP enrollment attempted but no result was returned for user ${e}.`):n}catch(r){if(console.error("ENROLL FLOW :: PASSKEY OPERATION FAILED ::",r),!s)return new Error(`[FIDO2Client] :: webauthnCreate :: The passkey portion of the Passkeys+ enrollment failed for user ${e} with error: ${r.message||r}. Since userVerified is set to "${t}", the entire enrollment process has failed. You may: retry, downgrade the userVerified mandate to "preferred" or "discouraged" to allow for ZSM-only enrollment.`)}}if(a=i?.zsmCredentialID?i?.zsmCredentialID:await this.checkEnrollment(e),"string"==typeof a)return new Error(`[FIDO2Client] :: webauthnCreate :: User ${e} is already enrolled with a ZSM credential. To authenticate it, call the webauthnGet method.`);await this.zsmAPI.checkIdentity(e,!0);const l=await this.zsmAPI.webauthnCreate(e);return l instanceof Error?new Error(`[FIDO2Client] :: webauthnCreate :: Unable to enroll ${e}!\nDetails:\n${l?.message||l}`,{cause:l?.stack}):l}async webauthnGet(e,t="prevented"){({userIdentifier:e,userVerified:t}=this.typeChecks("webauthnGet",e,t,"zsm"));const r=/^(preferred|required)$/.test(t),s="preferred"===t;let n,i,a;if(r){if(i=await this.checkAllEnrollments(e),!i.zsmCredentialID)return new Error(`[FIDO2Client] :: webauthnGet :: User ${e} must be enrolled with a ZSM credential before authenticating. Please enroll the user first.`);if(!i.passkeySupported)return new Error(`[FIDO2Client] :: webauthnGet :: User ${e} is not eligible for Passkeys+ authentication; their device does not support passkey authentication. To authenticate, call the webauthnGet method with userVerified set to "preferred", "discouraged", or "prevented"/"zsm". To enroll with Passkeys+, call webauthnCreate with userVerified set to "preferred" or "required" on a Passkeys+-compatible device.`);if(!i.hasLocalPasskey)return new Error(`[FIDO2Client] :: webauthnGet :: User ${e} must be enrolled with both ZSM and Passkeys+ credentials to authenticate using Passkeys+ (userVerified set to a non-prevented value).`);if(r){try{n=await this.webauthnPasskeyPlusGet(i,t)}catch(r){if(!s)return new Error(`[FIDO2Client] :: webauthnGet :: The passkey portion of the Passkeys+ authentication failed for user ${e} with error: ${r.message||r}. Since userVerified is set to "${t}", the entire authentication process has failed. You may: retry or downgrade the userVerified mandate to "preferred" or "discouraged" to allow for ZSM-only authentication.`)}return void 0===n?new Error(`[FIDO2Client] :: webauthnGet :: Unexpected PKP flow: PKP authentication attempted but no result was returned for user ${e}.`):n}}a=i?.zsmCredentialID?i?.zsmCredentialID:await this.checkEnrollment(e);if(!1===("string"==typeof a))return new Error(`[FIDO2Client] :: webauthnGet :: ${e} is not enrolled. To authenticate the user, please enroll them first by calling the webauthnCreate method.`);const l=await this.zsmAPI.webauthnPartialGet(e);return l?.credential??l}
|
|
2
|
+
//! PASSKEYS PLUS FIDO2 CLIENT METHODS ==============================================================================================================================
|
|
3
|
+
async webauthnPasskeyPlusCreate(e,t="preferred"){({userIdentifier:e,userVerified:t}=this.typeChecks("webauthnPasskeyPlusCreate",e,t,"re-enroll,reenroll"));return await this.zsmAPI.pkpCreate(e,t)}async webauthnPasskeyPlusGet(e,t="preferred"){let r=null;({userIdentifier:e,userVerified:t,enrollmentDetails:r}=this.typeChecks("webauthnPasskeyPlusGet",e,t,"re-enroll,reenroll")),r||(r=await this.checkAllEnrollments(e,!0));return await this.zsmAPI.pkpAuthenticate(r,t,!r.zsmCredentialID)}async webauthnBindZSMToPasskey(e,t){({userIdentifier:e}=this.typeChecks("webauthnBindZSMToPasskey",e));return await this.webauthnPasskeyPlusGet(e)}async webauthnBindPasskeyToZSM(e,t="preferred"){let r=null;({userIdentifier:e,userVerified:t,enrollmentDetails:r}=this.typeChecks("webauthnBindPasskeyToZSM",e,t,"re-enroll,reenroll"));return await this.zsmAPI.pkpAddPasskeyCredential(r||e,t)}}
|
package/GlobalScoping.js
CHANGED
|
@@ -1,8 +1 @@
|
|
|
1
|
-
|
|
2
|
-
const GLOBAL = typeof globalThis !== "undefined" ? globalThis
|
|
3
|
-
: typeof self !== "undefined" ? self
|
|
4
|
-
: typeof window !== "undefined" ? window
|
|
5
|
-
: typeof global !== "undefined" ? global
|
|
6
|
-
: (() => { throw new Error("[ZSM GlobalScoping] :: Unable to determine global scope!"); })();
|
|
7
|
-
|
|
8
|
-
export default GLOBAL;
|
|
1
|
+
const e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:(()=>{throw new Error("[ZSM GlobalScoping] :: Unable to determine global scope!")})();export default e;
|
package/IdentityIndexing.js
CHANGED
|
@@ -1,51 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* @file IdentityIndexing.js
|
|
3
|
-
* @description This module provides functions to bind a user identifier (UID) to an identity ID (IID) in IndexedDB and to retrieve the IID associated with a given UID.
|
|
4
|
-
* @author Jason Jurusz for Ideem
|
|
5
|
-
* @version 1.0.0
|
|
6
|
-
* @exports uid2iid, iid4uid
|
|
7
|
-
* @todo Migrate this code into the WebAssembly module.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* @name uid2iid
|
|
12
|
-
* @description Binds a user identifier (UID) to an identity ID (IID) in IndexedDB.
|
|
13
|
-
* @param {string} uid The user identifier (UID) to bind.
|
|
14
|
-
* @param {string} iid The identity ID (IID) to bind to the UID.
|
|
15
|
-
* @throws {Error} If unable to bind UID to IID or if a DB Connection Error occurs.
|
|
16
|
-
* @returns {void}
|
|
17
|
-
*/
|
|
18
|
-
function uid2iid(uid, iid) {
|
|
19
|
-
const idbInstance = indexedDB.open("ideem", 1);
|
|
20
|
-
idbInstance.onerror = (err=idbInstance.error) => { throw new Error(`[IdentityIndexing] :: uid2iid :: Unable to bind UID to IID:\n${err}`); }
|
|
21
|
-
idbInstance.onsuccess = (e, db=e.target.result) => {
|
|
22
|
-
const zsmTbl = db.transaction("zsm", "readwrite");
|
|
23
|
-
const write = zsmTbl.objectStore("zsm").put(iid, uid);
|
|
24
|
-
write.onsuccess = () => (db.close());
|
|
25
|
-
write.onerror = (err=write.error) => (db.close(), console.error(`[IdentityIndexing] :: uid2iid :: Unable to bind UID to IID:\n${err}`));
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* @name iid4uid
|
|
31
|
-
* @description Finds the identity ID for a given user identifier (UID) in IndexedDB.
|
|
32
|
-
* @param {string} uid The user identifier (UID) to search for.
|
|
33
|
-
* @returns {Promise<string>} A promise that resolves to the identity ID (IID) associated with the UID, or the UID itself if not found.
|
|
34
|
-
* @throws {Error} If unable to search IndexedDB or if a DB Connection Error occurs.
|
|
35
|
-
*/
|
|
36
|
-
function iid4uid(uid) {
|
|
37
|
-
return new Promise((resolve, reject) => {
|
|
38
|
-
const idbInstance = indexedDB.open("ideem", 1);
|
|
39
|
-
idbInstance.onupgradeneeded = (e, db=e.target.result) => { if (!db.objectStoreNames.contains("zsm")) db.createObjectStore("zsm");};
|
|
40
|
-
idbInstance.onerror = (err=idbInstance.error) => { return reject(new Error(`[IdentityIndexing] :: iid4uid :: Unable to connect to IDB:\n${err}`)); }
|
|
41
|
-
idbInstance.onsuccess = (e, db=e.target.result) => {
|
|
42
|
-
if(!db.objectStoreNames.contains("zsm")) return resolve(uid);
|
|
43
|
-
const zsmTbl = db.transaction("zsm", "readonly");
|
|
44
|
-
const lookup = zsmTbl.objectStore("zsm").get(uid);
|
|
45
|
-
lookup.onsuccess = () => (db.close(), (lookup.result) ? resolve(lookup.result) : resolve(uid));
|
|
46
|
-
lookup.onerror = (err=lookup.error) => (db.close(), console.error(`[IdentityIndexing] :: iid4uid :: Unable to find IID for UID:\n${err}`), reject(new Error(`[IdentityIndexing] :: iid4uid :: Unable to find IID for UID:\n${err}`)));
|
|
47
|
-
};
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export { uid2iid, iid4uid };
|
|
1
|
+
function e(e,n){const r=indexedDB.open("ideem",1);r.onerror=(e=r.error)=>{throw new Error(`[IdentityIndexing] :: uid2iid :: Unable to bind UID to IID:\n${e}`)},r.onsuccess=(r,o=r.target.result)=>{const t=o.transaction("zsm","readwrite").objectStore("zsm").put(n,e);t.onsuccess=()=>o.close(),t.onerror=(e=t.error)=>(o.close(),console.error(`[IdentityIndexing] :: uid2iid :: Unable to bind UID to IID:\n${e}`))}}function n(e){return new Promise((n,r)=>{const o=indexedDB.open("ideem",1);o.onupgradeneeded=(e,n=e.target.result)=>{n.objectStoreNames.contains("zsm")||n.createObjectStore("zsm")},o.onerror=(e=o.error)=>r(new Error(`[IdentityIndexing] :: iid4uid :: Unable to connect to IDB:\n${e}`)),o.onsuccess=(o,t=o.target.result)=>{if(!t.objectStoreNames.contains("zsm"))return n(e);const i=t.transaction("zsm","readonly").objectStore("zsm").get(e);i.onsuccess=()=>(t.close(),i.result?n(i.result):n(e)),i.onerror=(e=i.error)=>(t.close(),console.error(`[IdentityIndexing] :: iid4uid :: Unable to find IID for UID:\n${e}`),r(new Error(`[IdentityIndexing] :: iid4uid :: Unable to find IID for UID:\n${e}`)))}})}export{e as uid2iid,n as iid4uid};
|
package/PKPUtils.js
CHANGED
|
@@ -1,61 +1 @@
|
|
|
1
|
-
|
|
2
|
-
@name decodeAttestationObject
|
|
3
|
-
@description Decodes a CBOR-encoded attestation object buffer into a JavaScript object (CBOR map of 3 entries (0xA3)) with properties 'fmt', 'attStmt', and 'authData'.
|
|
4
|
-
@param {ArrayBuffer} buffer The CBOR-encoded attestation object.
|
|
5
|
-
@returns {Object} The decoded attestation object with properties 'fmt', 'attStmt', and 'authData'.
|
|
6
|
-
@throws {Error} If the first byte does not indicate a CBOR map of 3 entries.
|
|
7
|
-
@throws {Error} If the major type of a value is unsupported.
|
|
8
|
-
@throws {Error} If the input buffer is not a valid CBOR-encoded attestation object.
|
|
9
|
-
*/
|
|
10
|
-
function decodeAttestationObject(buffer) {
|
|
11
|
-
try {
|
|
12
|
-
if (!buffer) // IF the buffer argument is missing...
|
|
13
|
-
throw new TypeError("Invalid input"); // ...throw an error
|
|
14
|
-
|
|
15
|
-
if (buffer instanceof Uint8Array) // IF the buffer argument is a Uint8Array...
|
|
16
|
-
buffer = buffer.buffer; // ...convert it to an ArrayBuffer
|
|
17
|
-
if (!(buffer instanceof ArrayBuffer)) // IF the buffer argument is (STILL) not an ArrayBuffer...
|
|
18
|
-
throw new TypeError("Invalid input"); // ...throw an error
|
|
19
|
-
|
|
20
|
-
if (!buffer || !(buffer instanceof ArrayBuffer)) // IF the buffer argument is missing or not an ArrayBuffer...
|
|
21
|
-
throw new TypeError("Invalid input"); // ...throw an error
|
|
22
|
-
const view = new DataView(buffer); // Create a DataView to read the buffer argument (CBOR is a binary format)
|
|
23
|
-
const attObj = {}; // Create an object to hold the decoded attestation object
|
|
24
|
-
let offset = 0; // Create an offset counter to track our position in the buffer
|
|
25
|
-
|
|
26
|
-
// Expect a CBOR map of 3 entries (0xA3)
|
|
27
|
-
const type = view.getUint8(offset++); // Read the first byte to determine the major type
|
|
28
|
-
if (type !== 0xA3) throw new Error("Unexpected format"); // Since we expect a map with 3 key-value pairs, throw an error if not found
|
|
29
|
-
|
|
30
|
-
for (let i = 0; i < 3; i++) { // Iterate over the 3 key-value pairs...
|
|
31
|
-
const keyLen = view.getUint8(offset++) - 0x60; // Read the length of the key (CBOR short string)
|
|
32
|
-
|
|
33
|
-
const key = new TextDecoder() // Isolate out the key by initializing a TextDecoder...
|
|
34
|
-
.decode(new Uint8Array(buffer, offset, keyLen)); // ...and decoding the buffered CBOR short string into it
|
|
35
|
-
|
|
36
|
-
offset += keyLen; // Move offset past the key by the length of the key
|
|
37
|
-
|
|
38
|
-
const majorType = view.getUint8(offset); // Read the major type of the value
|
|
39
|
-
|
|
40
|
-
offset++; // Move past the major type byte
|
|
41
|
-
|
|
42
|
-
if (majorType === 0x58) { // IF the major type's value is a byte string with length in next byte...
|
|
43
|
-
const len = view.getUint8(offset++); // ...read the length of the byte string...
|
|
44
|
-
attObj[key] = new Uint8Array(buffer, offset, len); // ...extract the byte string...
|
|
45
|
-
offset += len; // ...and increment offset by length.
|
|
46
|
-
} else if (majorType >= 0x60 && majorType < 0x78) { // OTHERWISE, if major type's value is a short string (for 'fmt')
|
|
47
|
-
const len = majorType - 0x60; // ...determine length from major type...
|
|
48
|
-
attObj[key] = new TextDecoder() // ...and initialize a TextDecoder...
|
|
49
|
-
.decode(new Uint8Array(buffer, offset, len)); // ...decoding the buffered CBOR short string into it
|
|
50
|
-
offset += len; // ...finally, increment offset by length
|
|
51
|
-
} else if (majorType === 0xA0) { // OTHERWISE, if major type's value is an empty map
|
|
52
|
-
attObj[key] = {}; // ...and assign an empty object
|
|
53
|
-
} else throw new Error("Unsupported type"); // OTHERWISE, throw an error
|
|
54
|
-
}
|
|
55
|
-
return attObj; // return the decoded attestation object
|
|
56
|
-
} catch (e) {
|
|
57
|
-
throw new Error("[Utils] :: decodeAttestationObject :: Failed to decode attestation object: " + e.message);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export { decodeAttestationObject };
|
|
1
|
+
function e(e){try{if(!e)throw new TypeError("Invalid input");if(e instanceof Uint8Array&&(e=e.buffer),!(e instanceof ArrayBuffer))throw new TypeError("Invalid input");if(!(e&&e instanceof ArrayBuffer))throw new TypeError("Invalid input");const t=new DataView(e),r={};let n=0;if(163!==t.getUint8(n++))throw new Error("Unexpected format");for(let o=0;o<3;o++){const o=t.getUint8(n++)-96,i=(new TextDecoder).decode(new Uint8Array(e,n,o));n+=o;const a=t.getUint8(n);if(n++,88===a){const o=t.getUint8(n++);r[i]=new Uint8Array(e,n,o),n+=o}else if(a>=96&&a<120){const t=a-96;r[i]=(new TextDecoder).decode(new Uint8Array(e,n,t)),n+=t}else{if(160!==a)throw new Error("Unsupported type");r[i]={}}}return r}catch(e){throw new Error("[Utils] :: decodeAttestationObject :: Failed to decode attestation object: "+e.message)}}export{e as decodeAttestationObject};
|