@ideem/plugins.passkeys-plus
Version:
Extend your ZSM Client SDK with Passkeys Plus functionality, enabling advanced passkey management and integration, with optional, sync-proof device-binding and attestation, invisible second-factor authentication, and user identity verification!
103 lines (87 loc) • 7.08 kB
JavaScript
import eventCoordinator from '@ideem/zsm-client-sdk/EventCoordinator.js';
import PasskeysPlusClient from './PasskeysPlusClient.js';
import WASMRustInterface from '@ideem/zsm-client-sdk/WASMRustInterface.js';
import WebAuthnClientBase from '@ideem/zsm-client-sdk/WebAuthnClientBase.js';
import { uid2iid, iid4uid } from '@ideem/zsm-client-sdk/IdentityIndexing.js';
class WebAuthnClient extends WebAuthnClientBase {
/**
* Constructs a WebAuthnClient object by extending the WebAuthnClientBase class.
* @param {Object} config - The configuration for ZSM initialization.
*/
constructor(config) {
super(config);
eventCoordinator.update('WebAuthnClient');
this.passkeysPlus = new PasskeysPlusClient();
eventCoordinator.update('WebAuthnClient', 'READY');
}
/**
* @name getZsmApi
* @description Retrieves the ZSM API instance.
* @param {Object} config The configuration object for ZSM initialization.
* @returns {Promise<ZSMAPI>} Resolves with the initialized ZSM API.
* @overrides {function} getZsmApi (ref: WebAuthnClientBase)
* @memberOf WebAuthnClient
*/
getZsmApi = async (config) => WASMRustInterface(config);
/**
* @name pkpCreate
* @description Creates a Passkeys+ credential for the user, if the user is not already enrolled, then creates a ZSM credential.
* @param {string} userIdentifier The identifier for the user.
* @param {boolean} pkpOnly If true, only creates the Passkeys+ credential without creating a ZSM credential.
* @returns {Promise<Object>} Call to WebAuthnClientBase's webauthnCreate method
* @throws {Error} If the userIdentifier is not provided or is empty.
* @throws {Error} If no challenge is received from the Relying Party Server.
* @throws {Error} If the public key is not found in the Relying Party Challenge.
* @throws {Error} If the Platform Authenticator Credential is not created successfully.
* @throws {Error} If the Platform Authenticator response is invalid.
* @extends WebAuthnClientBase Adds this function to base class.
* @memberOf WebAuthnClient, PasskeysPlus
*/
pkpCreate = async (userIdentifier, pkpOnly=false) => {
if(userIdentifier == null
|| userIdentifier === '') return new Error(`[PK+ WebAuthnClient] :: pkpCreate :: A userIdentifier String is required! Received: ${userIdentifier}.`);
this.userIdentifier = userIdentifier;
const relyingPartyChallenge = await this.relyingParty.pkpRegistrationStart(userIdentifier);
if(!relyingPartyChallenge) return new Error(`[PK+ WebAuthnClient] :: pkpCreate :: No response from ZSM API's attempt to interface with Relying Party Server!`);
const paChallengePublicKey = relyingPartyChallenge.publicKey;
if(!paChallengePublicKey) return new Error(`[PK+ WebAuthnClient] :: pkpCreate :: No publicKey found in Relying Party Challenge!`);
const paCredential = await this.passkeysPlus.pkpCreatePasskeyCredential(paChallengePublicKey);
if (!paCredential) return new Error('[PK+ WebAuthnClient] :: pkpCreate :: Unable to call Platform Authenticator to create Passkey!');
const paRegistrationResult = await this.relyingParty.pkpRegistrationFinish(paCredential);
if (!paRegistrationResult) return new Error('[PK+ WebAuthnClient] :: pkpCreate :: Platform Authenticator response was invalid!');
uid2iid('pk:' + userIdentifier, paCredential.id);
return pkpOnly ? paCredential.id : this.webauthnCreate(userIdentifier);
}
/**
* @name pkpAuthenticate
* @description Authenticates a user using Passkeys+ credentials.
* @param {string} userIdentifier The identifier for the user.
* @returns {Promise<Object>} Call to WebAuthnClientBase's webauthnGet method
* @throws {Error} If the userIdentifier is not provided or is empty.
* @throws {Error} If no Platform Authenticator Credential ID is found for the user.
* @throws {Error} If the Platform Authority Challenge is not retrieved successfully.
* @throws {Error} If the Platform Authenticator Credential is not created successfully.
* @throws {Error} If the Platform Authenticator response is invalid.
* @extends WebAuthnClientBase Adds this function to base class.
* @memberOf WebAuthnClient, PasskeysPlus
*/
pkpAuthenticate = async (userIdentifier) => {
if(userIdentifier == null
|| userIdentifier === '') return new Error(`[PK+ WebAuthnClient] :: pkpCreate :: A userIdentifier String is required! Received: ${userIdentifier}.`);
this.userIdentifier = userIdentifier;
const storedCredID = await iid4uid('pk:' + userIdentifier);
if (!storedCredID) return new Error(`[PK+ WebAuthnClient] :: pkpAuthenticate :: No Platform Authenticator Credential ID found for ${userIdentifier}!`);
// Initialize Relying Party Challenge
const platformAuthorityChallenge = await this.relyingParty.pkpAuthenticationStart(storedCredID);
if(!platformAuthorityChallenge) return new Error(`[PK+ WebAuthnClient] :: pkpAuthenticate :: Unable to retrieve Platform Authority Challenge from Relying Party Server!`);
// Perform Platform Authenticator create()
const paCredential = await this.passkeysPlus.pkpGetPasskeyCredential(platformAuthorityChallenge);
if (!paCredential) return new Error('[PK+ WebAuthnClient] :: pkpAuthenticate :: Unable to call Platform Authenticator to create Passkey!');
// Finalize ZSM Credential
const paAuthenticationResult = await this.relyingParty.pkpAuthenticationFinish(paCredential);
if (!paAuthenticationResult) return new Error('[PK+ WebAuthnClient] :: pkpAuthenticate :: Platform Authenticator response was invalid!');
return this.webauthnGet(userIdentifier);
}
}
// Export the WebAuthnClient class
export default WebAuthnClient;