UNPKG

@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
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;