@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 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
- - A corner-case exists in which a developer, using both the core SDK packages as well as the Passkeys+ SDK, may encounter conflicts if a ZSM credential is re-enrolled after the Passkeys+ credential has been enrolled. Dev team is aware of this issue and a fix is forthcoming. A safeguard has been implemented to prevent this issue from occurring accidentally but, in the immediate term, developers are advised to avoid re-enrolling ZSM credentials after Passkeys+ credentials have been enrolled.
11
+ - None at this time.
13
12
 
14
13
  ----
15
-
16
- ## Release 2.5.0 - Aug 29, 2025 [ NPM PACKAGE RELEASE ] **[ LATEST ]**
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
@@ -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
@@ -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 = new Map(this.cleanState);
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 });
@@ -8,7 +8,7 @@ class FIDO2ClientBase {
8
8
  * @param {Object} config - The configuration for the FIDO2Client
9
9
  */
10
10
  constructor(config) {
11
- if(eventCoordinator.done) eventCoordinator.reset();
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/sdk/browser/index.html. The documentation covers interfaces provided for FIDO2 Authenticator and Universal MFA integrations.
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 RELEASE_NOTES.txt file in this directory for the release notes of this SDK.
37
+ Please refer to the CHANGELOG.md file in this directory for the release notes of this SDK.
@@ -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 }, body, this.addlRelyingPartyBodyProps, tracePrimer(true));
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
- return this.makePostRequest("api/webauthn/registration/start")
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((error instanceof Error) ? error : new Error(error)));
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("api/webauthn/registration/finish", { credential })
115
- .then(() => (this.credentialID = credential.rawId, credential))
116
- .then(credential => credential)
117
- .catch(error => (this.clearEnrollmentCredentials(), Promise.reject((error instanceof Error) ? error : new Error(error))))
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( "api/webauthn/authentication/start", { credential_id })
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((error instanceof Error) ? error : new Error(error)));
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
- return this.makePostRequest("api/webauthn/registration/create-identity-then-start")
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((error instanceof Error) ? error : new Error(error)));
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("api/webauthn/registration/finish-then-authentication-start", { credential })
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((error instanceof Error) ? error : new Error(error))));
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
- return await this.zsmAPI.webauthnRetrieve(userIdentifier);
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
- try {
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
- return new Error(e.message || e || '[UMFAClient] :: enroll :: An error occurred during enrollment.');
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
  /**