@ideem/zsm-client-sdk 2.9.3 → 2.9.4

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
@@ -12,7 +12,34 @@ and Universal MFA into a browser application.
12
12
 
13
13
  ----
14
14
 
15
- ## Release 2.9.3 - August 13, 2026 [ NPM PACKAGE RELEASE ] **latest release**
15
+ ## Release 2.9.4 - September 14, 2026 **latest release**
16
+ - Removes the optional `installation_id` override mechanism that release 2.9.0 added.
17
+ - `InstallationID::get()` now always derives the identifier from the IndexedDB keypair at `installation_id_key_pair`.
18
+ - Adds `getInstallationId()` to the `UMFAClient` class. It resolves with this installation's semi-persistent identifier as a string, and throws if the module returns no identifier. A page that must name its own installation to a server no longer has to reach through the client's internals for the value.
19
+ - Keeps the original WebAuthn error name on a failed passkey ceremony. When `enroll` or `authenticate` on `UMFAClient`, or `webauthnCreate` or `webauthnGet` on `FIDO2Client`, fails at the passkey step under a `userVerification` of `"required"`, the returned `Error` now carries the name of the underlying `DOMException`, for example `NotAllowedError`, `SecurityError` or `InvalidStateError`. The original `DOMException` is also attached as the `cause` property. Earlier releases discarded both and kept the message text only. A consumer can now detect a user cancellation with `error.name === 'NotAllowedError'`. The consumer no longer has to match the message text, which each browser phrases differently.
20
+ - Automatically binds a local credential's `state` value to the one corresponding to it on the Ideem remote server, ensuring that the client's view of the user's enrollment status is always up-to-date with the server. Accordingly, adjusts the `checkAllEnrollments()` return value to reflect the current state of the local enrollment(s) and the actions that the user can take based upon that state.
21
+ - Expands the `checkAllEnrollments()` return value to include two new properties: `state` and `actions`.
22
+ - The `state` property is a string that indicates the overall enrollment state of the user, and can be one of:
23
+ - `"NONE"` (indicating there are NO finalized credentials available)
24
+ - `"MPC_ONLY"` (indicating the presence of a finalized ZSM credential only)
25
+ - `"BOTH"` (indicating the presence of both finalized ZSM and passkey credentials).
26
+ - The `actions` property is an array of two strings that indicates the actions that the user can take based upon the current state of the local enrollment(s). This allows consumers to provide more detailed guidance to their users on how to complete their enrollment, and to tailor their UI/UX accordingly. These include:
27
+ - `"ENROLL-ZSM"` (the user can perform a ZSM-only enrollment)
28
+ - `"ENROLL-PKP"` (the user can perform a passkey-only enrollment)
29
+ - `"ENROLL-BOTH"` (the user can perform both ZSM and passkey enrollments)
30
+ - `"AUTHENTICATE-ZSM"` (the user can authenticate using a ZSM credential)
31
+ - `"AUTHENTICATE-BOTH"` (the user can authenticate using either a ZSM or passkey credential)
32
+ - Resolves a lockout that could follow an enrollment whose out-of-band validation never completed. A credential that is created but never validated remains inactive on the Ideem server, so every subsequent authentication is rejected, while the local record still reads as enrolled and blocks any attempt to enroll the user again. `enroll()` and `authenticate()` now recognize this state, clear the stranded local record, and return the user to the ordinary first-time enrollment path. `enroll()` clears the record and proceeds directly to a fresh enrollment; `authenticate()` reports the condition as an `Error` explaining that the enrollment was never validated and has been cleared from the device, and that the user must enroll again. A local record is only ever cleared when the server has explicitly confirmed the credential to be inactive: a check that could not reach the server, or that was not needed, leaves the local record untouched.
33
+ - `checkAllEnrollments()` now RESOLVES with an `Error` when enrollment details cannot be retrieved at all, rather than rejecting. This aligns the method with its documented contract, under which it resolves with either an enrollment details object or an `Error`, and with the `enroll()` and `authenticate()` methods, which already behave this way. A consumer relying on a `try`/`catch` around `checkAllEnrollments()` should test the resolved value with `instanceof Error` instead. The returned `Error` carries a `code` property of `"ENROLLMENT_DETAILS_UNAVAILABLE"`, the originating error as its `cause`, and the `userIdentifier` and `forceRemoteCheck` values the call was made with. Note that an unenrolled user is NOT an error, and still resolves with an ordinary details object whose `state` is `"NONE"`.
34
+ - Adds a `remoteCheckStatus` property to the data returned by `checkAllEnrollments()`, reporting whether the values in the returned object were confirmed against the Ideem server. It is one of:
35
+ - `"confirmed"` (the server was consulted, and returned the user's enrollment details)
36
+ - `"not-found"` (the server was consulted, and reported no enrollment for the user)
37
+ - `"unavailable"` (the server could not be reached; every other value in the object is local-only and unverified)
38
+ - `"skipped"` (local data was sufficient, so no server call was made)
39
+ This allows a consumer to distinguish an authoritative "this user is not enrolled" from a failure to reach the server, which earlier releases reported identically. It is particularly relevant when calling `checkAllEnrollments()` with its `forceRemoteCheck` argument set to `true`, as it reports whether that request was actually satisfied.
40
+ - `unenroll()` now throws an `Error` when it is unable to unbind a user's identity from the device, where earlier releases threw a bare string. A consumer's `catch` block can now read `error.message` and `error.cause`.
41
+
42
+ ## Release 2.9.3 - August 13, 2026 [ NPM PACKAGE RELEASE ]
16
43
  - Minor bug fixes and improvements
17
44
  - Resolves a strange conflict experienced by users running third party password managers (e.g. 1Password, Bitwarden, etc.) in which the `hints` KVP on Webauthn publicKey objects was preventing said password managers from properly handling the enrollment and authorization flows (flows still worked, but passkeys would not be saved to the password manager). The SDK now automatically removes the optional `hints` KVP from publicKey objects before passing them to the browser's Webauthn API, which resolves this issue and allows password managers to properly handle passkey enrollment and authorization flows.
18
45
 
@@ -1,3 +1,3 @@
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}
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){const s=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.`,{cause:r});return s.name=r?.name||"Error",s}}}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){const s=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.`,{cause:r});return s.name=r?.name||"Error",s}}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
2
  //! PASSKEYS PLUS FIDO2 CLIENT METHODS ==============================================================================================================================
