@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!
105 lines (93 loc) • 7.47 kB
JavaScript
import eventCoordinator from '@ideem/zsm-client-sdk/EventCoordinator.js';
import FIDO2ClientBase from '@ideem/zsm-client-sdk/FIDO2ClientBase.js';
import { iid4uid } from '@ideem/zsm-client-sdk/IdentityIndexing.js';
class FIDO2Client extends FIDO2ClientBase {
/**
* Constructs a FIDO2Client object by extending the FIDO2ClientBase class.
* @param {Object} config - The configuration for ZSM initialization (ref: ./zsm_app_config.json)
*/
constructor(config) {
super(config);
eventCoordinator.update('FIDO2Client', 'PENDING');
this.webauthnPasskeyCreate = this.webauthnPasskeyCreate.bind(this);
this.webauthnPasskeyGet = this.webauthnPasskeyGet.bind(this);
if(!(this.config?.use_passkeys??undefined) && window.location.hostname === 'localhost') console.warn('[PK+ FIDO2Client] :: use_passkeys is not enabled, but you are running on localhost. This may lead to unexpected behavior when directing requests to production environments.');
eventCoordinator.update('FIDO2Client', 'READY');
}
/**
* @name webauthnCreate
* @description Creates a ZSM credential for a user on the device, or acts as a proxy for Passkeys+ enrollment when usePasskeys flag is set
* @param {string} userIdentifier The identifier for the user
* @param {boolean} usePasskeys Whether to use Passkeys+ enrollment method instead of base class method
* @param {boolean} pkpOnly Whether to only use Passkeys+ enrollment method
* @returns {Promise<Object>} The results of the call to FIDO2ClientBase's create or the webauthnPasskeyCreate methods
* @overrides {function} webauthnCreate (ref: FIDO2ClientBase)
* @memberOf FIDO2Client, PasskeysPlus
*/
webauthnCreate (userIdentifier, usePasskeys=false, pkpOnly=false) {
return usePasskeys ? this.webauthnPasskeyCreate(userIdentifier, pkpOnly) : super.webauthnCreate(userIdentifier);
}
/**
* @name webauthnPasskeyCreate
* @description Creates a Passkeys+ credential for the user, then creates a ZSM credential
* @param {string} userIdentifier The identifier for the user
* @param {boolean} pkpOnly Whether to only use Passkeys+ enrollment method
* @returns {Promise<Object>} The results of the call to WebAuthnClient's pkpCreate method or an Error
* @throws {Error} If the userIdentifier is not provided or is empty
* @throws {Error} If an error occurs during Passkeys+ creation
* @extends FIDO2ClientBase Adds this function to base class
* @memberOf FIDO2Client, PasskeysPlus
*/
async webauthnPasskeyCreate (userIdentifier, pkpOnly=false) {
try {
if(userIdentifier == null
|| userIdentifier === '') throw new Error(`[PK+ FIDO2Client] :: webauthnPasskeyCreate :: A userIdentifier String is required! Received: ${userIdentifier}.`);
const pkpCreateResult = await this.zsmAPI.pkpCreate(userIdentifier, pkpOnly);
if (pkpCreateResult instanceof Error) throw new Error(`[PK+ FIDO2Client] :: webauthnPasskeyCreate :: Error occurred during Passkeys+ creation! \nDetails:\n${pkpCreateResult.message}`);
return pkpCreateResult;
} catch (e) {
return new Error(e.message || e || '[PK+ FIDO2Client] :: webauthnPasskeyCreate :: An error occurred during Passkeys+ creation.');
}
}
/**
* @name webauthnGet
* @description Authenticates a ZSM credential for a user from the device, or acts as a proxy for Passkeys+ authentication when usePasskeys flag is set
* @param {string} userIdentifier The identifier for the user
* @param {boolean} usePasskeys Whether to use Passkeys+ authentication method instead of base class method
* @returns {Promise<Object>} The results of the call to FIDO2ClientBase's get or the webauthnPasskeyGet methods
* @overrides {function} webauthnGet (ref: FIDO2ClientBase)
* @memberOf FIDO2Client, PasskeysPlus
*/
webauthnGet (userIdentifier, usePasskeys=false) {
return usePasskeys ? this.webauthnPasskeyGet(userIdentifier) : super.webauthnGet(userIdentifier);
}
/**
* @name webauthnPasskeyGet
* @description Authenticates a ZSM credential on the device for the specified user
* @param {string} userIdentifier The identifier for the user
* @returns {Promise<Object>} The results of the call to WebAuthnClient's pkpAuthenticate method or an Error
* @throws {Error} If the userIdentifier is not provided or is empty
* @throws {Error} If an error occurs during Passkeys+ authentication
* @extends FIDO2ClientBase Adds this function to base class
* @memberOf FIDO2Client, PasskeysPlus
*/
async webauthnPasskeyGet (userIdentifier) {
try {
if(userIdentifier == null
|| userIdentifier === '') throw new Error(`[PK+ FIDO2Client] :: webauthnPasskeyGet :: A userIdentifier String is required! Received: ${userIdentifier}.`);
const storedZSMCred = await iid4uid(userIdentifier);
const storedPKPCred = await iid4uid(`pk:${userIdentifier}`);
const hasZSMCred = (storedZSMCred !== userIdentifier);
const hasPKPCred = (storedPKPCred !== `pk:${userIdentifier}`);
if(!hasPKPCred && !hasZSMCred) throw new Error(`[PK+ FIDO2Client] :: webauthnPasskeyGet :: No ZSM credentials found for userIdentifier: ${userIdentifier}. Please enroll the user first.`);
if(!hasPKPCred && hasZSMCred) return new Error(`[PK+ FIDO2Client] :: webauthnPasskeyGet :: ZSM credentials WERE found for userIdentifier: ${userIdentifier}, but no Passkeys+ credential found on this device. webauthnCreate the user using Passkeys+ in addition to their ZSM credentials.`, {cause: 'PKP_UNREGISTERED'});
if(hasPKPCred === `pk:${userIdentifier}`) throw new Error(`[PK+ FIDO2Client] :: webauthnPasskeyGet :: No Passkeys+ credentials found for userIdentifier: ${userIdentifier}.`);
const pkpAuthResult = await this.zsmAPI.pkpAuthenticate(userIdentifier);
if (pkpAuthResult instanceof Error) throw new Error(`[PK+ FIDO2Client] :: webauthnPasskeyGet :: Error occurred during Passkeys+ authentication! \nDetails:\n${pkpAuthResult.message}`);
return pkpAuthResult;
} catch (e) {
return new Error(e.message || e || '[PK+ FIDO2Client] :: webauthnPasskeyGet :: An error occurred during Passkeys+ authentication.');
}
}
}
export {FIDO2Client};