@ideem/zsm-client-sdk 2.9.4 → 2.9.5
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 +42 -1
- package/FIDO2ClientBase.js +1 -1
- package/IdentityIndexing.js +1 -1
- package/PasskeySupport.js +1 -0
- package/PasskeysPlusClient.js +1 -1
- package/RelyingPartyBase.js +1 -1
- package/UMFAClientBase.js +1 -1
- package/WASMRustInterface.js +1 -1
- package/WebAuthnClientBase.js +1 -1
- package/ZSMClientSDK.js +1 -1
- package/package.json +98 -69
package/CHANGELOG.md
CHANGED
|
@@ -12,7 +12,48 @@ and Universal MFA into a browser application.
|
|
|
12
12
|
|
|
13
13
|
----
|
|
14
14
|
|
|
15
|
-
## Release 2.9.
|
|
15
|
+
## Release 2.9.5 - October 30, 2026 **latest release**
|
|
16
|
+
- `checkAllEnrollments()` now consults the Ideem server on every call. Earlier releases skipped the server whenever the local record looked complete, so the device's own view of an enrollment could go unverified indefinitely. The `forceRemoteCheck` argument is still accepted but no longer has any effect, and `remoteCheckStatus` is never `"skipped"`. A call whose server check fails still resolves with the local data, as described below.
|
|
17
|
+
- `checkAllEnrollments()` now reports a server check that failed with an HTTP error separately from one that got no answer at all. Earlier releases reported both as `"unavailable"`, so a misconfiguration (HTTP 400) and a rejected API key (HTTP 401) looked identical, and both looked like an unreachable server.
|
|
18
|
+
- `remoteCheckStatus` is `"error::NNN"` when the server answered with an HTTP error, `NNN` being its status. It remains `"unavailable"` only when no answer was received (offline, DNS, CORS).
|
|
19
|
+
- When the check fails and the device holds no enrollment, `state` is `"ERROR::NNN"` (or `"ERROR"` when there is no HTTP status) instead of `"NONE"`. An unverified `"NONE"` cannot be told apart from a user who is genuinely not enrolled, and acting on it sends an enrolled user back through enrollment. A local `"BOTH"` or `"MPC_ONLY"` is never overwritten, because the user can still authenticate with it. Test for a failure with `/^ERROR/`: the `::NNN` suffix is advisory and may be absent.
|
|
20
|
+
- The result carries a new `error` property: `{ status, traceId, text }` describing the failed check, or `null` when the check succeeded. `status` is `null` when no answer was received.
|
|
21
|
+
- A failed request to the Ideem server now produces an `Error` that keeps the HTTP status, even when the error response is not JSON (for example an HTML page from a proxy). Earlier releases could lose the status entirely in that case.
|
|
22
|
+
- Headers passed for a single request to the Ideem server no longer carry over into later requests.
|
|
23
|
+
- Handles a credential that the Ideem server reports as `replaced`, meaning it has been superseded by an enrollment elsewhere. Before resolving, `checkAllEnrollments()` removes that user's credential from the device, then resolves exactly as it would for a user the device has never seen: any remote passkey is still reported, but the device no longer holds or reports the ZSM credential. If the credential cannot be removed after two attempts, ALL local Ideem data on the device is deleted, including other users' credentials and the installation identifier. If even that fails, `checkAllEnrollments()` resolves with an `Error` whose `code` is `"ENROLLMENT_DETAILS_UNAVAILABLE"`.
|
|
24
|
+
- The result carries `replaced: true` when the server reported the device's credential as replaced and it is no longer on the device, and `localDataWiped: true` when removing it took deleting ALL local Ideem data. Both are `false` otherwise. After a wipe, the SDK reconfigures itself, so `enroll()` works in the same tab without a reload.
|
|
25
|
+
- A wipe that is blocked by an open connection (for example another tab) is now reported: `checkAllEnrollments()` resolves with an `Error` (`code` `"ENROLLMENT_DETAILS_UNAVAILABLE"`, `cause.code` `"LOCAL_DATA_WIPE_BLOCKED"`). Earlier releases reported success while the deletion was still queued.
|
|
26
|
+
- The credential is removed only when the device can read it back. If the local record cannot be read, nothing more is deleted and `checkAllEnrollments()` resolves with an `Error` (`cause.code` `"LOCAL_CREDENTIAL_UNREADABLE"`); a failed read no longer counts as a removal, and no longer leads to the full wipe.
|
|
27
|
+
- The credential is removed only when it is still the one the server was asked about. If another credential was stored meanwhile (for example by an enrollment in another tab), before or during the removal, it is left intact and the check is repeated once for that credential instead.
|
|
28
|
+
- `UMFAClient.authenticate()` resolves with an `Error` whose `code` is `"ENROLLMENT_REPLACED"` when the user's enrollment was replaced on another device: in passkey mode after `checkAllEnrollments()` removed the replaced credential (earlier releases said the user "must be enrolled"), and in ZSM mode when the server refuses the credential with error 15040. The Crypto Module now passes the server's error on an MPC round through to the SDK, which the 15040 case needs. The credential is not removed by `authenticate()`; the next `checkAllEnrollments()` or `enroll()` removes it.
|
|
29
|
+
- `checkAllEnrollments()` reports a `pending` or `suspended` enrollment as not enrolled (`state` `"NONE"`, `enrollmentActive` `false`), even when the server's `zsm_active` is `true`. The server's state is exposed as `enrollmentState` (`"active"`, `"pending"`, `"suspended"`, or `null` when not reported), and whether the ZSM credential itself was validated as `credentialActive` (`true`, `false`, or `null` when unknown), independently of the enrollment's state. A credential is cleared from the device only when the server reports the credential itself as never validated, as before, whether its enrollment reads `pending` or (under AutoVerify) `active`. A suspended enrollment's credential is always kept, and so is a validated credential whose enrollment is pending activation.
|
|
30
|
+
- `unenroll()` now removes only the given user's local records. Earlier releases matched records by substring, so unenrolling `turley@x` also removed records of `aturley@x`.
|
|
31
|
+
- `UMFAClient.enroll()` now resolves with the actual `Error` when the enrollment lookup fails, instead of reporting that the user is already enrolled, and resolves (rather than rejects) with an `Error` when the identity check fails.
|
|
32
|
+
- `FIDO2Client.webauthnGet()` and `FIDO2Client.webauthnCreate()` follow the same rules as `UMFAClient.authenticate()` and `UMFAClient.enroll()`: `webauthnGet()` resolves with an `ENROLLMENT_REPLACED` `Error` for a replaced enrollment (in passkey mode after the purge, in ZSM mode on 15040), and both resolve with the actual `Error` when the enrollment lookup fails. `webauthnCreate()` now enrolls a user whose local credential `checkAllEnrollments()` has just removed as replaced, instead of reporting that the user is already enrolled, and resolves (rather than rejects) with an `Error` when the identity check fails.
|
|
33
|
+
- The Crypto Module closes its database connection on every path, including errors. An open connection could block the deletion of the local database (`LOCAL_DATA_WIPE_BLOCKED`).
|
|
34
|
+
- The device unenrollment used by `unenroll()` now closes its database connection when it finishes, whether it succeeds or fails. An open connection could block a later deletion of the local database.
|
|
35
|
+
- `UMFAClient.authenticate()` with `userVerification` set to `"preferred"` now falls back to the ZSM credential alone when the passkey ceremony fails. A user cancellation also causes this fallback. Earlier releases returned an error. The iOS and Android SDKs already behave this way.
|
|
36
|
+
- `UMFAClient.authenticate()` with `userVerification` set to `"preferred"` now completes the ZSM-only fallback where the browser does not support passkeys, or where the user holds no passkey on the device. Earlier releases failed with `Credential is missing` on each fallback.
|
|
37
|
+
- With `userVerification` set to `"required"`, a passkey failure still fails the authentication.
|
|
38
|
+
- Adds `isPasskeySupported()`, `passkeyNotSupportedReason()` and `getPasskeySupportStatus()` to the `UMFAClient` class. The names and the report shape match the Android SDK. Each method resolves asynchronously.
|
|
39
|
+
- `isPasskeySupported()` resolves with `false` only when a signal proves that a passkey ceremony cannot start:
|
|
40
|
+
- The page is not a secure context.
|
|
41
|
+
- The browser does not expose `PublicKeyCredential` and `navigator.credentials`.
|
|
42
|
+
- The permissions policy of the frame blocks both `publickey-credentials-create` and `publickey-credentials-get`. Only Chromium exposes this policy to a script.
|
|
43
|
+
- A missing platform authenticator does not turn passkeys off. A phone, a security key or a password manager extension can still hold the passkey.
|
|
44
|
+
- A frame that blocks only one ceremony keeps passkey support. The blocked ceremony now fails at once with an error that names the policy and the `allow` value for the iframe. Earlier releases started the ceremony, and the browser then rejected it with an unexplained `NotAllowedError`.
|
|
45
|
+
- `getPasskeySupportStatus()` resolves with a report. The report has `"platform": "browser"` and these fields:
|
|
46
|
+
- `supported`, `capability` and `reason`. The `capability` value is one of `"capable"`, `"no_platform_authenticator"`, `"unsupported"` or `"unknown"`.
|
|
47
|
+
- `secure_context`, `webauthn_api`, `create_allowed`, `get_allowed` and `cross_origin_frame`.
|
|
48
|
+
- `platform_authenticator`, from `isUserVerifyingPlatformAuthenticatorAvailable()`. The value is `null` where the browser answers a constant: Chromium on Linux, and Firefox on systems other than Windows. The value is also `null` where an extension replaced the check. In WebKit, a `false` value includes an app whose webview cannot use passkeys for the RP.
|
|
49
|
+
- `hybrid_transport`. Only desktop Chromium measures this value, so other browsers report `null`.
|
|
50
|
+
- `api_wrapped`. The value is `true` when a script, usually a password manager extension, replaced a WebAuthn function.
|
|
51
|
+
- `client_capabilities`, the unchanged result of `getClientCapabilities()`. The SDK takes no decision from it, because most engines return constants.
|
|
52
|
+
- A `null` field means that the browser gives no reliable value for that field. A caller must not treat `null` as `false`.
|
|
53
|
+
- The client logs the report with `console.debug` when it starts.
|
|
54
|
+
- Fixes the ZSM-only fallback of `enroll()` on `UMFAClient` and `webauthnCreate()` on `FIDO2Client`. Where the browser does not support passkeys and `userVerification` is `"preferred"`, earlier releases returned an "Unexpected PKP flow" `Error` and enrolled nothing. The methods now enroll the user with a ZSM credential only, as the warning in the console says. Under `"required"`, the methods still return an `Error`.
|
|
55
|
+
|
|
56
|
+
## Release 2.9.4 - September 14, 2026
|
|
16
57
|
- Removes the optional `installation_id` override mechanism that release 2.9.0 added.
|
|
17
58
|
- `InstallationID::get()` now always derives the identifier from the IndexedDB keypair at `installation_id_key_pair`.
|
|
18
59
|
- 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.
|
package/FIDO2ClientBase.js
CHANGED
|
@@ -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){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)
|
|
1
|
+
import e from"./EventCoordinator.js";import{zsmPluginManager as t}from"./PluginManager.js";import{encodeUserIdentifier as r}from"./encodeUserIdentifier.js";const s=e=>{const t=new Error(`[FIDO2Client] :: webauthnGet :: User ${e}'s enrollment was replaced by a newer one on another device, and has been removed from this device. Please enroll the user again.`);return t.code="ENROLLMENT_REPLACED",t};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)){if(i=await this.checkAllEnrollments(e),i instanceof Error)return i;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.`)}if(i?.passkeySupported)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){if(i||(i=await this.checkAllEnrollments(e)),i instanceof Error)return i;if(i.zsmCredentialID)return new Error(`[FIDO2Client] :: webauthnCreate :: User ${e} is already enrolled with a ZSM credential. To authenticate it, call the webauthnGet method.`);a=!1}try{await this.zsmAPI.checkIdentity(e,!0)}catch(t){return new Error(`[FIDO2Client] :: webauthnCreate :: Unable to establish an identity for ${e}!\nDetails:\n${t?.message||t}`,{cause:t})}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),n="preferred"===t;let i,a,l;if(r){if(a=await this.checkAllEnrollments(e),a instanceof Error)return a;if(!a.zsmCredentialID)return a.replaced?s(e):new Error(`[FIDO2Client] :: webauthnGet :: User ${e} must be enrolled with a ZSM credential before authenticating. Please enroll the user first.`);if(!a.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(!a.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{i=await this.webauthnPasskeyPlusGet(a,t)}catch(r){if("ENROLLMENT_REPLACED"===r?.code)return r;if(!n){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===i?new Error(`[FIDO2Client] :: webauthnGet :: Unexpected PKP flow: PKP authentication attempted but no result was returned for user ${e}.`):i}}l=a?.zsmCredentialID?a?.zsmCredentialID:await this.checkEnrollment(e);if(!1===("string"==typeof l))return new Error(`[FIDO2Client] :: webauthnGet :: ${e} is not enrolled. To authenticate the user, please enroll them first by calling the webauthnCreate method.`);let o;try{o=await this.zsmAPI.webauthnPartialGet(e)}catch(e){if("ENROLLMENT_REPLACED"===e?.code)return e;throw e}return o?.credential??o}
|
|
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)}}
|
package/IdentityIndexing.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
const e=(e,n=e.target.result)=>{n.objectStoreNames.contains("zsm")||n.createObjectStore("zsm")};function n(n,o){return new Promise(r=>{const t=(e,n)=>(e?.close(),console.error(`[IdentityIndexing] :: uid2iid :: Unable to bind UID to IID:\n${n}`),r(!1)),s=indexedDB.open("ideem",1);s.onupgradeneeded=e,s.onerror=()=>t(null,s.error),s.onsuccess=(e,s=e.target.result)=>{s.onversionchange=()=>s.close();try{const e=s.transaction("zsm","readwrite").objectStore("zsm").put(o,n);e.onsuccess=()=>(s.close(),r(!0)),e.onerror=()=>t(s,e.error)}catch(e){t(s,e)}}})}function o(n){return new Promise((o,r)=>{const t=(e,n)=>(e?.close(),console.error(`[IdentityIndexing] :: iid4uid :: Unable to find IID for UID:\n${n}`),r(new Error(`[IdentityIndexing] :: iid4uid :: Unable to find IID for UID:\n${n}`))),s=indexedDB.open("ideem",1);s.onupgradeneeded=e,s.onerror=(e=s.error)=>r(new Error(`[IdentityIndexing] :: iid4uid :: Unable to connect to IDB:\n${e}`)),s.onsuccess=(e,r=e.target.result)=>{if(r.onversionchange=()=>r.close(),!r.objectStoreNames.contains("zsm"))return r.close(),o(n);try{const e=r.transaction("zsm","readonly").objectStore("zsm").get(n);e.onsuccess=()=>(r.close(),e.result?o(e.result):o(n)),e.onerror=()=>t(r,e.error)}catch(e){t(r,e)}}})}export{n as uid2iid,o as iid4uid};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=1,t=Object.freeze({CAPABLE:"capable",NO_PLATFORM_AUTHENTICATOR:"no_platform_authenticator",UNSUPPORTED:"unsupported",UNKNOWN:"unknown"}),n="publickey-credentials-create",o="publickey-credentials-get",r=`${n}; ${o}`;function i(e=globalThis){const t=e?.navigator?.credentials;return{secureContext:!0===e?.isSecureContext,webauthnApi:"function"==typeof e?.PublicKeyCredential&&"function"==typeof t?.create&&"function"==typeof t?.get,createAllowed:s(e,n),getAllowed:s(e,o),crossOriginFrame:u(e)}}function a(e){return e.secureContext&&e.webauthnApi&&!(!1===e.createAllowed&&!1===e.getAllowed)}function l(e){return e.secureContext?e.webauthnApi?!1===e.createAllowed&&!1===e.getAllowed?`The permissions policy of this frame blocks ${n} and ${o}. Add allow="${r}" to the iframe element.`:!1===e.createAllowed?`The permissions policy of this frame blocks ${n}, so passkey enrollment cannot start. Add allow="${r}" to the iframe element.`:!1===e.getAllowed?`The permissions policy of this frame blocks ${o}, so passkey authentication cannot start. Add allow="${r}" to the iframe element.`:null:"This browser does not expose the WebAuthn API (PublicKeyCredential and navigator.credentials).":"This page is not a secure context. WebAuthn requires HTTPS or localhost."}async function c(e,n=globalThis){const o=a(e),r=e.webauthnApi?n.PublicKeyCredential:null,i=r?.isUserVerifyingPlatformAuthenticatorAvailable,c="function"==typeof i&&!p(i),s=e.webauthnApi&&(!p(n.navigator.credentials.create)||!p(n.navigator.credentials.get)||c),[u,d]=await Promise.all([f(r,"isUserVerifyingPlatformAuthenticatorAvailable"),f(r,"getClientCapabilities")]),b=function(e){if(!e||"object"!=typeof e)return null;const t="function"==typeof e.entries&&"function"==typeof e.get?Array.from(e.entries()):Object.entries(e),n={};for(const[e,o]of t)"boolean"==typeof o&&(n[e]=o);return n}(d),h=function(e){const t=e?.navigator?.userAgentData,n=String(e?.navigator?.userAgent||""),o=Array.isArray(t?.brands)&&t.brands.some(e=>"Chromium"===e?.brand),r=!o&&/\bFirefox\/\d/.test(n);return{chromium:o,chromiumDesktop:o&&!1===t.mobile&&"Android"!==t.platform,chromiumLinux:o&&"Linux"===t.platform,gecko:r,geckoWindows:r&&/\bWindows\b/.test(n)}}(n),m=function(e,t,n){return"boolean"!=typeof e||t||n.chromiumLinux||n.gecko&&!n.geckoWindows?null:e}(u,c,h),y=h.chromiumDesktop&&"boolean"==typeof b?.hybridTransport?b.hybridTransport:null;let A=t.UNKNOWN;return o?!0===m?A=t.CAPABLE:!1===m&&(A=t.NO_PLATFORM_AUTHENTICATOR):A=t.UNSUPPORTED,{schema:1,platform:"browser",supported:o,capability:A,secure_context:e.secureContext,webauthn_api:e.webauthnApi,create_allowed:e.createAllowed,get_allowed:e.getAllowed,cross_origin_frame:e.crossOriginFrame,platform_authenticator:m,hybrid_transport:y,api_wrapped:s,client_capabilities:b,reason:l(e)}}export{t as PASSKEY_CAPABILITY,e as PASSKEY_SUPPORT_REPORT_SCHEMA_VERSION,i as readPasskeyGates,a as isSupportedByGates,l as passkeyGateReason,c as buildPasskeySupportStatus};function s(e,t){try{const n=e?.document?.featurePolicy;return"function"!=typeof n?.allowsFeature||"function"!=typeof n?.features?null:n.features().includes(t)?!0===n.allowsFeature(t):null}catch{return null}}function u(e){try{if(!e?.top||!e?.self)return null;if(e.top===e.self)return!1;const t=e.location?.ancestorOrigins&&"number"==typeof e.location.ancestorOrigins.length?Array.from(e.location.ancestorOrigins):null;if(t&&!t.includes("null"))return t.some(t=>t!==e.location.origin);for(let t=e.self;t!==t.parent;t=t.parent)if(t.parent.location.origin!==e.location?.origin)return!0;return!1}catch{return!0}}async function f(e,t){try{return"function"!=typeof e?.[t]?null:await e[t]()}catch{return null}}function p(e){try{return"function"==typeof e&&/\{\s*\[native code\]\s*\}\s*$/.test(Function.prototype.toString.call(e))}catch{return!1}}
|
package/PasskeysPlusClient.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"./EventCoordinator.js";export default class{constructor(){e.update("PasskeysPlus"),this.passkeyAPI=(navigator?.credentials??null)||null,this.
|
|
1
|
+
import e from"./EventCoordinator.js";import{readPasskeyGates as t,isSupportedByGates as r,passkeyGateReason as a,buildPasskeySupportStatus as s}from"./PasskeySupport.js";export default class{constructor(){e.update("PasskeysPlus"),this.passkeyAPI=(navigator?.credentials??null)||null,this.passkeyGates=t(),this.deviceSupportsPasskeys=r(this.passkeyGates),e.update("PasskeysPlus","READY")}get passkeysSupported(){return this.deviceSupportsPasskeys}pkpSupported=()=>this.deviceSupportsPasskeys;passkeyNotSupportedReason=()=>a(this.passkeyGates);getPasskeySupportStatus=()=>s(this.passkeyGates);blockUnsupportedInvocation=e=>{const t="create"===e&&!1===this.passkeyGates.createAllowed||"get"===e&&!1===this.passkeyGates.getAllowed;if(!this.pkpSupported()||t)throw new Error(`[PK+ PasskeysPlus] :: blockUnsupportedInvocation :: This browser and/or device does not support the PasskeysPlus API! ${this.passkeyNotSupportedReason()}`);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("create"))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("get"))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}})}}
|
package/RelyingPartyBase.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
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(
|
|
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(i,r,n,a,s,o){this.host=i,this.apiKey=r,this.applicationId=n,this.appEnvironment=a||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",...e.ZSMVersions?{"X-SDK-Version":JSON.stringify(e.ZSMVersions)}:{}},this.addlRelyingPartyBodyProps=s?{use_origin:"true"}:{},this.metadata=o||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.text().then(t=>{let e=null;try{e=t?JSON.parse(t):null}catch(t){}return e}).catch(()=>null).then(e=>{throw function(t,e){const i=new Error(`[RelyingParty] :: makePostRequest :: Request failed: ${t.status}${e?.message?` -- ${e.message}`:""}`);return i.status=t.status,i.traceId=e?.trace_id??null,i.text=e?.message??e?.detail??null,i.body=e??null,i}(t,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
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 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}}
|
|
1
|
+
import e from"./EventCoordinator.js";import{zsmPluginManager as t}from"./PluginManager.js";import{encodeUserIdentifier as r}from"./encodeUserIdentifier.js";const s=e=>{const t=new Error(`[UMFAClient] :: authenticate :: User ${e}'s enrollment was replaced by a newer one on another device, and has been removed from this device. Please enroll the user again.`);return t.code="ENROLLMENT_REPLACED",t};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),this.isPasskeySupported=this.isPasskeySupported.bind(this),this.passkeyNotSupportedReason=this.passkeyNotSupportedReason.bind(this),this.getPasskeySupportStatus=this.getPasskeySupportStatus.bind(this),this.getPasskeySupportStatus().then(e=>console.debug("[UMFAClient] :: Passkey capability:",JSON.stringify(e,null,2))).catch(()=>{}),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,n=""){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!=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",...n.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 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),s="preferred"===t;let n,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".`);n=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?n=await this.passkeyPlusEnroll(i,t):i.zsmCredentialID&&!i.hasLocalPasskey?n=await this.addPasskeyToZSMCredential(i,t):!i.zsmCredentialID&&i.pkpCredential&&(n=await this.addZSMToPasskeyCredential(i,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.`)}if(i?.passkeySupported)return void 0===n?new Error(`[UMFAClient] :: enroll :: 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(`[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 s.name=r?.name||"Error",s}}}if(a=i?.zsmCredentialID?i?.zsmCredentialID:await this.checkEnrollment(e),"string"==typeof a){if(i||(i=await this.checkAllEnrollments(e)),i instanceof Error)return i;if(i.zsmCredentialID&&!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}try{await this.zsmAPI.checkIdentity(e,!0)}catch(t){return new Error(`[UMFAClient] :: enroll :: Unable to establish an identity for ${e}!\nDetails:\n${t?.message||t}`,{cause:t})}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 i,a,l;if(r){if(a=await this.checkAllEnrollments(e),a instanceof Error)return a;if(await this.clearUnvalidatedEnrollment(e,a))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(!a.zsmCredentialID)return a.replaced?s(e):new Error(`[UMFAClient] :: authenticate :: User ${e} must be enrolled with a ZSM credential before authenticating. Please enroll the user first.`);if(a.passkeySupported)if(a.hasLocalPasskey){if(r){try{i=await this.passkeyPlusAuthenticate(a,t)}catch(r){if("ENROLLMENT_REPLACED"===r?.code)return r;if(!n){const s=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 s.name=r?.name||"Error",s}i=r instanceof Error?r:new Error(String(r),{cause:r})}if(void 0===i&&(i=new Error(`[UMFAClient] :: authenticate :: Unexpected PKP flow: PKP authentication attempted but no result was returned for user ${e}.`)),!(i instanceof Error&&n&&"ENROLLMENT_REPLACED"!==i.code))return i;console.warn(`[UMFAClient] :: authenticate :: The passkey portion of the Passkeys+ authentication failed for user ${e} with error: ${i.message||i}. They are authenticated with their ZSM credential alone.`)}}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.`)}}l=a?.zsmCredentialID?a?.zsmCredentialID:await this.checkEnrollment(e);if(!1===("string"==typeof l))return new Error(`[UMFAClient] :: authenticate :: ${e} is not enrolled. To authenticate the user, please enroll them first by calling the enroll method.`);let o;try{o=await this.zsmAPI.webauthnPartialGet(e)}catch(e){if("ENROLLMENT_REPLACED"===e?.code)return e;throw e}return o?.credential??o}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;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?.credentialActive&&!!t?.zsmCredentialID&&"suspended"!==t?.enrollmentState))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 isPasskeySupported(){return this.zsmAPI.passkeysPlus.pkpSupported()}async passkeyNotSupportedReason(){return this.zsmAPI.passkeysPlus.passkeyNotSupportedReason()}async getPasskeySupportStatus(){return this.zsmAPI.passkeysPlus.getPasskeySupportStatus()}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}}
|