@ideem/zsm-client-sdk 2.8.0 → 2.9.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.
@@ -74,6 +74,21 @@ class WebAuthnClientBase {
74
74
  if(!config?.zsm_host_url) config.zsm_host_url = config.zsm_host = config.host_url.replace("zsm-authenticator", "zsm");
75
75
  config.host_url = config.zsm_host_url;
76
76
 
77
+ // [J] API key is not strictly required for the client to function, as long as the KVP exists and is in the format of a UUID.
78
+ // The line below ensures that if an API key is not provided, but an application_id is (which IS required), it will use the
79
+ // application_id in place of the absent api_key. If it - application_id - ALSO isn't provided, we DO want the calls to fail -
80
+ // and to do so conspicuously (as this indicates a misconfiguration) – so the default string below will be clearly visible inside
81
+ // of any server-side log outputs and in DevTools Network Panel requests for easy debugging (for both us and for our customers).
82
+ config.api_key ??= (config?.application_id??"NO APPLICATION_ID PROVIDED! CHECK YOUR zsm_app_config.json!");
83
+
84
+ // Once we are certain there's nothing else looking for api_key in the WASM/back-end/Rust code, we can delete the above line and
85
+ // uncomment the line below, which will remove the api_key from the config object passed to the WASM module entirely in the event
86
+ // it is omitted or is not in a valid UUID format. Presently, the ability to do so is contingent upon a specific column's value
87
+ // inside the Authenticator database: if sdk_authorization_type != "none", then an api_key remains required. Once we've updated all
88
+ // database records and verified the WASM/Rust code compliance, we can just delete config?.api_key here without any conditions.
89
+
90
+ // if(!config?.api_key || !/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(config.api_key)) delete config.api_key;
91
+
77
92
  this.config = GLOBAL.zsmAppConfig = config;
78
93
 
79
94
  iid4uid(this.config.consumer_id)
@@ -210,9 +225,7 @@ class WebAuthnClientBase {
210
225
 
211
226
  let passkeySupported = false; // [J] Best-effort detection of platform authenticator support
212
227
  try {
213
- passkeySupported = !!(typeof window !== 'undefined' && window.PublicKeyCredential
214
- && navigator?.credentials
215
- && await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable());
228
+ passkeySupported = !!(navigator?.credentials);
216
229
  } catch { /* unsupported environment */ }
217
230
 
218
231
  this.credentialID = localData['zsm_credential_id']??null;
@@ -425,7 +438,26 @@ class WebAuthnClientBase {
425
438
 
426
439
  /**
427
440
  * @name unbindFromDevice
428
- * @description Removes the device binding and stored account data for userIdentifier
441
+ * @description Removes the device binding and stored account data for userIdentifier.
442
+ *
443
+ * Enrollment writes several "ideem"/"zsm" IDB entries for a single user:
444
+ * the uid→iid mapping (key = user identifier), iid-valued string entries
445
+ * (key = "", key = "<hash>/equivalence"), and a credential object
446
+ * (key = "<hash>", value with `zsm_credential` as a Map of WebAuthn
447
+ * fields). The cursor walk below matches each shape: condition (a) for
448
+ * uid-keyed entries, condition (b) for iid-valued strings, condition
449
+ * (c) for the credential Map. Missing any one leaves zombie entries
450
+ * that subsequent checkEnrollment finds.
451
+ *
452
+ * The IDB operations are wrapped in an awaited Promise so the function
453
+ * returns only after the transaction commits — without this, callers
454
+ * that immediately call checkEnrollment race the cursor's deletes and
455
+ * read stale entries. The zombie-then-stale-read pattern was invisible
456
+ * when install_id was stable per device (the survivors stayed
457
+ * self-consistent across sessions) but becomes a silent failure when
458
+ * install_id varies per session, because the WASM's webauthn_retrieve
459
+ * lookup keys depend on it and finds the prior session's residue.
460
+ *
429
461
  * @param {String} userIdentifier The Customer-Defined Identifier of the user whose account is to be removed
430
462
  * @returns {Promise<Boolean>} Returns true if the user's account was present (and subsequently removed from the device)
431
463
  * @returns {Promise<Boolean>} Returns false if there is no credential matching userIdentifier stored on the device
@@ -442,27 +474,39 @@ class WebAuthnClientBase {
442
474
 
443
475
  const rawID = await this.webauthnRetrieve(userIdentifier);
444
476
  if (!rawID) return false;
445
-
446
- const request = indexedDB.open("ideem", 1);
447
- request.onsuccess = function(event) {
448
- const store = event.target.result.transaction("zsm", "readwrite").objectStore("zsm");
449
- const cursorRequest = store.openCursor();
450
- cursorRequest.onsuccess = function(event) {
451
- const cursor = event.target.result;
452
- if (cursor) {
453
- const { key, value } = cursor;
454
- if(
455
- key.includes(userIdentifier) ||
456
- (typeof value === 'string' && value.includes(userIdentifier)) ||
457
- typeof value === 'object' && value?.zsmCredential?.get('rawId') === rawID
458
- ) (store.delete(key), true);
459
- cursor.continue();
460
- }
477
+ const userIid = await iid4uid(userIdentifier);
478
+
479
+ await new Promise((resolve, reject) => {
480
+ const request = indexedDB.open("ideem", 1);
481
+ request.onerror = () => reject(new Error(`[WebAuthnClient] :: unbindFromDevice :: Unable to open indexedDB for ${userIdentifier}`));
482
+ request.onsuccess = (event) => {
483
+ const db = event.target.result;
484
+ const tx = db.transaction("zsm", "readwrite");
485
+ const store = tx.objectStore("zsm");
486
+ tx.oncomplete = () => resolve();
487
+ tx.onerror = () => reject(new Error(`[WebAuthnClient] :: unbindFromDevice :: transaction failed for ${userIdentifier}`));
488
+ tx.onabort = () => reject(new Error(`[WebAuthnClient] :: unbindFromDevice :: transaction aborted for ${userIdentifier}`));
489
+ const cursorRequest = store.openCursor();
490
+ cursorRequest.onerror = () => reject(new Error(`[WebAuthnClient] :: unbindFromDevice :: cursor failed for ${userIdentifier}`));
491
+ cursorRequest.onsuccess = (event) => {
492
+ const cursor = event.target.result;
493
+ if (cursor) {
494
+ const { key, value } = cursor;
495
+ const matchesStringValue = typeof value === 'string'
496
+ && (value.includes(userIdentifier) || value === userIid);
497
+ const matchesCredential = typeof value === 'object'
498
+ && value !== null
499
+ && value.zsm_credential instanceof Map
500
+ && value.zsm_credential.get('rawId') === rawID;
501
+ if (key.includes(userIdentifier) || matchesStringValue || matchesCredential) {
502
+ store.delete(key);
503
+ }
504
+ cursor.continue();
505
+ }
506
+ };
461
507
  };
462
- cursorRequest.onerror = (err) => { throw new Error(`[WebAuthnClient] :: unbindFromDevice :: Unable to unbind the device for ${userIdentifier}\n${err}`); };
463
- };
464
- request.onerror = (err) => { throw new Error(`[WebAuthnClient] :: unbindFromDevice :: Unable to open indexedDB for ${userIdentifier}\n${err}`); };
465
- this.webauthnRetrieve(userIdentifier);
508
+ });
509
+
466
510
  this.status = 'UNENROLL_COMPLETE';
467
511
  return true;
468
512
  }
package/ZSMClientSDK.js CHANGED
@@ -37,7 +37,7 @@ zsmPluginManager.registerPlugin('Core', {
37
37
  mode: 'OVERRIDE'
38
38
  });
39
39
 
40
- const ZSMClientSDKVersion = '2.8.0';
40
+ const ZSMClientSDKVersion = '2.9.0';
41
41
  const ZSMVersions = GLOBAL.ZSMVersions = (GLOBAL.ZSMVersions) ? {...GLOBAL.ZSMVersions, CORE: ZSMClientSDKVersion} : { CORE: ZSMClientSDKVersion }
42
42
 
43
43
  export { UMFAClient, FIDO2Client, ZSMVersions };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name" : "@ideem/zsm-client-sdk",
3
- "version" : "2.8.0",
3
+ "version" : "2.9.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
  "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" ],
6
6
  "author" : "Jason Jurusz <jj@useideem.com>",