3
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)}}
@@ -1 +1 @@
1
- import e from"./EventCoordinator.js";export default class{constructor(){e.update("PasskeysPlus"),this.passkeyAPI=(navigator?.credentials??null)||null,e.update("PasskeysPlus","READY")}pkpSupported=()=>!!this.passkeyAPI&&"function"==typeof this.passkeyAPI.create&&"function"==typeof this.passkeyAPI.get;blockUnsupportedInvocation=()=>{if(!this.pkpSupported())throw new Error("[PK+ PasskeysPlus] :: blockUnsupportedInvocation :: This browser and/or device does not support the PasskeysPlus API!");return!0};toArrayBuffer(e){if("string"!=typeof e||!/^[A-Za-z0-9\-_]+$/.test(e))throw new Error(`[PK+ PasskeysPlus] :: toArrayBuffer :: Invalid base64url string provided: ${e}`);try{const t=atob(e.replace(/-/g,"+").replace(/_/g,"/")+"====".slice(0,-e.length%4)),r=new Uint8Array(t.length);for(let e=0;e<t.length;e++)r[e]=t.charCodeAt(e);return r.buffer}catch(e){throw new Error(`[PK+ PasskeysPlus] :: toArrayBuffer :: Error converting base64url string to ArrayBuffer: ${e.message||e}`)}}pkpCreatePasskeyCredential=async(e,t="preferred")=>{if(!this.blockUnsupportedInvocation())return!1;if(e?.authenticatorSelection&&(e.authenticatorSelection.userVerification=t),e?.hints&&delete e.hints,e?.extensions?.credentialProtectionPolicy&&delete e.extensions.credentialProtectionPolicy,!e||"object"!=typeof e)throw new Error(`[PK+ PasskeysPlus] :: pkpCreatePasskeyCredential :: Invalid publicKey parameter provided: ${e}! It must be an object. Received: ${e}`);if(!(e?.challenge&&e?.rp&&e?.rp?.id&&e?.rp?.name))throw new Error(`[PK+ PasskeysPlus] :: pkpCreatePasskeyCredential :: Invalid publicKey structure! It must contain challenge and rp properties with id and name. Received: ${e}`);if(!(e?.user&&e?.user?.id&&e?.user?.id instanceof ArrayBuffer))throw new Error(`[PK+ PasskeysPlus] :: pkpCreatePasskeyCredential :: Invalid user ID in publicKey! It must be an ArrayBuffer. Received: ${e?.user?.id}`);if(!e?.pubKeyCredParams||!Array.isArray(e?.pubKeyCredParams)||0===e?.pubKeyCredParams?.length)throw new Error(`[PK+ PasskeysPlus] :: pkpCreatePasskeyCredential :: pubKeyCredParams must be a non-empty array of public key credential parameters. Received: ${e?.pubKeyCredParams}`);if(!e?.authenticatorSelection||"object"!=typeof e?.authenticatorSelection)throw new Error(`[PK+ PasskeysPlus] :: pkpCreatePasskeyCredential :: Invalid authenticatorSelection in publicKey! It must be an object. Received: ${e?.authenticatorSelection} (${typeof e?.authenticatorSelection})`);if(e?.authenticatorSelection?.authenticatorAttachment&&!["platform","cross-platform"].includes(e?.authenticatorSelection?.authenticatorAttachment))throw new Error(`[PK+ PasskeysPlus] :: pkpCreatePasskeyCredential :: Invalid authenticatorAttachment in authenticatorSelection! It must be either "platform" or "cross-platform". Received: ${e?.authenticatorSelection?.authenticatorAttachment}`);if(e?.authenticatorSelection?.requireResidentKey&&"boolean"!=typeof e?.authenticatorSelection?.requireResidentKey)throw new Error(`[PK+ PasskeysPlus] :: pkpCreatePasskeyCredential :: Invalid requireResidentKey in authenticatorSelection! It must be a boolean. Received: ${e?.authenticatorSelection?.requireResidentKey} (${typeof e?.authenticatorSelection?.requireResidentKey})`);if(e?.authenticatorSelection?.userVerification&&!["required","preferred","discouraged"].includes(e?.authenticatorSelection?.userVerification))throw new Error(`[PK+ PasskeysPlus] :: pkpCreatePasskeyCredential :: Invalid userVerification in authenticatorSelection! It must be one of "required", "preferred", or "discouraged". Received: ${e?.authenticatorSelection?.userVerification}`);if(!e?.timeout||"number"!=typeof e?.timeout||e?.timeout<=0)throw new Error(`[PK+ PasskeysPlus] :: pkpCreatePasskeyCredential :: Invalid timeout in publicKey! It must be a positive number. Received: ${e?.timeout} (${typeof e?.timeout})`);if(e?.excludeCredentials&&(!Array.isArray(e?.excludeCredentials)||e?.excludeCredentials?.length>0&&!e?.excludeCredentials[0].id))throw new Error(`[PK+ PasskeysPlus] :: pkpCreatePasskeyCredential :: Invalid credential ID in excludeCredentials! It must be an ArrayBuffer. Received: ${e?.excludeCredentials[0]?.id}`);return await this.passkeyAPI.create({publicKey:{...e,userVerification:t}})};pkpGetPasskeyCredential=async(e,t="preferred")=>{if(!this.blockUnsupportedInvocation())return!1;if(e?.authenticatorSelection&&(e.authenticatorSelection.userVerification=t),e?.hints&&delete e.hints,!e||"object"!=typeof e)throw new Error("[PK+ PasskeysPlus] :: pkpGetPasskeyCredential :: Invalid publicKey parameter provided!");if(!e?.challenge||!e?.allowCredentials||!Array.isArray(e?.allowCredentials))throw new Error(`[PK+ PasskeysPlus] :: pkpGetPasskeyCredential :: Invalid publicKey structure provided! It must contain challenge and allowCredentials properties. Received: ${e}`);if(0===e?.allowCredentials?.length)throw new Error(`[PK+ PasskeysPlus] :: pkpGetPasskeyCredential :: allowCredentials array must not be empty! It should contain at least one credential ID. Received: ${e?.allowCredentials}`);const r=e?.allowCredentials.reduce((e,t)=>e&&t?.id&&(t.id instanceof ArrayBuffer||ArrayBuffer.isView(t.id)),!0);if(!r)throw new Error(`[PK+ PasskeysPlus] :: pkpGetPasskeyCredential :: Invalid credential ID in allowCredentials! It must be an ArrayBuffer. Received: ${e?.allowCredentials[0]?.id}`);return await this.passkeyAPI.get({publicKey:{...e,userVerification:t}})}}
1
+ import e from"./EventCoordinator.js";export default class{constructor(){e.update("PasskeysPlus"),this.passkeyAPI=(navigator?.credentials??null)||null,this.deviceSupportsPasskeys=!1;try{this.passkeyAPI=navigator?.credentials??null,this.deviceSupportsPasskeys=!!this.passkeyAPI&&"function"==typeof this.passkeyAPI.create&&"function"==typeof this.passkeyAPI.get}catch{}e.update("PasskeysPlus","READY")}get passkeysSupported(){return this.deviceSupportsPasskeys}pkpSupported=()=>this.deviceSupportsPasskeys;blockUnsupportedInvocation=()=>{if(!this.pkpSupported())throw new Error("[PK+ PasskeysPlus] :: blockUnsupportedInvocation :: This browser and/or device does not support the PasskeysPlus API!");return!0};toArrayBuffer(e){if("string"!=typeof e||!/^[A-Za-z0-9\-_]+$/.test(e))throw new Error(`[PK+ PasskeysPlus] :: toArrayBuffer :: Invalid base64url string provided: ${e}`);try{const t=atob(e.replace(/-/g,"+").replace(/_/g,"/")+"====".slice(0,-e.length%4)),r=new Uint8Array(t.length);for(let e=0;e<t.length;e++)r[e]=t.charCodeAt(e);return r.buffer}catch(e){throw new Error(`[PK+ PasskeysPlus] :: toArrayBuffer :: Error converting base64url string to ArrayBuffer: ${e.message||e}`)}}pkpCreatePasskeyCredential=async(e,t="preferred")=>{if(!this.blockUnsupportedInvocation())return!1;if(e?.authenticatorSelection&&(e.authenticatorSelection.userVerification=t),e?.hints&&delete e.hints,e?.extensions?.credentialProtectionPolicy&&delete e.extensions.credentialProtectionPolicy,!e||"object"!=typeof e)throw new Error(`[PK+ PasskeysPlus] :: pkpCreatePasskeyCredential :: Invalid publicKey parameter provided: ${e}! It must be an object. Received: ${e}`);if(!(e?.challenge&&e?.rp&&e?.rp?.id&&e?.rp?.name))throw new Error(`[PK+ PasskeysPlus] :: pkpCreatePasskeyCredential :: Invalid publicKey structure! It must contain challenge and rp properties with id and name. Received: ${e}`);if(!(e?.user&&e?.user?.id&&e?.user?.id instanceof ArrayBuffer))throw new Error(`[PK+ PasskeysPlus] :: pkpCreatePasskeyCredential :: Invalid user ID in publicKey! It must be an ArrayBuffer. Received: ${e?.user?.id}`);if(!e?.pubKeyCredParams||!Array.isArray(e?.pubKeyCredParams)||0===e?.pubKeyCredParams?.length)throw new Error(`[PK+ PasskeysPlus] :: pkpCreatePasskeyCredential :: pubKeyCredParams must be a non-empty array of public key credential parameters. Received: ${e?.pubKeyCredParams}`);if(!e?.authenticatorSelection||"object"!=typeof e?.authenticatorSelection)throw new Error(`[PK+ PasskeysPlus] :: pkpCreatePasskeyCredential :: Invalid authenticatorSelection in publicKey! It must be an object. Received: ${e?.authenticatorSelection} (${typeof e?.authenticatorSelection})`);if(e?.authenticatorSelection?.authenticatorAttachment&&!["platform","cross-platform"].includes(e?.authenticatorSelection?.authenticatorAttachment))throw new Error(`[PK+ PasskeysPlus] :: pkpCreatePasskeyCredential :: Invalid authenticatorAttachment in authenticatorSelection! It must be either "platform" or "cross-platform". Received: ${e?.authenticatorSelection?.authenticatorAttachment}`);if(e?.authenticatorSelection?.requireResidentKey&&"boolean"!=typeof e?.authenticatorSelection?.requireResidentKey)throw new Error(`[PK+ PasskeysPlus] :: pkpCreatePasskeyCredential :: Invalid requireResidentKey in authenticatorSelection! It must be a boolean. Received: ${e?.authenticatorSelection?.requireResidentKey} (${typeof e?.authenticatorSelection?.requireResidentKey})`);if(e?.authenticatorSelection?.userVerification&&!["required","preferred","discouraged"].includes(e?.authenticatorSelection?.userVerification))throw new Error(`[PK+ PasskeysPlus] :: pkpCreatePasskeyCredential :: Invalid userVerification in authenticatorSelection! It must be one of "required", "preferred", or "discouraged". Received: ${e?.authenticatorSelection?.userVerification}`);if(!e?.timeout||"number"!=typeof e?.timeout||e?.timeout<=0)throw new Error(`[PK+ PasskeysPlus] :: pkpCreatePasskeyCredential :: Invalid timeout in publicKey! It must be a positive number. Received: ${e?.timeout} (${typeof e?.timeout})`);if(e?.excludeCredentials&&(!Array.isArray(e?.excludeCredentials)||e?.excludeCredentials?.length>0&&!e?.excludeCredentials[0].id))throw new Error(`[PK+ PasskeysPlus] :: pkpCreatePasskeyCredential :: Invalid credential ID in excludeCredentials! It must be an ArrayBuffer. Received: ${e?.excludeCredentials[0]?.id}`);return await this.passkeyAPI.create({publicKey:{...e,userVerification:t}})};pkpGetPasskeyCredential=async(e,t="preferred")=>{if(!this.blockUnsupportedInvocation())return!1;if(e?.authenticatorSelection&&(e.authenticatorSelection.userVerification=t),e?.hints&&delete e.hints,!e||"object"!=typeof e)throw new Error("[PK+ PasskeysPlus] :: pkpGetPasskeyCredential :: Invalid publicKey parameter provided!");if(!e?.challenge||!e?.allowCredentials||!Array.isArray(e?.allowCredentials))throw new Error(`[PK+ PasskeysPlus] :: pkpGetPasskeyCredential :: Invalid publicKey structure provided! It must contain challenge and allowCredentials properties. Received: ${e}`);if(0===e?.allowCredentials?.length)throw new Error(`[PK+ PasskeysPlus] :: pkpGetPasskeyCredential :: allowCredentials array must not be empty! It should contain at least one credential ID. Received: ${e?.allowCredentials}`);const r=e?.allowCredentials.reduce((e,t)=>e&&t?.id&&(t.id instanceof ArrayBuffer||ArrayBuffer.isView(t.id)),!0);if(!r)throw new Error(`[PK+ PasskeysPlus] :: pkpGetPasskeyCredential :: Invalid credential ID in allowCredentials! It must be an ArrayBuffer. Received: ${e?.allowCredentials[0]?.id}`);return await this.passkeyAPI.get({publicKey:{...e,userVerification:t}})}}
@@ -1,3 +1,3 @@
1
1
  import t from"./EventCoordinator.js";import e from"./GlobalScoping.js";import{uid2iid as i}from"./IdentityIndexing.js";import{b64urlToBuf as r,toObject as n}from"./Utils.js";t.update("RelyingPartyBase");export default class{constructor(e,i,r,n,a,s){this.host=e,this.apiKey=i,this.applicationId=r,this.appEnvironment=n||null,this.customerDefinedIdentifier=void 0,this.publicKey=void 0,this.identityID=void 0,this.credentialID=void 0,this.traceId=void 0,this.xhrHeaders={"Content-Type":"application/json"},this.addlRelyingPartyBodyProps=a?{use_origin:"true"}:{},this.metadata=s||null,t.update("RelyingPartyBase","READY")}get userIdentifier(){return this.customerDefinedIdentifier}set userIdentifier(t){return userIdentifier=this.customerDefinedIdentifier=t}makePostRequest(t,i={},r={},n="POST"){t=`${this.host.endsWith("/")?this.host.slice(0,-1):this.host}/${t.startsWith("/")?t.slice(1):t}`,i="object"==typeof i?i:{},this.metadata&&Object.keys(this.metadata).length&&(i=Object.assign({},i,{metadata:this.metadata})),this.appEnvironment&&(i=Object.assign({},i,{environment:this.appEnvironment})),i=Object.assign({customer_defined_identifier:this.customerDefinedIdentifier,application_id:this.applicationId},i,this.addlRelyingPartyBodyProps,function(t=!1){const e=[[(new Date).toISOString(),"TRACEPRIMER"]];return t?{trace:e}:e}(!0));let a={method:n,headers:Object.assign(this.xhrHeaders,r?.Authorization?{}:{Authorization:`Bearer ${this.apiKey}`},r),body:JSON.stringify(i)};return fetch(t,a).then(t=>t.ok?t.json():t.json().then(e=>{throw new Error(`[RelyingParty] :: makePostRequest :: Request failed: ${t.statusText}, ${JSON.stringify(e)}`)})).then(t=>(e.dispatchEvent(new CustomEvent("AuthResponse",{bubbles:!0,cancelable:!1,detail:t})),t))}clearEnrollmentCredentials(){return this.publicKey=this.credentialID=null}clearLoginCredentials(){}resetAll(){return this.clearLoginCredentials(),this.clearEnrollmentCredentials()}registrationStart(){return this.makePostRequest("api/webauthn/registration/start").then(t=>(t.identity_id&&(e.mpcConfig.consumer_id=t.identity_id,i(this.userIdentifier,t.identity_id)),this.publicKey=t.ccr.publicKey)).then(t=>t).catch(t=>Promise.reject(new Error(t)))}registrationFinish(t){return t}authenticationStart(t=this.credentialID,e="passkey",i,r,n){return null!=t?this.makePostRequest("api/webauthn/authentication/start",{credential_id:t}).then(t=>t):Promise.reject(new Error("[RelyingParty] :: authenticationStart :: No user ID or credential ID provided"))}authenticationFinish(t){return t}createIdentityThenRegistrationStart(t="zsm",r,n,a){const s={credential_type:t,chain_handoff:r,chain_credential_id:n,chain_challenge:a};return this.makePostRequest("api/webauthn/registration/create-identity-then-start",s).then(t=>(t.identity_id&&(e.mpcConfig.consumer_id=t.identity_id,i(this.userIdentifier,t.identity_id)),{publicKey:this.publicKey=t.ccr.publicKey,identity_id:t.identity_id})).then(t=>t).catch(t=>Promise.reject(new Error(t)))}registrationFinishAuthenticationStart(t){return t}checkServerSideIdentity(t){return t=t?{create_new_identity:"true"}:{},this.makePostRequest("api/umfa/check-identity",t).then(t=>t).catch(t=>Promise.reject(new Error(t)))}
2
2
  //! PASSKEYS PLUS RELYING PARTY METHODS =========================================================================================================================
3
- async pkpRegistrationStart(t,n,a=null,s=!1){const o=a?{chain_handoff:"internal",chain_credential_id:a}:{chain_handoff:"internal"},l=await this.makePostRequest("api/passkey/registration/create-identity-then-start",o).then(t=>(t.identity_id&&(e.mpcConfig.consumer_id=t.identity_id,i(this.userIdentifier,t.identity_id)),{publicKey:this.publicKey=t.ccr.publicKey,identity_id:t.identity_id})).then(t=>t).catch(t=>Promise.reject(new Error(t)));if(!l)throw new Error("[PK+ RelyingParty] :: pkpRegistrationStart :: Unable to retrieve Platform Authority Challenge from Relying Party Server!");if(!l?.publicKey)throw new Error("[PK+ RelyingParty] :: pkpRegistrationStart :: Platform Authority Challenge's Public Key was not found!");if(l.publicKey.challenge=r(l.publicKey.challenge),!l?.publicKey?.user)throw new Error("[PK+ RelyingParty] :: pkpRegistrationStart :: User for the Platform Authority Challenge Public Key's user node was not found!");return l.publicKey.user.id=r(l.publicKey.user.id),s?delete l.publicKey.excludeCredentials:Array.isArray(l.publicKey.excludeCredentials)&&l.publicKey.excludeCredentials.forEach(t=>{t&&"string"==typeof t.id&&(t.id=r(t.id))}),l}async pkpRegistrationFinish(t){if(!t)throw new TypeError("[PK+ RelyingParty] :: pkpRegistrationFinish :: No Platform Authenticator Credential provided!");return this.makePostRequest("api/passkey/registration/finish",{credential:t}).then(e=>(this.credentialID=t.rawId,{credential:t,credentialID:this.credentialID,response:{credentialID:this.credentialID,...e}})).catch(t=>{throw this.clearEnrollmentCredentials(),new Error("[PK+ RelyingParty] :: pkpRegistrationFinish :: Unable to complete registration!",{cause:t})})}async pkpAuthenticationStart(t){if(null==t||"object"!=typeof t)throw new TypeError("[PK+ RelyingParty] :: pkpAuthenticationStart :: No Credential Data object provided!");if(!t.hasRemotePasskey&&!t.zsmCredentialID)throw new Error("[PK+ RelyingParty] :: pkpAuthenticationStart :: Credential Data object does not contain a valid Passkeys+ Credential ID or ZSM Credential ID!");const e={credential_id:t?.pkpCredentialID,chain_credential_id:t.zsmCredentialID,chain_handoff:t?.chainHandoff||"internal",chain_challenge:t?.pkpCredential?JSON.stringify(t.pkpCredential):null};let i=await this.makePostRequest("api/passkey/authentication/start",e);if(i instanceof Error||!i)throw new Error("[PK+ RelyingParty] :: pkpAuthenticationStart :: Unable to retrieve Platform Authority Challenge from Relying Party Server!",{cause:i});if(i=n(i),!i?.rcr||!i?.rcr?.publicKey)throw new Error("[PK+ RelyingParty] :: pkpAuthenticationStart :: Platform Authority's RCR challenge and/or publicKey was not found!");const a=i.rcr.publicKey;a.challenge=r(a.challenge);const s=a.allowCredentials;if(!s||!s[0]?.id)throw new Error("[PK+ RelyingParty] :: pkpAuthenticationStart :: Platform Authority Challenge's allowCredentials and/or id was not found!");return s[0].id=r(s[0].id),a}async pkpAuthenticationFinish(t){if(!t||"object"!=typeof t)throw new Error("[PK+ RelyingParty] :: pkpAuthenticationFinish :: No Platform Authenticator Credential provided or is not an object!");return this.makePostRequest("api/passkey/authentication/finish",{credential:t}).then(e=>({credential:t,response:e})).catch(t=>Promise.reject(new Error("[PK+ RelyingParty] :: pkpAuthenticationFinish :: Unable to complete authentication!",{cause:t})))}async getRemoteEnrollmentDetails(t,e,i=!1){const r={customer_defined_identifier:t};return await this.makePostRequest("/api/v1/enrollment-details",r)}}
3
+ async pkpRegistrationStart(t,n,a=null,s=!1){const o=a?{chain_handoff:"internal",chain_credential_id:a}:{chain_handoff:"internal"},l=await this.makePostRequest("api/passkey/registration/create-identity-then-start",o).then(t=>(t.identity_id&&(e.mpcConfig.consumer_id=t.identity_id,i(this.userIdentifier,t.identity_id)),{publicKey:this.publicKey=t.ccr.publicKey,identity_id:t.identity_id})).then(t=>t).catch(t=>Promise.reject(new Error(t)));if(!l)throw new Error("[PK+ RelyingParty] :: pkpRegistrationStart :: Unable to retrieve Platform Authority Challenge from Relying Party Server!");if(!l?.publicKey)throw new Error("[PK+ RelyingParty] :: pkpRegistrationStart :: Platform Authority Challenge's Public Key was not found!");if(l.publicKey.challenge=r(l.publicKey.challenge),!l?.publicKey?.user)throw new Error("[PK+ RelyingParty] :: pkpRegistrationStart :: User for the Platform Authority Challenge Public Key's user node was not found!");return l.publicKey.user.id=r(l.publicKey.user.id),s?delete l.publicKey.excludeCredentials:Array.isArray(l.publicKey.excludeCredentials)&&l.publicKey.excludeCredentials.forEach(t=>{t&&"string"==typeof t.id&&(t.id=r(t.id))}),l}async pkpRegistrationFinish(t,e=!1){if(!t)throw new TypeError("[PK+ RelyingParty] :: pkpRegistrationFinish :: No Platform Authenticator Credential provided!");return this.makePostRequest("api/passkey/registration/finish",{credential:t}).then(i=>(e||(this.credentialID=t.rawId),{credential:t,credentialID:t.rawId,response:{credentialID:t.rawId,...i}})).catch(t=>{throw this.clearEnrollmentCredentials(),new Error("[PK+ RelyingParty] :: pkpRegistrationFinish :: Unable to complete registration!",{cause:t})})}async pkpAuthenticationStart(t){if(null==t||"object"!=typeof t)throw new TypeError("[PK+ RelyingParty] :: pkpAuthenticationStart :: No Credential Data object provided!");if(!t.hasRemotePasskey&&!t.zsmCredentialID)throw new Error("[PK+ RelyingParty] :: pkpAuthenticationStart :: Credential Data object does not contain a valid Passkeys+ Credential ID or ZSM Credential ID!");const e={credential_id:t?.pkpCredentialID,chain_credential_id:t.zsmCredentialID,chain_handoff:t?.chainHandoff||"internal",chain_challenge:t?.pkpCredential?JSON.stringify(t.pkpCredential):null};let i=await this.makePostRequest("api/passkey/authentication/start",e);if(i instanceof Error||!i)throw new Error("[PK+ RelyingParty] :: pkpAuthenticationStart :: Unable to retrieve Platform Authority Challenge from Relying Party Server!",{cause:i});if(i=n(i),!i?.rcr||!i?.rcr?.publicKey)throw new Error("[PK+ RelyingParty] :: pkpAuthenticationStart :: Platform Authority's RCR challenge and/or publicKey was not found!");const a=i.rcr.publicKey;a.challenge=r(a.challenge);const s=a.allowCredentials;if(!s||!s[0]?.id)throw new Error("[PK+ RelyingParty] :: pkpAuthenticationStart :: Platform Authority Challenge's allowCredentials and/or id was not found!");return s[0].id=r(s[0].id),a}async pkpAuthenticationFinish(t){if(!t||"object"!=typeof t)throw new Error("[PK+ RelyingParty] :: pkpAuthenticationFinish :: No Platform Authenticator Credential provided or is not an object!");return this.makePostRequest("api/passkey/authentication/finish",{credential:t}).then(e=>({credential:t,response:e})).catch(t=>Promise.reject(new Error("[PK+ RelyingParty] :: pkpAuthenticationFinish :: Unable to complete authentication!",{cause:t})))}async getRemoteEnrollmentDetails(t,e,i=!1){const r={customer_defined_identifier:t};i||e.zsm_credential_id&&(r.zsm_credential_id=e.zsm_credential_id);const n=await this.makePostRequest("/api/v1/enrollment-details",r);return n?.trace_map&&delete n.trace_map,n}}
package/UMFAClientBase.js CHANGED
@@ -1 +1 @@
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("UMFAClientBase"),this.config=r;const s=t.classes("WEBAUTHNCLIENT");this.zsmAPI=new s(r),this.checkEnrollment=this.checkEnrollment.bind(this),this.enroll=this.enroll.bind(this),this.authenticate=this.authenticate.bind(this),this.checkAllEnrollments=this.checkAllEnrollments.bind(this),this.passkeyPlusEnroll=this.passkeyPlusEnroll.bind(this),this.passkeyPlusAuthenticate=this.passkeyPlusAuthenticate.bind(this),this.unenroll=this.unenroll.bind(this),this.resetDevice=this.resetDevice.bind(this),e.update("UMFAClientBase","READY")}get userIdentifier(){return this.zsmAPI.userIdentifier}get credentialID(){return this.zsmAPI.credentialID}get metadata(){return this.zsmAPI.metadata}set metadata(e){return this.zsmAPI.metadata=e}typeChecks(e,t,s=null,i=""){if(!t)throw new TypeError(`[UMFAClient] :: ${e} :: A userIdentifier String or Object is required! Received: ${t} (type: ${typeof t}).`);let n;if("object"==typeof t&&(n={...t},t=t.userIdentifier),null!=s){if("boolean"==typeof s)s=!0===s?"preferred":"prevented";else{if("string"!=typeof s)throw new TypeError(`[UMFAClient] :: ${e} :: userVerification must be a boolean or string. Received type: ${typeof s}.`);s=s.toLowerCase()}const t=["preferred","required","discouraged","prevented",...i.split(/, ?/g).filter(Boolean)];if(!new RegExp(`^(${t.join("|")})$`).test(s))throw new TypeError(`[UMFAClient] :: ${e} :: userVerification must be one of the following Strings: "${t.join('", "')}"). Received: ${s}.`)}let a={userIdentifier:t=r(t),userVerification:s};return n&&(a={enrollmentDetails:n,...a}),a}async allowBrowserObserversToSettle(){"undefined"!=typeof window&&("function"==typeof requestAnimationFrame&&await new Promise(e=>requestAnimationFrame(e)),await new Promise(e=>setTimeout(e,0)))}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)}async enroll(e,t="prevented"){await this.allowBrowserObserversToSettle(),({userIdentifier:e,userVerification:t}=this.typeChecks("enroll",e,t,"zsm,re-enroll,reenroll"));const r=/^re-?enroll$/.test(t),s="preferred"===t;let i,n,a;if(r||/^(preferred|required)$/.test(t)){n=await this.checkAllEnrollments(e);try{if(n.passkeySupported)if(r){if(!n.zsmCredentialID&&!n.hasLocalPasskey)return new Error(`[UMFAClient] :: enroll :: Cannot RE-enroll user's passkey; user ${e} is not currently enrolled with either ZSM or Passkeys+ credentials. To enroll the user, call enroll with a userVerification value of "preferred", "required", or "discouraged".`);i=await this.passkeyPlusEnroll(n,"required",!0)}else{if(n.zsmCredentialID&&n.hasLocalPasskey)return new Error(`[UMFAClient] :: enroll :: User ${e} is already enrolled with both ZSM and Passkeys+ credentials. To authenticate one or both credential, call the authenticate method.`);n instanceof Error||!1===n||!n.zsmCredentialID&&!n.hasLocalPasskey?i=await this.passkeyPlusEnroll(n,t):n.zsmCredentialID&&!n.hasLocalPasskey?i=await this.addPasskeyToZSMCredential(n,t):!n.zsmCredentialID&&n.pkpCredential&&(i=await this.addZSMToPasskeyCredential(n,t))}else{if(!s)return new Error(`[UMFAClient] :: enroll :: Passkeys+ is required for enrollment (userVerification 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(`[UMFAClient] :: enroll :: 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===i?new Error(`[UMFAClient] :: enroll :: Unexpected PKP flow: PKP enrollment attempted but no result was returned for user ${e}.`):i}catch(r){if(console.error("ENROLL FLOW :: PASSKEY OPERATION FAILED ::",r),!s)return new Error(`[UMFAClient] :: enroll :: The passkey portion of the Passkeys+ enrollment failed for user ${e} with error: ${r.message||r}. Since userVerification is set to "${t}", the entire enrollment process has failed. You may: retry, downgrade the userVerification mandate to "preferred" or "discouraged" to allow for ZSM-only enrollment.`)}}if(a=n?.zsmCredentialID?n?.zsmCredentialID:await this.checkEnrollment(e),"string"==typeof a)return new Error(`[UMFAClient] :: enroll :: User ${e} is already enrolled with a ZSM credential. To authenticate it, call the authenticate method.`);await this.zsmAPI.checkIdentity(e,!0);const l=await this.zsmAPI.webauthnCreate(e);return l instanceof Error?new Error(`[UMFAClient] :: enroll :: Unable to enroll ${e}!\nDetails:\n${l?.message||l}`,{cause:l?.stack}):l}async authenticate(e,t="prevented"){await this.allowBrowserObserversToSettle(),({userIdentifier:e,userVerification:t}=this.typeChecks("authenticate",e,t,"zsm"));const r=/^(preferred|required)$/.test(t),s="preferred"===t;let i,n,a;if(r){if(n=await this.checkAllEnrollments(e),!n.zsmCredentialID)return new Error(`[UMFAClient] :: authenticate :: User ${e} must be enrolled with a ZSM credential before authenticating. Please enroll the user first.`);if(!n.passkeySupported)return new Error(`[UMFAClient] :: authenticate :: User ${e} is not eligible for Passkeys+ authentication; their device does not support passkey authentication. To authenticate, call the authenticate method with userVerification set to "preferred", "discouraged", or "prevented"/"zsm". To enroll with Passkeys+, call enroll with userVerification set to "preferred" or "required" on a Passkeys+-compatible device.`);if(!n.hasLocalPasskey)return new Error(`[UMFAClient] :: authenticate :: User ${e} must be enrolled with both ZSM and Passkeys+ credentials to authenticate using Passkeys+ (userVerification set to a non-prevented value).`);if(r){try{i=await this.passkeyPlusAuthenticate(n,t)}catch(r){if(!s)return new Error(`[UMFAClient] :: authenticate :: The passkey portion of the Passkeys+ authentication failed for user ${e} with error: ${r.message||r}. Since userVerification is set to "${t}", the entire authentication process has failed. You may: retry or downgrade the userVerification mandate to "preferred" or "discouraged" to allow for ZSM-only authentication.`)}return void 0===i?new Error(`[UMFAClient] :: authenticate :: Unexpected PKP flow: PKP authentication attempted but no result was returned for user ${e}.`):i}}a=n?.zsmCredentialID?n?.zsmCredentialID:await this.checkEnrollment(e);if(!1===("string"==typeof a))return new Error(`[UMFAClient] :: authenticate :: ${e} is not enrolled. To authenticate the user, please enroll them first by calling the enroll method.`);const l=await this.zsmAPI.webauthnPartialGet(e);return l?.credential??l}async addPasskey(e,t="required"){let r,s;return await this.allowBrowserObserversToSettle(),({userIdentifier:e,userVerification:t,enrollmentDetails:r}=this.typeChecks("addPasskey",e,t,"re-enroll,reenroll")),s=r?await this.zsmAPI.pkpCreate(r||e,t):await this.addPasskeyToZSMCredential(r||e,t),s}async passkeyPlusEnroll(e,t="preferred",r=!1){await this.allowBrowserObserversToSettle(),({userIdentifier:e,userVerification:t}=this.typeChecks("passkeyPlusEnroll",e,t,"re-enroll,reenroll"));return await this.zsmAPI.pkpCreate(e,t,!1,r)}async passkeyPlusAuthenticate(e,t="preferred"){await this.allowBrowserObserversToSettle();let r=null;({userIdentifier:e,userVerification:t,enrollmentDetails:r}=this.typeChecks("passkeyPlusAuthenticate",e,t,"re-enroll,reenroll")),r||(r=await this.checkAllEnrollments(e,!0));return await this.zsmAPI.pkpAuthenticate(r,t,!r.zsmCredentialID)}async addZSMToPasskeyCredential(e,t){({userIdentifier:e}=this.typeChecks("addZSMToPasskeyCredential",e));return await this.passkeyPlusAuthenticate(e)}async addPasskeyToZSMCredential(e,t="preferred"){let r=null;({userIdentifier:e,userVerification:t,enrollmentDetails:r}=this.typeChecks("addPasskeyToZSMCredential",e,t,"re-enroll,reenroll"));return await this.zsmAPI.pkpAddPasskeyCredential(r||e,t)}async unenroll(e){if(({userIdentifier:e}=this.typeChecks("unenroll",e)),!1===await this.checkEnrollment(e))return!1;let t=await this.zsmAPI.unbindFromDevice(e);if(t instanceof Error)throw`Unable to unenroll ${e}'s identity from this device.\nDetails:\n${t}`;return!0===t}async resetDevice(){this.zsmAPI.webauthnReset()}}
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("UMFAClientBase"),this.config=r;const n=t.classes("WEBAUTHNCLIENT");this.zsmAPI=new n(r),this.checkEnrollment=this.checkEnrollment.bind(this),this.enroll=this.enroll.bind(this),this.authenticate=this.authenticate.bind(this),this.checkAllEnrollments=this.checkAllEnrollments.bind(this),this.passkeyPlusEnroll=this.passkeyPlusEnroll.bind(this),this.passkeyPlusAuthenticate=this.passkeyPlusAuthenticate.bind(this),this.unenroll=this.unenroll.bind(this),this.resetDevice=this.resetDevice.bind(this),e.update("UMFAClientBase","READY")}get userIdentifier(){return this.zsmAPI.userIdentifier}get credentialID(){return this.zsmAPI.credentialID}get metadata(){return this.zsmAPI.metadata}set metadata(e){return this.zsmAPI.metadata=e}typeChecks(e,t,n=null,s=""){if(!t)throw new TypeError(`[UMFAClient] :: ${e} :: A userIdentifier String or Object is required! Received: ${t} (type: ${typeof t}).`);let i;if("object"==typeof t&&(i={...t},t=t.userIdentifier),null!=n){if("boolean"==typeof n)n=!0===n?"preferred":"prevented";else{if("string"!=typeof n)throw new TypeError(`[UMFAClient] :: ${e} :: userVerification must be a boolean or string. Received type: ${typeof n}.`);n=n.toLowerCase()}const t=["preferred","required","discouraged","prevented",...s.split(/, ?/g).filter(Boolean)];if(!new RegExp(`^(${t.join("|")})$`).test(n))throw new TypeError(`[UMFAClient] :: ${e} :: userVerification must be one of the following Strings: "${t.join('", "')}"). Received: ${n}.`)}let a={userIdentifier:t=r(t),userVerification:n};return i&&(a={enrollmentDetails:i,...a}),a}async allowBrowserObserversToSettle(){"undefined"!=typeof window&&("function"==typeof requestAnimationFrame&&await new Promise(e=>requestAnimationFrame(e)),await new Promise(e=>setTimeout(e,0)))}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)}async enroll(e,t="prevented"){await this.allowBrowserObserversToSettle(),({userIdentifier:e,userVerification:t}=this.typeChecks("enroll",e,t,"zsm,re-enroll,reenroll"));const r=/^re-?enroll$/.test(t),n="preferred"===t;let s,i,a;if(r||/^(preferred|required)$/.test(t)){if(i=await this.checkAllEnrollments(e),i instanceof Error)return i;if(await this.clearUnvalidatedEnrollment(e,i)&&(i=await this.checkAllEnrollments(e),i instanceof Error))return i;try{if(i.passkeySupported)if(r){if(!i.zsmCredentialID&&!i.hasLocalPasskey)return new Error(`[UMFAClient] :: enroll :: Cannot RE-enroll user's passkey; user ${e} is not currently enrolled with either ZSM or Passkeys+ credentials. To enroll the user, call enroll with a userVerification value of "preferred", "required", or "discouraged".`);s=await this.passkeyPlusEnroll(i,"required",!0)}else{if(i.zsmCredentialID&&i.hasLocalPasskey)return new Error(`[UMFAClient] :: enroll :: User ${e} is already enrolled with both ZSM and Passkeys+ credentials. To authenticate one or both credential, call the authenticate method.`);i instanceof Error||!1===i||!i.zsmCredentialID&&!i.hasLocalPasskey?s=await this.passkeyPlusEnroll(i,t):i.zsmCredentialID&&!i.hasLocalPasskey?s=await this.addPasskeyToZSMCredential(i,t):!i.zsmCredentialID&&i.pkpCredential&&(s=await this.addZSMToPasskeyCredential(i,t))}else{if(!n)return new Error(`[UMFAClient] :: enroll :: Passkeys+ is required for enrollment (userVerification 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(`[UMFAClient] :: enroll :: 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===s?new Error(`[UMFAClient] :: enroll :: Unexpected PKP flow: PKP enrollment attempted but no result was returned for user ${e}.`):s}catch(r){if(console.error("ENROLL FLOW :: PASSKEY OPERATION FAILED ::",r),!n){const n=new Error(`[UMFAClient] :: enroll :: The passkey portion of the Passkeys+ enrollment failed for user ${e} with error: ${r.message||r}. Since userVerification is set to "${t}", the entire enrollment process has failed. You may: retry, downgrade the userVerification mandate to "preferred" or "discouraged" to allow for ZSM-only enrollment.`,{cause:r});return n.name=r?.name||"Error",n}}}if(a=i?.zsmCredentialID?i?.zsmCredentialID:await this.checkEnrollment(e),"string"==typeof a){if(i||(i=await this.checkAllEnrollments(e)),i instanceof Error||!await this.clearUnvalidatedEnrollment(e,i))return new Error(`[UMFAClient] :: enroll :: User ${e} is already enrolled with a ZSM credential. To authenticate it, call the authenticate method.`);a=!1}await this.zsmAPI.checkIdentity(e,!0);const l=await this.zsmAPI.webauthnCreate(e);return l instanceof Error?new Error(`[UMFAClient] :: enroll :: Unable to enroll ${e}!\nDetails:\n${l?.message||l}`,{cause:l?.stack}):l}async authenticate(e,t="prevented"){await this.allowBrowserObserversToSettle(),({userIdentifier:e,userVerification:t}=this.typeChecks("authenticate",e,t,"zsm"));const r=/^(preferred|required)$/.test(t),n="preferred"===t;let s,i,a;if(r){if(i=await this.checkAllEnrollments(e),i instanceof Error)return i;if(await this.clearUnvalidatedEnrollment(e,i))return new Error(`[UMFAClient] :: authenticate :: User ${e}'s enrollment was never validated, and has been cleared from this device. Please enroll the user again.`);if(!i.zsmCredentialID)return new Error(`[UMFAClient] :: authenticate :: User ${e} must be enrolled with a ZSM credential before authenticating. Please enroll the user first.`);if(i.passkeySupported)if(i.hasLocalPasskey){if(r){try{s=await this.passkeyPlusAuthenticate(i,t)}catch(r){if(!n){const n=new Error(`[UMFAClient] :: authenticate :: The passkey portion of the Passkeys+ authentication failed for user ${e} with error: ${r.message||r}. Since userVerification is set to "${t}", the entire authentication process has failed. You may: retry or downgrade the userVerification mandate to "preferred" or "discouraged" to allow for ZSM-only authentication.`,{cause:r});return n.name=r?.name||"Error",n}}return void 0===s?new Error(`[UMFAClient] :: authenticate :: Unexpected PKP flow: PKP authentication attempted but no result was returned for user ${e}.`):s}}else{if(!n)return new Error(`[UMFAClient] :: authenticate :: User ${e} holds no passkey on this device, and userVerification is "required". Call enroll to add one. Do not retry authenticate: no passkey exists, so this call gives the same error until enroll creates one.`);console.warn(`[UMFAClient] :: authenticate :: User ${e} holds no passkey on this device. They are authenticated with their ZSM credential alone.`)}else{if(!n)return new Error(`[UMFAClient] :: authenticate :: User ${e} is not eligible for Passkeys+ authentication; their device does not support passkey authentication. To authenticate, call the authenticate method with userVerification set to "preferred", "discouraged", or "prevented"/"zsm". To enroll with Passkeys+, call enroll with userVerification set to "preferred" or "required" on a Passkeys+-compatible device.`);console.warn(`[UMFAClient] :: authenticate :: This device does not support passkeys. User ${e} is authenticated with their ZSM credential alone.`)}}a=i?.zsmCredentialID?i?.zsmCredentialID:await this.checkEnrollment(e);if(!1===("string"==typeof a))return new Error(`[UMFAClient] :: authenticate :: ${e} is not enrolled. To authenticate the user, please enroll them first by calling the enroll method.`);const l=await this.zsmAPI.webauthnPartialGet(e);return l?.credential??l}async addPasskey(e,t="required"){let r,n;return await this.allowBrowserObserversToSettle(),({userIdentifier:e,userVerification:t,enrollmentDetails:r}=this.typeChecks("addPasskey",e,t,"re-enroll,reenroll")),n=r?await this.zsmAPI.pkpCreate(r||e,t):await this.addPasskeyToZSMCredential(r||e,t),n}async passkeyPlusEnroll(e,t="preferred",r=!1){await this.allowBrowserObserversToSettle(),({userIdentifier:e,userVerification:t}=this.typeChecks("passkeyPlusEnroll",e,t,"re-enroll,reenroll"));return await this.zsmAPI.pkpCreate(e,t,!1,r)}async passkeyPlusAuthenticate(e,t="preferred"){await this.allowBrowserObserversToSettle();let r=null;if(({userIdentifier:e,userVerification:t,enrollmentDetails:r}=this.typeChecks("passkeyPlusAuthenticate",e,t,"re-enroll,reenroll")),r||(r=await this.checkAllEnrollments(e,!0)),r instanceof Error)return r;if(await this.clearUnvalidatedEnrollment(e,r))return new Error(`[UMFAClient] :: passkeyPlusAuthenticate :: User ${e}'s enrollment was never validated, and has been cleared from this device. Please enroll the user again.`);return await this.zsmAPI.pkpAuthenticate(r,t,!r.zsmCredentialID)}async addZSMToPasskeyCredential(e,t){({userIdentifier:e}=this.typeChecks("addZSMToPasskeyCredential",e));return await this.passkeyPlusAuthenticate(e)}async addPasskeyToZSMCredential(e,t="preferred"){let r=null;({userIdentifier:e,userVerification:t,enrollmentDetails:r}=this.typeChecks("addPasskeyToZSMCredential",e,t,"re-enroll,reenroll"));return await this.zsmAPI.pkpAddPasskeyCredential(r||e,t)}async unenroll(e){if(({userIdentifier:e}=this.typeChecks("unenroll",e)),!1===await this.checkEnrollment(e))return!1;let t=await this.zsmAPI.unbindFromDevice(e);if(t instanceof Error)throw new Error(`Unable to unenroll ${e}'s identity from this device.\nDetails:\n${t.message||t}`,{cause:t});return!0===t}async clearUnvalidatedEnrollment(e,t){if(!("confirmed"===t?.remoteCheckStatus&&!1===t?.enrollmentActive&&!!t?.zsmCredentialID))return!1;try{return await this.unenroll(e),console.warn(`[UMFAClient] :: clearUnvalidatedEnrollment :: ${e}'s credential was never validated and has been cleared from this device. The user must enroll again.`),!0}catch(e){return console.error("[UMFAClient] :: clearUnvalidatedEnrollment :: Unable to clear the unvalidated credential:",e),!1}}async resetDevice(){this.zsmAPI.webauthnReset()}async getInstallationId(){const e=await this.zsmAPI.primeZsmApi(),t=await e.installation_id();if(!t||"string"!=typeof t)throw new TypeError(`[UMFAClient] :: getInstallationId :: No installation identifier is available! Received: ${t} (type: ${typeof t}).`);return t}}