@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!
95 lines (85 loc) • 9.82 kB
JavaScript
import eventCoordinator from '@ideem/zsm-client-sdk/EventCoordinator.js';
class PasskeysPlusClient {
constructor() {
eventCoordinator.update('PasskeysPlus');
this.passkeyAPI = (navigator?.credentials??null) || null;
eventCoordinator.update('PasskeysPlus', 'READY');
}
/**
* @name pkpSupported
* @description Checks if the PasskeysPlus API is supported in the current browser.
* @returns {boolean} True if supported, false otherwise.
*/
pkpSupported = () => !!this.passkeyAPI && typeof this.passkeyAPI.create === 'function' && typeof this.passkeyAPI.get === 'function';
/**
* @name blockUnsupportedInvocation
* @description Throws an error if the PasskeysPlus API is not supported in the current browser.
* @returns {boolean} True if the invocation is supported, false otherwise.
* @throws {Error} If the PasskeysPlus API is not supported.
*/
blockUnsupportedInvocation = () => {
if(!this.pkpSupported()) throw new Error(`[PK+ PasskeysPlus] :: blockUnsupportedInvocation :: This browser and/or device does not support the PasskeysPlus API!`);
return true;
}
/**
* @name pkpCreatePasskeyCredential
* @description Creates a passkey credential based on the provided public key.
* @param {Object} publicKey The public key options for the passkey credential.
* @returns {Promise<Credential>} Resolves with the created passkey credential.
* @throws {Error} The publicKey parameter was not provided or it was not an object.
* @throws {Error} The structure of the publicKey parameter is invalid.
* @throws {Error} The publicKey parameter's user ID was not an ArrayBuffer.
* @throws {Error} The publicKey parameter's pubKeyCredParams was not an Array, was empty, or contained invalid entries.
* @throws {Error} The publicKey parameter's authenticatorSelection was not an object (if provided).
* @throws {Error} The publicKey parameter's authenticatorSelection.authenticatorAttachment was missing or not a valid value.
* @throws {Error} The publicKey parameter's authenticatorSelection.requireResidentKey was missing or not a boolean.
* @throws {Error} The publicKey parameter's authenticatorSelection.userVerification was missing or not a valid value.
* @throws {Error} The publicKey parameter's timeout (if provided) was missing or not a positive number.
* @throws {Error} The publicKey parameter's excludeCredentials must be an array and cannot be empty (if provided).
* @throws {Error} The publicKey parameter's excludeCredentials (if provided) contained an entry that was not an ArrayBuffer.
* @memberof PasskeysPlus
*/
pkpCreatePasskeyCredential = async (publicKey) => {
if(!this.blockUnsupportedInvocation()) return false;
if(!publicKey || typeof publicKey !== 'object') throw new Error(`[PK+ PasskeysPlus] :: pkpCreatePasskeyCredential :: Invalid publicKey parameter provided: ${publicKey}! It must be an object. Received: ${publicKey}`);
if(!publicKey?.challenge || !publicKey?.rp || !publicKey?.rp?.id || !publicKey?.rp?.name) throw new Error(`[PK+ PasskeysPlus] :: pkpCreatePasskeyCredential :: Invalid publicKey structure! It must contain challenge and rp properties with id and name. Received: ${publicKey}`);
if(!publicKey?.user || !publicKey?.user?.id || !(publicKey?.user?.id instanceof ArrayBuffer)) throw new Error(`[PK+ PasskeysPlus] :: pkpCreatePasskeyCredential :: Invalid user ID in publicKey! It must be an ArrayBuffer. Received: ${publicKey?.user?.id}`);
if(!publicKey?.pubKeyCredParams || !Array.isArray(publicKey?.pubKeyCredParams)
|| publicKey?.pubKeyCredParams?.length === 0) throw new Error(`[PK+ PasskeysPlus] :: pkpCreatePasskeyCredential :: pubKeyCredParams must be a non-empty array of public key credential parameters. Received: ${publicKey?.pubKeyCredParams}`);
if(!publicKey?.authenticatorSelection || typeof publicKey?.authenticatorSelection !== 'object') throw new Error(`[PK+ PasskeysPlus] :: pkpCreatePasskeyCredential :: Invalid authenticatorSelection in publicKey! It must be an object. Received: ${publicKey?.authenticatorSelection} (${typeof publicKey?.authenticatorSelection})`);
if(publicKey?.authenticatorSelection?.authenticatorAttachment
&& !['platform', 'cross-platform'].includes(publicKey?.authenticatorSelection?.authenticatorAttachment)) throw new Error(`[PK+ PasskeysPlus] :: pkpCreatePasskeyCredential :: Invalid authenticatorAttachment in authenticatorSelection! It must be either "platform" or "cross-platform". Received: ${publicKey?.authenticatorSelection?.authenticatorAttachment}`);
if(publicKey?.authenticatorSelection?.requireResidentKey
&& typeof publicKey?.authenticatorSelection?.requireResidentKey !== 'boolean') throw new Error(`[PK+ PasskeysPlus] :: pkpCreatePasskeyCredential :: Invalid requireResidentKey in authenticatorSelection! It must be a boolean. Received: ${publicKey?.authenticatorSelection?.requireResidentKey} (${typeof publicKey?.authenticatorSelection?.requireResidentKey})`);
if(publicKey?.authenticatorSelection?.userVerification
&& !['required', 'preferred', 'discouraged'].includes(publicKey?.authenticatorSelection?.userVerification)) throw new Error(`[PK+ PasskeysPlus] :: pkpCreatePasskeyCredential :: Invalid userVerification in authenticatorSelection! It must be one of "required", "preferred", or "discouraged". Received: ${publicKey?.authenticatorSelection?.userVerification}`);
if(!publicKey?.timeout || typeof publicKey?.timeout !== 'number' || publicKey?.timeout <= 0) throw new Error(`[PK+ PasskeysPlus] :: pkpCreatePasskeyCredential :: Invalid timeout in publicKey! It must be a positive number. Received: ${publicKey?.timeout} (${typeof publicKey?.timeout})`);
if(publicKey?.excludeCredentials && (!Array.isArray(publicKey?.excludeCredentials)
|| publicKey?.excludeCredentials?.length > 0 && !publicKey?.excludeCredentials[0].id)) throw new Error(`[PK+ PasskeysPlus] :: pkpCreatePasskeyCredential :: Invalid credential ID in excludeCredentials! It must be an ArrayBuffer. Received: ${publicKey?.excludeCredentials[0]?.id}`);
return await this.passkeyAPI.create({ publicKey });
}
/**
* @name pkpGetPasskeyCredential
* @description Retrieves a passkey credential based on the provided public key.
* @param {Object} publicKey The public key options for the passkey credential.
* @returns {Promise<Credential>} Resolves with the passkey credential.
* @throws {Error} The publicKey parameter was not provided or it was not an object.
* @throws {Error} The structure of the publicKey parameter is invalid.
* @throws {Error} The publicKey parameter's allowCredentials was not an array or was empty.
* @throws {Error} The publicKey parameter's allowCredentials did not contain an ArrayBuffer.
* @throws {Error} The publicKey parameter's user ID was missing or was not an ArrayBuffer.
* @throws {Error} The publicKey parameter's rp was missing or was missing name and/or id.
* @memberof PasskeysPlus
*/
pkpGetPasskeyCredential = async (publicKey) => {
if(!this.blockUnsupportedInvocation()) return false;
if(!publicKey || typeof publicKey !== 'object') throw new Error(`[PK+ PasskeysPlus] :: pkpGetPasskeyCredential :: Invalid publicKey parameter provided!`);
if(!publicKey?.challenge || !publicKey?.allowCredentials
|| !Array.isArray(publicKey?.allowCredentials)) throw new Error(`[PK+ PasskeysPlus] :: pkpGetPasskeyCredential :: Invalid publicKey structure provided! It must contain challenge and allowCredentials properties. Received: ${publicKey}`);
if(publicKey?.allowCredentials?.length === 0) throw new Error(`[PK+ PasskeysPlus] :: pkpGetPasskeyCredential :: allowCredentials array must not be empty! It should contain at least one credential ID. Received: ${publicKey?.allowCredentials}`);
if(!publicKey?.allowCredentials[0].id
|| !(publicKey?.allowCredentials[0].id instanceof ArrayBuffer)) throw new Error(`[PK+ PasskeysPlus] :: pkpGetPasskeyCredential :: Invalid credential ID in allowCredentials! It must be an ArrayBuffer. Received: ${publicKey?.allowCredentials[0]?.id}`);
return await this.passkeyAPI.get({ publicKey });
}
}
export default PasskeysPlusClient;