@ideem/zsm-client-sdk 2.5.1 → 2.6.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 +22 -6
- package/ErrorHandler.js +154 -0
- package/EventCoordinator.js +28 -1
- package/FIDO2ClientBase.js +2 -1
- package/README.md +2 -2
- package/RelyingPartyBase.js +38 -18
- package/UMFAClientBase.js +15 -7
- package/WASMRustInterface.js +448 -357
- package/WebAuthnClientBase.js +153 -83
- package/package.json +68 -21
package/CHANGELOG.md
CHANGED
|
@@ -1,19 +1,35 @@
|
|
|
1
|
-
ZSM Client SDK for Browser Apps
|
|
2
|
-
Latest version: v2.5.0
|
|
3
|
-
Latest release date: Aug 29, 2025
|
|
1
|
+
# ZSM Client SDK for Browser Apps
|
|
4
2
|
|
|
5
3
|
The ZSM Client SDK for Browser Apps provides the resources needed to integrate Ideem's FIDO2 Authenticator
|
|
6
4
|
and Universal MFA into a browser application.
|
|
7
5
|
|
|
8
6
|
## Resources:
|
|
7
|
+
- Documentation: https://docs.useideem.com/pkp
|
|
9
8
|
- Support: support@useideem.com
|
|
10
9
|
|
|
11
10
|
## Known Issues
|
|
12
|
-
-
|
|
11
|
+
- None at this time.
|
|
13
12
|
|
|
14
13
|
----
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
## Release 2.6.1 - December 16, 2025 [ NPM PACKAGE RELEASE ] **[ LATEST ]**
|
|
15
|
+
- Minor bug fixes and improvements
|
|
16
|
+
- Minor alteration to initialization process to remove the need to provide a `userIdentifier` when initializing (all method calls now provide the update).
|
|
17
|
+
- Improvements to EventCoordinator:
|
|
18
|
+
- Streamlined onReset to ensure that all events are set to "PENDING" state.
|
|
19
|
+
- Improved onReady handling to ensure accurate state representation.
|
|
20
|
+
- Enhanced error handling and reporting mechanisms.
|
|
21
|
+
- Improvements to NPM packaging and distribution process for use with additional package managers and build tools.
|
|
22
|
+
- Reduced complexity of `zsm_app_config.json` file by:
|
|
23
|
+
- Removing _necessity_ of `use_passkeys` and `use_origin` properties (now auto-detected based on environment and browser capabilities).
|
|
24
|
+
- Simplifying host URL configuration down to a `region` property; host URLs are now auto-generated based on region although previous syntax is still supported for backward compatibility.
|
|
25
|
+
|
|
26
|
+
## Release 2.6.0 - November 26, 2025 [ NPM PACKAGE RELEASE ]
|
|
27
|
+
- Support of updated Passkeys plugin that provides migration scenarios for ZSM to Passkeys+ and boostrapping of new device using existing passkey.
|
|
28
|
+
|
|
29
|
+
## Release 2.5.1 - October 6, 2025 [ NPM PACKAGE RELEASE ]
|
|
30
|
+
- Minor improvements
|
|
31
|
+
|
|
32
|
+
## Release 2.5.0 - Aug 29, 2025 [ NPM PACKAGE RELEASE ]
|
|
17
33
|
- Significant re-architecture of the SDK to improve performance and maintainability (non-breaking, but may alter existing implementations, if plugins are used)
|
|
18
34
|
- Introduction of a new plugin system for easier integration of additional Ideem services
|
|
19
35
|
- Plugin system allows for dynamic loading and unloading of features
|
package/ErrorHandler.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file zsm-client-sdk/ErrorHandler.js
|
|
3
|
+
* @name ErrorHandler
|
|
4
|
+
* @description A centralized error handling singleton for managing sequential errors in ZSM client SDK operations, intended to facilitate self-healing from repeated Relying Party server operation errors.
|
|
5
|
+
* @param {ZSMAPI} zsmAPIInstance An optional instance of a ZSM API (e.g., WebAuthnClient) to facilitate local credential purging.
|
|
6
|
+
* @setter {ZSMAPI} ERR.zsmAPI Sets the ZSM API instance (WebAuthnClient, etc.) to facilitate local credential purging.
|
|
7
|
+
* @setter {string} ERR.userIdentifier Sets the active userIdentifier (used for local credential purging).
|
|
8
|
+
* @method ERR.error(errType, message, options) Generates & returns an Error object, increments sequential error count, and triggers individual or global IDB record purging (by count)
|
|
9
|
+
* @method ERR.reset() Resets the sequential error count to zero.
|
|
10
|
+
* @exports ERR An instance of the ErrorHandler singleton.
|
|
11
|
+
*
|
|
12
|
+
* @notes IMPORTANT: If unable to unbind the active user at 3+ errors, or either setter value is unset, OR the count reaches 5, the entire ideem IndexedDB gets purged.
|
|
13
|
+
*
|
|
14
|
+
* @notes IMPORTANT: the `reset()` method MUST be called to reset the sequential error count upon successful trannsaction with the RP (see: makePostRequest in RelyingPartyBase.js).
|
|
15
|
+
*
|
|
16
|
+
* @notes IMPORTANT: This class relies on a valid userIdentifier and zsmAPIInstance being set PRIOR to invoking the error() method to facilitate local credential purging when necessary.
|
|
17
|
+
* @example ERR.zsmAPI = webAuthnClientInstance; // Set the ZSM API instance (e.g., WebAuthnClient)
|
|
18
|
+
* @example ERR.userIdentifier = 'user123'; // Set the active userIdentifier
|
|
19
|
+
* @example throw new Error("An example error."); // Generate and throw an error via the ErrorHandler
|
|
20
|
+
*
|
|
21
|
+
* @notes The sequential error count is persisted in sessionStorage to survive page reloads within the same browser session.
|
|
22
|
+
*
|
|
23
|
+
*/
|
|
24
|
+
class ErrorHandler {
|
|
25
|
+
#sequentialErrorCount; // Number of sequential errors encountered
|
|
26
|
+
#zsmAPIInstance = null; // Instance of ZSM API (e.g., WebAuthnClient)
|
|
27
|
+
#activeUserIdentifier = null; // Currently active userIdentifier
|
|
28
|
+
|
|
29
|
+
constructor(zsmAPIInstance=null) {
|
|
30
|
+
try { this.#sequentialErrorCount = Number(sessionStorage.getItem('zsmSequentialErrorCt') || 0); } // Try to retrieve sequential error count from sessionStorage...
|
|
31
|
+
catch { this.#sequentialErrorCount = 0; } // ...and roll over to 0 if unable to do so (or it's absent)
|
|
32
|
+
this.#zsmAPIInstance = zsmAPIInstance || null; // Set ZSM API instance if provided, else null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get #count() { return this.#sequentialErrorCount; } // Get sequential error count
|
|
36
|
+
set #count(v) { try { sessionStorage.setItem('zsmSequentialErrorCt', (this.#sequentialErrorCount = v)); }catch(e){} } // Set sequential error count and store in sessionStorage
|
|
37
|
+
get #userIdentifier() { return this.#activeUserIdentifier; } // Get active userIdentifier
|
|
38
|
+
set userIdentifier(id) { return (this.#activeUserIdentifier = id); } // Set active userIdentifier
|
|
39
|
+
get #zsmAPI() { return this.#zsmAPIInstance; } // Get ZSM API instance
|
|
40
|
+
set zsmAPI(instance) { return (this.#zsmAPIInstance = instance); } // Set ZSM API instance
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @name purgeLocalCredentialForActiveUser
|
|
44
|
+
* @description Attempts to purge the local ZSM credential data for the currently active userIdentifier via the associated ZSM API instance. Failing that, purges the entire local IndexedDB database.
|
|
45
|
+
* @throws {Promise<void>} Rollover functionality (full reset) is triggered if userIdentifier or zsmAPIInstance is unset, or unable to unbind active user
|
|
46
|
+
* @returns {Promise<void>}
|
|
47
|
+
* @memberOf ErrorHandler
|
|
48
|
+
* @private
|
|
49
|
+
* @async
|
|
50
|
+
*/
|
|
51
|
+
async #purgeLocalCredentialForActiveUser() {
|
|
52
|
+
try {
|
|
53
|
+
|
|
54
|
+
const uid = this.#userIdentifier; // Use the active userIdentifier...
|
|
55
|
+
if(!uid || uid === '') throw new Error(`No active userIdentifier is set on this ErrorHandler.`); // ...or throw an error if not set
|
|
56
|
+
if(this.#zsmAPI == null) throw new Error(`No ZSM API instance is associated with this ErrorHandler.`); // Throw an error if no WebAuthnClient instance is yet set
|
|
57
|
+
await this.#zsmAPI.unbindFromDevice(uid); // Attempt to unenroll the active user from the device via the WebAuthnClient
|
|
58
|
+
console.info(`[ErrorHandler] :: Local ZSM credential data purged for ${uid}`);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
console.warn(new Error(`[ErrorHandler] :: purgeLocalCredentialForActiveUser :: ${err.message}.`, err));
|
|
61
|
+
console.warn(`\n\nBecause local credential purge failed, attempting to purge entire local IndexedDB instead.`);
|
|
62
|
+
this.#purgeLocalIDB(); // If unable to unbind the active user, purge the entire IndexedDB instead
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @name purgeLocalIDB
|
|
68
|
+
* @description Attempts to purge the entire local IndexedDB database named 'ideem', removing all credential and ZSM data.
|
|
69
|
+
* @returns {void}
|
|
70
|
+
* @memberOf ErrorHandler
|
|
71
|
+
* @private
|
|
72
|
+
*/
|
|
73
|
+
#purgeLocalIDB() {
|
|
74
|
+
try {
|
|
75
|
+
indexedDB.deleteDatabase('ideem'); // Delete the entire 'ideem' IndexedDB database
|
|
76
|
+
console.info(`[ErrorHandler] :: Local IndexedDB purged of all credential and ZSM data.`);
|
|
77
|
+
} catch (err) {
|
|
78
|
+
console.error(`[ErrorHandler] :: purgeLocalIDB :: Unable to purge local IndexedDB (usually this is due to the local store being absent).\nDetails:\n`, err);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @name processErrorCountsForGeneratedError
|
|
84
|
+
* @description Processes the sequential error count after an error has been generated, triggering local credential or IndexedDB purging as necessary.
|
|
85
|
+
* @param {Error} errOP The newly-generated or amended error object.
|
|
86
|
+
* @returns {Error} The same error object passed in, for chaining.
|
|
87
|
+
* @memberOf ErrorHandler
|
|
88
|
+
* @private
|
|
89
|
+
*/
|
|
90
|
+
#processErrorCountsForGeneratedError(errOP) {
|
|
91
|
+
if(this.#count === 3) { // IF sequential error count is exactly 3...
|
|
92
|
+
this.#purgeLocalCredentialForActiveUser(); // ...purge local credential for active user
|
|
93
|
+
} else if(this.#count >= 5) { // ELSE IF sequential error count is 5 or more...
|
|
94
|
+
this.#purgeLocalIDB(); // ...purge entire local IndexedDB
|
|
95
|
+
this.reset(); // ...reset the sequential error count
|
|
96
|
+
}
|
|
97
|
+
return errOP; // Return the [new|amended] error object
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @name reset
|
|
102
|
+
* @description Resets the sequential error count to zero and persists the change in sessionStorage.
|
|
103
|
+
* @param {...any} args Optional arguments to pass through transparently.
|
|
104
|
+
* @returns {void|any|Array<any>} For 0 args: undefined; 1 arg: that arg; multiple args: array of those args.
|
|
105
|
+
* @memberOf ErrorHandler
|
|
106
|
+
*/
|
|
107
|
+
reset(...args) {
|
|
108
|
+
this.#count = 0; // Reset the sequential error count and store it in sessionStorage
|
|
109
|
+
console.info(`[ErrorHandler] :: reset :: Sequential Error Count reset to 0.`); // Log the reset action
|
|
110
|
+
return (typeof args === 'array' && args.length === 0) ? void(0) // Pass through received arguments transparently, if any
|
|
111
|
+
: (typeof args === 'array' && args.length === 1) ? args[0]
|
|
112
|
+
: args;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @name error
|
|
117
|
+
* @description Generates & returns an Error object, increments sequential error count, and triggers individual or global IDB record purging (by count)
|
|
118
|
+
* @param {function|Error} errType Either an Error constructor function (e.g., Error, TypeError; counted) or existing Error object to amend (not counted).
|
|
119
|
+
* @param {string} message The error message to associate with the generated or amended Error object.
|
|
120
|
+
* @param {Object} options Optional additional options to pass to the Error constructor.
|
|
121
|
+
* @returns {Error} The newly-generated or amended Error object.
|
|
122
|
+
* @memberOf ErrorHandler
|
|
123
|
+
* @notes This method is designed to be resilient as hell; if anything's wrong with its invocation, it will STILL return a valid Error object (albeit a generic one).
|
|
124
|
+
*/
|
|
125
|
+
error(errType=Error, message="An error occurred.", options={}) {
|
|
126
|
+
let errOP;
|
|
127
|
+
try {
|
|
128
|
+
if(typeof message !== 'string') message = "An error occurred."; // Ensure message is a string (or supplant it with a default)
|
|
129
|
+
if(options == null || typeof options !== 'object') options = {}; // Ensure options is an object (or supplant it with an empty one)
|
|
130
|
+
if(typeof errType === 'string') { // [NEW ERROR] Only the error message was provided as a string...
|
|
131
|
+
message = errType; // ...so shift it into the message...
|
|
132
|
+
errType = Error; // ...and set errType to the generic Error constructor
|
|
133
|
+
} else if(typeof errType === 'function') { // [NEW ERROR] A constructor function was provided
|
|
134
|
+
errOP = new errType(message, options); // Attempt to create the error object
|
|
135
|
+
if(!(errOP instanceof Error)) { // IF the newly-created object is NOT an Error...
|
|
136
|
+
errOP = new Error(message, options); // ...replace it with a generic Error object (still with message & options)
|
|
137
|
+
}
|
|
138
|
+
this.#count = this.#count + 1; // Increment the sequential error count
|
|
139
|
+
} else if (errType instanceof Error) { // [EXISTING ERROR] An existing error object was provided
|
|
140
|
+
errOP = new errType.constructor(message, options); // Create a new Error object of the previous's prototype...
|
|
141
|
+
errOP.cause = errType; // ...and leverage its cause to preserve the causal stack trace
|
|
142
|
+
} else { // [INVALID INPUT] Neither a constructor function nor an existing error object was provided
|
|
143
|
+
throw new Error(`Invalid errType provided to ErrorHandler.error(): must be either an Error constructor function or an existing Error object.`);
|
|
144
|
+
}
|
|
145
|
+
return this.#processErrorCountsForGeneratedError(errOP); // Post-process the count and return the [new|amended] error object
|
|
146
|
+
}catch(e) {
|
|
147
|
+
this.#count = this.#count + 1; // Increment the sequential error count
|
|
148
|
+
errOP = new Error(`[ErrorHandler] :: error :: An error occurred while attempting to process another error.\nDetails:\n${e.message}\n\nOriginal Error Message:\n${message}`);
|
|
149
|
+
return this.#processErrorCountsForGeneratedError(errOP); // Post-process the count and return the rollover hail mary error object
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const ERR = new ErrorHandler(); // Export a singleton instance of the ErrorHandler
|
|
154
|
+
export default ERR
|
package/EventCoordinator.js
CHANGED
|
@@ -52,6 +52,13 @@ class EventCoordinator {
|
|
|
52
52
|
set coreReady(value) { this.coreSDKClass.ready = !!value; }
|
|
53
53
|
set coreErrs(value) { this.coreSDKClass.containsErrors = !!value; }
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* @name setCoreSDKClass
|
|
57
|
+
* @description Sets the core SDK class reference on the EventCoordinator instance, and defines its "finished" method.
|
|
58
|
+
* @param {string} classDesc The name of the client class being initialized (UMFAClient or FIDO2Client).
|
|
59
|
+
* @param {Object} classRef The reference to the core SDK class being initialized.
|
|
60
|
+
* @returns {void}
|
|
61
|
+
*/
|
|
55
62
|
setCoreSDKClass(classDesc, classRef) {
|
|
56
63
|
const core = (this.coreSDKClass = classRef); // Set a reference to the core class on the singleton instance, then...
|
|
57
64
|
core.ready = false; // ... set its default ready state to false
|
|
@@ -63,6 +70,12 @@ class EventCoordinator {
|
|
|
63
70
|
this.specifyClient(classDesc); // ... finally, specify the client class that is being initialized.
|
|
64
71
|
}
|
|
65
72
|
|
|
73
|
+
/**
|
|
74
|
+
* @name specifyClient
|
|
75
|
+
* @description Specifies the active client class (UMFAClient or FIDO2Client) being initialized.
|
|
76
|
+
* @param {string} selectedClient The name of the client class being initialized (UMFAClient or FIDO2Client).
|
|
77
|
+
* @returns {void}
|
|
78
|
+
*/
|
|
66
79
|
specifyClient(selectedClient) {
|
|
67
80
|
if (!selectedClient || typeof selectedClient !== 'string' || !selectedClient.length)
|
|
68
81
|
throw new TypeError(`[ZSM EventCoordinator] :: specifyClient :: Invalid 'selectedClient' value: "${selectedClient}". 'selectedClient' is required and expects a String.`);
|
|
@@ -74,6 +87,12 @@ class EventCoordinator {
|
|
|
74
87
|
}
|
|
75
88
|
}
|
|
76
89
|
|
|
90
|
+
/**
|
|
91
|
+
* @name summarizeEventStack
|
|
92
|
+
* @description Logs a summary of the current event stack to the console.
|
|
93
|
+
* @param {...any} triggerData Additional data to include in the log (e.g., the event that triggered the update).
|
|
94
|
+
* @returns {void}
|
|
95
|
+
*/
|
|
77
96
|
summarizeEventStack (...triggerData) {
|
|
78
97
|
let summaryObject = this.object,
|
|
79
98
|
{ client, containsErrors, done } = this;
|
|
@@ -102,6 +121,14 @@ class EventCoordinator {
|
|
|
102
121
|
GLOBAL.dispatchEvent(new CustomEvent(eventName, { detail: eventDetail }));
|
|
103
122
|
}
|
|
104
123
|
|
|
124
|
+
/**
|
|
125
|
+
* @name update
|
|
126
|
+
* @description Updates the status of a specific event in the event stack.
|
|
127
|
+
* @param {string} classDesc The name of the event to update.
|
|
128
|
+
* @param {string} eventStatus The new status for the event (accepts: UNREGISTERED, PENDING, READY, ERROR).
|
|
129
|
+
* @throws {TypeError} If the classDesc is invalid or eventStatus is not one of the allowed values.
|
|
130
|
+
* @returns {Object} Returns the updated event stack object.
|
|
131
|
+
*/
|
|
105
132
|
update (classDesc, eventStatus="PENDING") {
|
|
106
133
|
if (!classDesc || typeof classDesc !== 'string' || !classDesc.length)
|
|
107
134
|
throw new TypeError(`[ZSM EventCoordinator] :: update :: Invalid 'classDesc' value: "${classDesc}". 'classDesc' is required and expects a String.`);
|
|
@@ -175,7 +202,7 @@ class EventCoordinator {
|
|
|
175
202
|
* @returns {Object} Returns the reset event stack object.
|
|
176
203
|
*/
|
|
177
204
|
reset() {
|
|
178
|
-
this._eventStack
|
|
205
|
+
this._eventStack.keys().forEach(key=>this._eventStack.set(key,'PENDING'));
|
|
179
206
|
if(window && window?.queueMicrotask) {
|
|
180
207
|
queueMicrotask(() => this.dispatchEvent('ZSMClientEventStackReset', { stack: this.object }));
|
|
181
208
|
} else this.dispatchEvent('ZSMClientEventStackReset', { stack: this.object });
|
package/FIDO2ClientBase.js
CHANGED
|
@@ -8,7 +8,7 @@ class FIDO2ClientBase {
|
|
|
8
8
|
* @param {Object} config - The configuration for the FIDO2Client
|
|
9
9
|
*/
|
|
10
10
|
constructor(config) {
|
|
11
|
-
|
|
11
|
+
eventCoordinator.reset();
|
|
12
12
|
eventCoordinator.update('FIDO2ClientBase');
|
|
13
13
|
this.config = config;
|
|
14
14
|
const WebAuthnClient = zsmPluginManager.classes('WEBAUTHNCLIENT');
|
|
@@ -103,6 +103,7 @@ class FIDO2ClientBase {
|
|
|
103
103
|
* @memberOf FIDO2ClientBase
|
|
104
104
|
*/
|
|
105
105
|
async webauthnGet (userIdentifier) {
|
|
106
|
+
|
|
106
107
|
try {
|
|
107
108
|
if(userIdentifier == null
|
|
108
109
|
|| userIdentifier === '') throw new Error(`[FIDO2Client] :: webauthnGet :: A userIdentifier String is required! Received: ${userIdentifier}.`);
|
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ TypeScript. These resources include documentation and example applications.
|
|
|
19
19
|
Ideem support, <a href="mailto:support@useideem.com">support@useideem.com</a>, to obtain a unique Application ID.
|
|
20
20
|
|
|
21
21
|
### Documentation
|
|
22
|
-
Documentation for this SDK can be found at: https://docs.useideem.com/
|
|
22
|
+
Documentation for this SDK can be found at: https://docs.useideem.com/pkp. The documentation covers interfaces provided for FIDO2 Authenticator and Universal MFA integrations.
|
|
23
23
|
|
|
24
24
|
### Demos
|
|
25
25
|
You can view demos of the FIDO2 Authenticator and Universal MFA Capabilities online at <a href="https://demo.useideem.com">https://demo.useideem.com</a>.
|
|
@@ -34,4 +34,4 @@ You can receive support and get your questions answered by emailing <a href="mai
|
|
|
34
34
|
Please refer to the LICENSE file in this directory for the license governing the use of this SDK.
|
|
35
35
|
|
|
36
36
|
### Release Notes
|
|
37
|
-
Please refer to the
|
|
37
|
+
Please refer to the CHANGELOG.md file in this directory for the release notes of this SDK.
|
package/RelyingPartyBase.js
CHANGED
|
@@ -2,6 +2,7 @@ import eventCoordinator from './EventCoordinator.js';
|
|
|
2
2
|
import GLOBAL from './GlobalScoping.js';
|
|
3
3
|
import {uid2iid} from './IdentityIndexing.js';
|
|
4
4
|
|
|
5
|
+
|
|
5
6
|
eventCoordinator.update('RelyingPartyBase');
|
|
6
7
|
|
|
7
8
|
function tracePrimer (obj=false) {
|
|
@@ -33,7 +34,7 @@ class RelyingPartyBase {
|
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
get userIdentifier() { return this.customerDefinedIdentifier; }
|
|
36
|
-
set userIdentifier(v) { return this.customerDefinedIdentifier = v; }
|
|
37
|
+
set userIdentifier(v) { return userIdentifier = this.customerDefinedIdentifier = v; }
|
|
37
38
|
|
|
38
39
|
/**
|
|
39
40
|
* @name makePostRequest
|
|
@@ -48,7 +49,7 @@ class RelyingPartyBase {
|
|
|
48
49
|
url = `${this.host.endsWith('/') ? this.host.slice(0,-1) : this.host}/${url.startsWith('/') ? url.slice(1) : url}`;
|
|
49
50
|
body = ((typeof body === 'object') ? body : {});
|
|
50
51
|
if(this.appEnvironment) body = Object.assign(body, {environment: this.appEnvironment});
|
|
51
|
-
body = Object.assign({ customer_defined_identifier : this.customerDefinedIdentifier, application_id : this.applicationId
|
|
52
|
+
body = Object.assign({ customer_defined_identifier : this.customerDefinedIdentifier, application_id : this.applicationId}, body, this.addlRelyingPartyBodyProps, tracePrimer(true));
|
|
52
53
|
let fetchObj = {
|
|
53
54
|
method,
|
|
54
55
|
headers : Object.assign(this.xhrHeaders, ((!headers?.Authorization) ? {'Authorization': `Bearer ${this.apiKey}`} : {}), headers),
|
|
@@ -92,8 +93,10 @@ class RelyingPartyBase {
|
|
|
92
93
|
* @returns {Promise<Object>} Resolves with publicKey object used in the subsequent Crypto Server API call.
|
|
93
94
|
*/
|
|
94
95
|
registrationStart () {
|
|
95
|
-
|
|
96
|
+
|
|
97
|
+
return this.makePostRequest(`api/webauthn/registration/start`)
|
|
96
98
|
.then(data => {
|
|
99
|
+
|
|
97
100
|
if(data.identity_id){
|
|
98
101
|
GLOBAL.mpcConfig.consumer_id = data.identity_id;
|
|
99
102
|
uid2iid(this.userIdentifier, data.identity_id);
|
|
@@ -101,7 +104,7 @@ class RelyingPartyBase {
|
|
|
101
104
|
return (this.publicKey = data.ccr.publicKey)
|
|
102
105
|
})
|
|
103
106
|
.then(result => result)
|
|
104
|
-
.catch(error => Promise.reject(
|
|
107
|
+
.catch(error => Promise.reject(new Error(error)));
|
|
105
108
|
}
|
|
106
109
|
/**
|
|
107
110
|
* @name registrationFinish
|
|
@@ -111,10 +114,12 @@ class RelyingPartyBase {
|
|
|
111
114
|
* @throws {Error} Throws an error if the request fails.
|
|
112
115
|
*/
|
|
113
116
|
registrationFinish (credential) {
|
|
114
|
-
return this.makePostRequest(
|
|
115
|
-
.then(
|
|
116
|
-
|
|
117
|
-
|
|
117
|
+
return this.makePostRequest(`api/webauthn/registration/finish`, { credential })
|
|
118
|
+
.then(body => {
|
|
119
|
+
this.credentialID = credential.rawId;
|
|
120
|
+
return {credential, response: body, ...body};
|
|
121
|
+
})
|
|
122
|
+
.catch(error => (this.clearEnrollmentCredentials(), Promise.reject(new Error(error))));
|
|
118
123
|
|
|
119
124
|
}
|
|
120
125
|
|
|
@@ -125,13 +130,21 @@ class RelyingPartyBase {
|
|
|
125
130
|
* @returns {Promise<Object>} Resolves with the authentication challenge.
|
|
126
131
|
* @throws {Error} Throws an error if the request fails.
|
|
127
132
|
*/
|
|
128
|
-
authenticationStart (credential_id=this.credentialID) {
|
|
133
|
+
authenticationStart (credential_id=this.credentialID, credential_type = 'passkey', chain_handoff, chain_credential_id, chain_challenge) {
|
|
134
|
+
const body = {
|
|
135
|
+
credential_type,
|
|
136
|
+
credential_id,
|
|
137
|
+
chain_handoff,
|
|
138
|
+
chain_credential_id,
|
|
139
|
+
chain_challenge,
|
|
140
|
+
}
|
|
129
141
|
return (credential_id != null)
|
|
130
|
-
? this.makePostRequest(
|
|
142
|
+
? this.makePostRequest( `api/webauthn/authentication/start`, { credential_id })
|
|
131
143
|
.then(result => result)
|
|
132
|
-
: (Promise.reject(new Error("[RelyingParty] :: authenticationStart :: No user ID or credential ID provided")))
|
|
144
|
+
: (Promise.reject(new Error("[RelyingParty] :: authenticationStart :: No user ID or credential ID provided")));
|
|
133
145
|
}
|
|
134
146
|
|
|
147
|
+
|
|
135
148
|
/**
|
|
136
149
|
* @name authenticationFinish
|
|
137
150
|
* @description Completes the authentication process by sending the credential to the server.
|
|
@@ -142,17 +155,24 @@ class RelyingPartyBase {
|
|
|
142
155
|
authenticationFinish (credential) {
|
|
143
156
|
return this.makePostRequest("api/webauthn/authentication/finish", { credential })
|
|
144
157
|
.then(result => result)
|
|
145
|
-
.catch(error => Promise.reject(
|
|
158
|
+
.catch(error => Promise.reject(new Error(error)));
|
|
146
159
|
}
|
|
147
160
|
|
|
161
|
+
|
|
148
162
|
/**
|
|
149
163
|
* @name createIdentityThenRegistrationStart
|
|
150
164
|
* @description UMFA-OPTIMIZED FLOW: Creates a server-side Identity and then starts the WebAuthn Registration challenge automatically.
|
|
151
165
|
* @returns {Promise<Object>} Resolves with an object containing the publicKey and identity_id.
|
|
152
166
|
* @throws {Error} Throws an error if the request fails.
|
|
153
167
|
*/
|
|
154
|
-
createIdentityThenRegistrationStart() {
|
|
155
|
-
|
|
168
|
+
createIdentityThenRegistrationStart(credential_type = 'zsm', chain_handoff, chain_credential_id, chain_challenge) {
|
|
169
|
+
const body = {
|
|
170
|
+
credential_type,
|
|
171
|
+
chain_handoff,
|
|
172
|
+
chain_credential_id,
|
|
173
|
+
chain_challenge,
|
|
174
|
+
};
|
|
175
|
+
return this.makePostRequest("api/webauthn/registration/create-identity-then-start", body)
|
|
156
176
|
.then(data => {
|
|
157
177
|
if(data.identity_id){
|
|
158
178
|
GLOBAL.mpcConfig.consumer_id = data.identity_id;
|
|
@@ -161,7 +181,7 @@ class RelyingPartyBase {
|
|
|
161
181
|
return {publicKey:(this.publicKey = data.ccr.publicKey), identity_id:data.identity_id}
|
|
162
182
|
})
|
|
163
183
|
.then(result => result)
|
|
164
|
-
.catch(error => Promise.reject(
|
|
184
|
+
.catch(error => Promise.reject(new Error(error)));
|
|
165
185
|
}
|
|
166
186
|
|
|
167
187
|
/**
|
|
@@ -172,10 +192,10 @@ class RelyingPartyBase {
|
|
|
172
192
|
* @throws {Error} Throws an error if the request fails.
|
|
173
193
|
*/
|
|
174
194
|
registrationFinishAuthenticationStart (credential) {
|
|
175
|
-
return this.makePostRequest(
|
|
195
|
+
return this.makePostRequest(`api/webauthn/registration/finish-then-authentication-start`, { credential })
|
|
176
196
|
.then(result => (this.credentialID = credential.rawId, result))
|
|
177
197
|
.then(credential => credential)
|
|
178
|
-
.catch(error => (this.clearEnrollmentCredentials(), Promise.reject(
|
|
198
|
+
.catch(error => (this.clearEnrollmentCredentials(), Promise.reject(new Error(error))));
|
|
179
199
|
}
|
|
180
200
|
|
|
181
201
|
/**
|
|
@@ -189,7 +209,7 @@ class RelyingPartyBase {
|
|
|
189
209
|
createNewIdentity = createNewIdentity ? { create_new_identity: "true" } : {};
|
|
190
210
|
return this.makePostRequest("api/umfa/check-identity", createNewIdentity)
|
|
191
211
|
.then(result => result)
|
|
192
|
-
.catch(error => Promise.reject(error));
|
|
212
|
+
.catch(error => Promise.reject(new Error(error)));
|
|
193
213
|
}
|
|
194
214
|
}
|
|
195
215
|
//## END RelyingPartyBase =====================================================================================================================================
|
package/UMFAClientBase.js
CHANGED
|
@@ -8,6 +8,7 @@ class UMFAClientBase {
|
|
|
8
8
|
* @param {Object} config - The configuration for ZSM initialization (ref: ./zsm_app_config.json)
|
|
9
9
|
*/
|
|
10
10
|
constructor(config) {
|
|
11
|
+
eventCoordinator.reset();
|
|
11
12
|
eventCoordinator.update('UMFAClientBase');
|
|
12
13
|
this.config = config;
|
|
13
14
|
const WebAuthnClient = zsmPluginManager.classes('WEBAUTHNCLIENT');
|
|
@@ -37,16 +38,19 @@ class UMFAClientBase {
|
|
|
37
38
|
* @name checkEnrollment
|
|
38
39
|
* @description Checks if a ZSM credential is enrolled on the device for the specified user
|
|
39
40
|
* @param {string} userIdentifier The identifier for the user
|
|
41
|
+
* @param {string} targetData The type of data to retrieve ('id' for credential ID, 'all' for full details)
|
|
40
42
|
* @returns {Promise<Object>|boolean} Resolves with the enrollment details or false if not enrolled, or an error if checking fails
|
|
41
43
|
* @throws {Error} If the userIdentifier is not provided or is empty
|
|
42
44
|
* @throws {Error} If an error occurs while checking enrollment
|
|
43
45
|
* @memberOf UMFAClientBase
|
|
44
46
|
*/
|
|
45
|
-
async checkEnrollment(userIdentifier) {
|
|
47
|
+
async checkEnrollment(userIdentifier, targetData='id') {
|
|
46
48
|
try {
|
|
47
49
|
if(userIdentifier == null
|
|
48
50
|
|| userIdentifier === '') throw new Error(`[UMFAClient] :: checkEnrollment :: A userIdentifier String is required! Received: ${userIdentifier}.`);
|
|
49
|
-
|
|
51
|
+
const enrollmentStatus = await this.zsmAPI.webauthnRetrieve(userIdentifier, targetData);
|
|
52
|
+
|
|
53
|
+
return enrollmentStatus;
|
|
50
54
|
} catch(e) {
|
|
51
55
|
return new Error(e.message || e || '[UMFAClient] :: checkEnrollment :: An error occurred while attempting to check enrollment.');
|
|
52
56
|
}
|
|
@@ -63,16 +67,20 @@ class UMFAClientBase {
|
|
|
63
67
|
* @memberOf UMFAClientBase
|
|
64
68
|
*/
|
|
65
69
|
async enroll(userIdentifier) {
|
|
66
|
-
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
// try {
|
|
67
73
|
if(userIdentifier == null
|
|
68
74
|
|| userIdentifier === '') throw new Error(`[UMFAClient] :: enroll :: A userIdentifier String is required! Received: ${userIdentifier}.`);
|
|
69
|
-
await this.zsmAPI.checkIdentity(userIdentifier, true);
|
|
75
|
+
const resultOfCheck = await this.zsmAPI.checkIdentity(userIdentifier, true);
|
|
76
|
+
|
|
70
77
|
const createResult = await this.zsmAPI.webauthnCreate(userIdentifier);
|
|
78
|
+
|
|
71
79
|
if(createResult instanceof Error) throw(`[UMFAClient] :: enroll :: Unable to associate ${userIdentifier}'s identity with device profile.\nDetails:\n${createResult}`);
|
|
72
80
|
return createResult;
|
|
73
|
-
}catch(e) {
|
|
74
|
-
|
|
75
|
-
}
|
|
81
|
+
// }catch(e) {
|
|
82
|
+
// return new Error(e.message || e || '[UMFAClient] :: enroll :: An error occurred during enrollment.');
|
|
83
|
+
// }
|
|
76
84
|
}
|
|
77
85
|
|
|
78
86
|
/**
|