@ideem/zsm-client-sdk 2.6.2 → 2.7.0
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/EventCoordinator.js +3 -2
- package/PluginManager.js +17 -16
- package/RelyingPartyBase.js +3 -0
- package/UMFAClientBase.js +1 -1
- package/WebAuthnClientBase.js +8 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -11,7 +11,18 @@ and Universal MFA into a browser application.
|
|
|
11
11
|
- None at this time.
|
|
12
12
|
|
|
13
13
|
----
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
## Release 2.7.0 - January 15, 2026 [ NPM PACKAGE RELEASE ] **[ LATEST ]**
|
|
16
|
+
- Minor bug fixes and improvements
|
|
17
|
+
- Standardization of credential responses across all SDK platforms (browser, mobile, native)
|
|
18
|
+
- Standardization of credential schema across all all UMFA workflows
|
|
19
|
+
- Improved security by addition of new validation checks for webauthn attestation and assertion responses
|
|
20
|
+
- Improvements to performance by removing now-unnecessary API trips during UMFA enrollment and authentication flows
|
|
21
|
+
|
|
22
|
+
## Release 2.6.3 - January 6, 2026 [ NPM PACKAGE RELEASE ]
|
|
23
|
+
- Minor alteration to event coordinator to support legacy versions of Safari (versions 14-18.4)
|
|
24
|
+
|
|
25
|
+
## Release 2.6.2 - December 19, 2025 [ NPM PACKAGE RELEASE ]
|
|
15
26
|
- Hotfix release to address an issue with the Passkeys+ plugin integration.
|
|
16
27
|
|
|
17
28
|
## Release 2.6.1 - December 16, 2025 [ NPM PACKAGE RELEASE ]
|
package/EventCoordinator.js
CHANGED
|
@@ -182,7 +182,7 @@ class EventCoordinator {
|
|
|
182
182
|
byStatus(eventStatus) {
|
|
183
183
|
if (!eventStatus || typeof eventStatus !== 'string' || !eventStatus.length || !["UNREGISTERED", "PENDING", "READY", "ERROR"].includes(eventStatus))
|
|
184
184
|
throw new Error(`[ZSM EventCoordinator] :: byStatus :: Invalid 'eventStatus' value: "${eventStatus}". Valid statuses are: UNREGISTERED, PENDING, READY, ERROR.`);
|
|
185
|
-
return
|
|
185
|
+
return this.entries.flatMap(([k, v]) => v === eventStatus ? [k] : []);
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
/**
|
|
@@ -202,7 +202,8 @@ class EventCoordinator {
|
|
|
202
202
|
* @returns {Object} Returns the reset event stack object.
|
|
203
203
|
*/
|
|
204
204
|
reset() {
|
|
205
|
-
this.
|
|
205
|
+
this.keys.forEach(key => this._eventStack.set(key, 'PENDING'));
|
|
206
|
+
|
|
206
207
|
if(window && window?.queueMicrotask) {
|
|
207
208
|
queueMicrotask(() => this.dispatchEvent('ZSMClientEventStackReset', { stack: this.object }));
|
|
208
209
|
} else this.dispatchEvent('ZSMClientEventStackReset', { stack: this.object });
|
package/PluginManager.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
class PluginManager {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
constructor() {
|
|
3
|
+
this._sdkClassMap = new Map(); // Map of SDK class names to their implementations
|
|
4
|
+
this._classCorpus = new Map(); // Map of plugin identities to their class maps
|
|
5
|
+
this._status = 'INITIALIZED'; // Indicates the current status of the plugin manager (INITIALIZED, SEEDED, FROZEN)
|
|
6
|
+
}
|
|
6
7
|
/**
|
|
7
8
|
* Returns whether the plugin manager is frozen.
|
|
8
9
|
* @returns {boolean} True if the plugin manager is frozen, false otherwise.
|
|
9
10
|
*/
|
|
10
11
|
get isFrozen() {
|
|
11
|
-
return this
|
|
12
|
+
return this._status === 'FROZEN';
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -20,15 +21,15 @@ class PluginManager {
|
|
|
20
21
|
* @throws {Error} If no class is registered for the given name.
|
|
21
22
|
*/
|
|
22
23
|
classes (classDesc) {
|
|
23
|
-
if(this
|
|
24
|
+
if(this._status === 'INITIALIZED') throw new Error('[zsmPluginManager] :: classes :: collection accessor cannot be referenced before SOME classes are registered!');
|
|
24
25
|
if(!classDesc || typeof classDesc !== 'string') throw new Error(`[zsmPluginManager] :: classes :: collection accessor requires a valid class description (received ${typeof classDesc}: ${classDesc})!`);
|
|
25
26
|
classDesc = classDesc.toUpperCase();
|
|
26
27
|
if(!this.isFrozen){
|
|
27
28
|
console.warn('[zsmPluginManager] :: Freezing the plugin manager as the first class has been preemptively requested. No further plugins can be registered after this point.\nIf you feel you received this message unexpectedly, please ensure all plugins are imported before initializing (`new UMFAClient()` or `new FIDO2Client()`) a client.');
|
|
28
29
|
this.freezePlugins();
|
|
29
30
|
}
|
|
30
|
-
if(!this
|
|
31
|
-
return this
|
|
31
|
+
if(!this._sdkClassMap.has(classDesc)) throw new Error(`[zsmPluginManager] :: classes :: No SDK class registered for: ${classDesc}`);
|
|
32
|
+
return this._sdkClassMap.get(classDesc);
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
/**
|
|
@@ -56,9 +57,9 @@ class PluginManager {
|
|
|
56
57
|
if (this.isFrozen) throw new Error('[zsmPluginManager] :: registerPlugin :: Cannot register plugin: PlugInManager is frozen!\nThis typically occurs when a plugin is imported after a client - UMFAClient or FIDO2Client - has already been initialized.\nMake sure to import all plugins before initializing (`new UMFAClient()` or `new FIDO2Client()`) a client.');
|
|
57
58
|
if (!pluginIdentity || typeof classObj !== 'object' || classObj == null) throw new Error('[zsmPluginManager] :: registerPlugin :: pluginIdentity and class map object required');
|
|
58
59
|
|
|
59
|
-
this
|
|
60
|
+
this._status = 'SEEDED';
|
|
60
61
|
|
|
61
|
-
this
|
|
62
|
+
this._classCorpus.set(pluginIdentity.toUpperCase(), classObj);
|
|
62
63
|
console.info(`[zsmPluginManager] :: PluginManager :: Registered plugin "${pluginIdentity}".`);
|
|
63
64
|
return true;
|
|
64
65
|
}
|
|
@@ -74,19 +75,19 @@ class PluginManager {
|
|
|
74
75
|
// As new plugins are DEVELOPED, they may introduce new classes or modify existing ones, and their extension/override logic must be declared below.
|
|
75
76
|
|
|
76
77
|
// [CORE PACKAGE] Default all of the active classes to the "core" classes (set below this declaration)
|
|
77
|
-
const core = this
|
|
78
|
+
const core = this._classCorpus.get('CORE');
|
|
78
79
|
if (!core) throw new Error('[zsmPluginManager] :: freezePlugins :: Core not registered before freeze');
|
|
79
80
|
|
|
80
81
|
const constructedMap = this.seedClassMap(core); // returns Map
|
|
81
82
|
|
|
82
83
|
// [PASSKEYS+ PLUGIN PACKAGE] If the "PASSKEYS+" plugin is registered, supercede the core classes with the passkeys classes
|
|
83
|
-
if(this
|
|
84
|
-
const passkeysMap = this
|
|
84
|
+
if(this._classCorpus.has('PASSKEYS+')) {
|
|
85
|
+
const passkeysMap = this._classCorpus.get('PASSKEYS+');
|
|
85
86
|
if (passkeysMap?.classDefs) for (const [k,v] of Object.entries(passkeysMap.classDefs)) constructedMap.set(k.toUpperCase(), v);
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
// Apply class decorators (wrappers) AFTER overlays, if present in any of the registrations
|
|
89
|
-
const postConstructDecorators = [...this
|
|
90
|
+
const postConstructDecorators = [...this._classCorpus.values()].filter(entry => entry.mode === 'DECORATOR');
|
|
90
91
|
if (postConstructDecorators.length) {
|
|
91
92
|
for (const modifier of postConstructDecorators) {
|
|
92
93
|
let { classDesc, postModFn } = modifier;
|
|
@@ -101,8 +102,8 @@ class PluginManager {
|
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
104
|
|
|
104
|
-
this
|
|
105
|
-
this
|
|
105
|
+
this._sdkClassMap = constructedMap;
|
|
106
|
+
this._status = 'FROZEN';
|
|
106
107
|
}
|
|
107
108
|
}
|
|
108
109
|
|
package/RelyingPartyBase.js
CHANGED
|
@@ -114,6 +114,7 @@ class RelyingPartyBase {
|
|
|
114
114
|
* @throws {Error} Throws an error if the request fails.
|
|
115
115
|
*/
|
|
116
116
|
registrationFinish (credential) {
|
|
117
|
+
return credential;
|
|
117
118
|
return this.makePostRequest(`api/webauthn/registration/finish`, { credential })
|
|
118
119
|
.then(body => {
|
|
119
120
|
this.credentialID = credential.rawId;
|
|
@@ -153,6 +154,7 @@ class RelyingPartyBase {
|
|
|
153
154
|
* @throws {Error} Throws an error if the request fails.
|
|
154
155
|
*/
|
|
155
156
|
authenticationFinish (credential) {
|
|
157
|
+
return credential;
|
|
156
158
|
return this.makePostRequest("api/webauthn/authentication/finish", { credential })
|
|
157
159
|
.then(result => result)
|
|
158
160
|
.catch(error => Promise.reject(new Error(error)));
|
|
@@ -192,6 +194,7 @@ class RelyingPartyBase {
|
|
|
192
194
|
* @throws {Error} Throws an error if the request fails.
|
|
193
195
|
*/
|
|
194
196
|
registrationFinishAuthenticationStart (credential) {
|
|
197
|
+
return credential;
|
|
195
198
|
return this.makePostRequest(`api/webauthn/registration/finish-then-authentication-start`, { credential })
|
|
196
199
|
.then(result => (this.credentialID = credential.rawId, result))
|
|
197
200
|
.then(credential => credential)
|
package/UMFAClientBase.js
CHANGED
|
@@ -99,7 +99,7 @@ class UMFAClientBase {
|
|
|
99
99
|
let userIsEnrolled = await this.checkEnrollment(userIdentifier);
|
|
100
100
|
if(userIsEnrolled === false) throw(`${userIdentifier} is not enrolled.`);
|
|
101
101
|
let authCredential = await this.zsmAPI.webauthnPartialGet(userIdentifier);
|
|
102
|
-
return authCredential
|
|
102
|
+
return authCredential?.credential??authCredential;
|
|
103
103
|
}catch(e) {
|
|
104
104
|
return new Error(e.message || e || '[UMFAClient] :: authenticate :: An error occurred during authentication.');
|
|
105
105
|
}
|
package/WebAuthnClientBase.js
CHANGED
|
@@ -12,6 +12,11 @@ const tracePrimer = (obj=false) => {
|
|
|
12
12
|
return (obj) ? {"trace": traceBase} : traceBase;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
const canParseURL = (url) => {
|
|
16
|
+
try { new URL(url); return true; } catch { return false; }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
15
20
|
class WebAuthnClientBase {
|
|
16
21
|
/**
|
|
17
22
|
* Constructs a WebAuthnClientBase object.
|
|
@@ -20,7 +25,7 @@ class WebAuthnClientBase {
|
|
|
20
25
|
constructor(config) {
|
|
21
26
|
// console.log('config :', Object.assign({}, config));
|
|
22
27
|
// Helper function to standardize URL format (ensuring it's parsable and ends with a '/')
|
|
23
|
-
const cleanURL = (url) =>
|
|
28
|
+
const cleanURL = (url) => canParseURL(url) ? new URL(url).origin + '/' : false;
|
|
24
29
|
|
|
25
30
|
if(config?.region){
|
|
26
31
|
config.authenticator_host_url ||= config.authenticator_host = `https://zsm-authenticator-${config.region}.useideem.com/`;
|
|
@@ -282,6 +287,7 @@ class WebAuthnClientBase {
|
|
|
282
287
|
GLOBAL.dispatchEvent(new CustomEvent("MPCResponse", {bubbles: true, cancelable: false, detail: mpcResponse }));
|
|
283
288
|
|
|
284
289
|
const credential = mpcResponse.result;
|
|
290
|
+
return credential;
|
|
285
291
|
let authFinishResponse = await this.relyingParty.authenticationFinish(credential);
|
|
286
292
|
|
|
287
293
|
if (authFinishResponse instanceof Error) throw new Error(authFinishResponse);
|
|
@@ -375,6 +381,7 @@ class WebAuthnClientBase {
|
|
|
375
381
|
GLOBAL.dispatchEvent(new CustomEvent("MPCResponse", {bubbles: true, cancelable: false, detail: mpcResponse }));
|
|
376
382
|
|
|
377
383
|
const getCredential = mpcResponse.result;
|
|
384
|
+
return getCredential;
|
|
378
385
|
return {
|
|
379
386
|
signature: getCredential.response.signature,
|
|
380
387
|
signedChallenge: getCredential.response.clientDataJSON,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name" : "@ideem/zsm-client-sdk",
|
|
3
|
-
"version" : "2.
|
|
3
|
+
"version" : "2.7.0",
|
|
4
4
|
"description" : "ZSM makes 2FA easy and invisible for everyone, all the time, using advanced cryptography like MPC to establish cryptographic proof of the origin of any transaction or login attempt, while eliminating opportunities for social engineering. ZSM has no reliance on specific hardware. Our cryptographic module has completed FIPS 140-3 NIST approved lab testing, is FIDO2-ready, and is on the Modules In Progress (MIP) list. Superior Usability. no opt-in, no codes, no downloads, no authenticator apps. Better Security.",
|
|
5
5
|
"author" : "Jason Jurusz <jj@useideem.com>",
|
|
6
6
|
"keywords" : [ "ideem", "zsm", "zsm-client-sdk", "umfaclient", "fido2client", "library", "umfa", "fido", "fido2", "client", "javascript", "multifactor", "multi-factor", "webauthn", "authentication", "security", "cryptography", "mpc", "fips", "nist", "mip", "passkeys" ],
|