UNPKG

apacuana-sdk-web

Version:

Apacuana SDK for Web

486 lines 21.1 kB
import ApacuanaCore from "apacuana-sdk-core"; import { ApacuanaWebError, ApacuanaWebErrorCode } from "./lib/errors.js"; import { ApacuanaSuccess } from "./lib/responses.js"; import { checkRecordExists, decryptData, encryptAndStoreValue, encryptData, getRecord, retrieveAndDecryptValue, signDigest, storeValue, } from "./lib/indexedDB.js"; import { createObjectStore, createPKCS12, encryptCSR, exportPrivateKey, generateCSR, generateKeyPair, transformCSR, } from "./lib/index.js"; import "./components/liveness-amplify.js"; // Asegúrate de que esta importación esté presente export class ApacuanaWeb { constructor() { this.isInitialized = false; } // ================================================================= // // PUBLIC METHODS // // ================================================================= async init(config) { try { const init = await ApacuanaCore.init(config); this.isInitialized = true; return init; } catch (error) { if (error instanceof Error && error.name === "ApacuanaAPIError") { throw error; } throw new ApacuanaWebError(`Error initializing Apacuana SDK for Web: ${error instanceof Error ? error.message : String(error)}`); } } async getCustomer() { this.ensureInitialized(); try { return await ApacuanaCore.getCustomer(); } catch (error) { throw error; } } async getRevocationReasons() { this.ensureInitialized(); try { return await ApacuanaCore.getRevocationReasons(); } catch (error) { throw error; } } async getCertStatus() { this.ensureInitialized(); try { const { customerId } = ApacuanaCore.getConfig(); const isCertificateInDevice = await checkRecordExists(customerId); return ApacuanaCore.getCertStatus(isCertificateInDevice); } catch (error) { if (error) { throw error; } throw new ApacuanaWebError("UNKNOWN_ERROR", ApacuanaWebErrorCode.UNKNOWN_ERROR); } } async generateCert(pin) { this.ensureInitialized(); if (!pin || typeof pin !== "string" || pin.trim() === "") { throw new ApacuanaWebError("PIN is required and must be a non-empty string.", ApacuanaWebErrorCode.VALIDATION_ERROR); } try { const config = ApacuanaCore.getConfig(); const userData = config.userData; const keyPair = await generateKeyPair(); if (userData && keyPair) { const { csr } = await generateCSR(keyPair, userData.email); const csrBase64 = transformCSR(csr); const csrEncrypted = encryptCSR(csrBase64); const result = await ApacuanaCore.generateCert(csrEncrypted); await encryptAndStoreValue("c", result === null || result === void 0 ? void 0 : result.data.cert, userData.id, pin); await storeValue("serial", result === null || result === void 0 ? void 0 : result.data.certifiedid, userData.id); const exportedPrivateKey = await exportPrivateKey(keyPair.privateKey); if (exportedPrivateKey) { await encryptAndStoreValue("pk", exportedPrivateKey, userData.id, pin); } return result; } } catch (error) { if (error) { throw error; } throw new ApacuanaWebError("UNKNOWN_ERROR", ApacuanaWebErrorCode.UNKNOWN_ERROR); } } async getCertTypes() { this.ensureInitialized(); try { return await ApacuanaCore.getCertTypes(); } catch (error) { throw error; } } async getCertRequerimentsByType(params) { this.ensureInitialized(); try { return await ApacuanaCore.getRequerimentsByTypeUser(params); } catch (error) { throw error; } } async uploadSignatureVariant(params) { try { this.ensureInitialized(); const result = await ApacuanaCore.uploadSignatureVariant(params); return result; } catch (error) { if (error) { throw error; } throw new ApacuanaWebError("UNKNOWN_ERROR", ApacuanaWebErrorCode.UNKNOWN_ERROR); } } async getSignatureVariant() { try { this.ensureInitialized(); const result = await ApacuanaCore.getSignatureVariant(); return result; } catch (error) { if (error) { throw error; } throw new ApacuanaWebError("UNKNOWN_ERROR", ApacuanaWebErrorCode.UNKNOWN_ERROR); } } async deleteSignatureVariant() { try { this.ensureInitialized(); const result = await ApacuanaCore.deleteSignatureVariant(); return result; } catch (error) { if (error) { throw error; } throw new ApacuanaWebError("UNKNOWN_ERROR", ApacuanaWebErrorCode.UNKNOWN_ERROR); } } async getDocsByCustomer(params) { this.ensureInitialized(); try { return await ApacuanaCore.getDocs(params); } catch (error) { throw error; } } async addSigner(params) { this.ensureInitialized(); try { return await ApacuanaCore.addSigner(params); } catch (error) { throw error; } } async requestRevocation(params) { this.ensureInitialized(); try { return await ApacuanaCore.requestRevocation(params); } catch (error) { throw error; } } async createApacuanaUser(params) { this.ensureInitialized(); try { return await ApacuanaCore.createApacuanaUser(params); } catch (error) { throw error; } } async isCertificateInDevice() { const { customerId } = this.getConfig(); const isExist = await checkRecordExists(customerId); return new ApacuanaSuccess({ isExist: isExist ? true : false, }); } async close() { return ApacuanaCore.close(); } async signDocument(params) { var _a; this.ensureInitialized(); const { customerId } = this.getConfig(); try { const cert = await retrieveAndDecryptValue("c", customerId, params.pin); if (cert) { const digest = await ApacuanaCore.getDigest({ cert: cert, signatureId: params.signature.id, document: params.document ? params.document : undefined, }); const privateKey = await retrieveAndDecryptValue("pk", customerId, params.pin); if (!privateKey) { throw new ApacuanaWebError("Private key not found or could not be decrypted.", ApacuanaWebErrorCode.NOT_FOUND); } if (!((_a = digest === null || digest === void 0 ? void 0 : digest.data) === null || _a === void 0 ? void 0 : _a.digest)) { throw new ApacuanaWebError("Failed to get digest for signing.", ApacuanaWebErrorCode.UNKNOWN_ERROR); } const signedDigest = await signDigest(digest.data.digest, privateKey); const sign = await ApacuanaCore.signDocument({ cert: cert, signature: params.signature, signedDigest, document: params.document ? params.document : undefined, }); return sign; } } catch (error) { throw error; } } async exportCertificateInJSON(params) { this.ensureInitialized(); const { customerId } = this.getConfig(); try { if (!params.pin || !params.filename) { throw new ApacuanaWebError("PIN and filename are required to generate the integrity seal.", ApacuanaWebErrorCode.VALIDATION_ERROR); } const pkRecord = await retrieveAndDecryptValue("pk", customerId, params.pin); const certRecord = await getRecord(customerId); if (!certRecord) { throw new ApacuanaWebError("Certificate record not found in device.", ApacuanaWebErrorCode.NOT_FOUND); } const dataForIntegrityCheck = { id: customerId, c: certRecord.c, // Agrega el certificado pk: pkRecord, // Agrega la clave privada descifrada }; const dataString = JSON.stringify(dataForIntegrityCheck); const { key: encryptedData, salt, iv, } = await encryptData(dataString, params.pin); const uint8ArrayToObject = (arr) => Object.fromEntries(arr.entries()); certRecord.serialize = { data: uint8ArrayToObject(encryptedData), salt: uint8ArrayToObject(salt), iv: uint8ArrayToObject(iv), }; const jsonData = JSON.stringify(certRecord, null, 2); const blob = new Blob([jsonData], { type: "application/json" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; const lastDotIndex = params.filename.lastIndexOf("."); const baseFilename = lastDotIndex > 0 ? params.filename.substring(0, lastDotIndex) : params.filename; a.download = `${baseFilename}.apacuana`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); return new ApacuanaSuccess({ isExport: true, }); } catch (error) { throw error; } } async startLivenessCheck(container) { return new Promise(async (resolve, reject) => { let targetContainer = container; const isModal = !targetContainer; try { // 1. Si no se proporciona un contenedor, creamos uno para el modo modal. if (isModal) { targetContainer = document.createElement("div"); Object.assign(targetContainer.style, { position: "fixed", top: "0", left: "0", width: "100vw", height: "100vh", backgroundColor: "rgba(0, 0, 0, 0.7)", display: "flex", justifyContent: "center", alignItems: "center", zIndex: "9999", }); document.body.appendChild(targetContainer); } else if (!(targetContainer instanceof HTMLElement)) { // Si se proporciona un contenedor pero no es válido, rechazamos. const error = new ApacuanaWebError("El contenedor proporcionado no es un elemento HTML válido.", ApacuanaWebErrorCode.VALIDATION_ERROR); return reject(error); } const faceLiveness = await ApacuanaCore.createFaceLivenessSession(); const { sessionId } = faceLiveness.data; const livenessElement = document.createElement("apacuana-liveness-amplify"); // CORRECCIÓN: Usar 'session-id' (kebab-case) para que coincida // con la definición del @property en el componente. livenessElement.setAttribute("session-id", sessionId); const cleanup = () => { livenessElement.removeEventListener("liveness-complete", onComplete); livenessElement.removeEventListener("liveness-error", onError); // 2. Si creamos el contenedor modal, lo eliminamos del body. if (isModal && targetContainer) { document.body.removeChild(targetContainer); } else if (targetContainer === null || targetContainer === void 0 ? void 0 : targetContainer.contains(livenessElement)) { // Si no, solo limpiamos el contenido del contenedor del cliente. targetContainer.removeChild(livenessElement); } }; const onComplete = () => { console.log("Liveness check complete: Success"); const validateFaceLiveness = this.handleLivenessSuccess(sessionId); cleanup(); resolve(new ApacuanaSuccess(validateFaceLiveness)); }; const onError = (event) => { const customEvent = event; const amplifyError = customEvent.detail; console.error("Liveness check failed:", amplifyError); cleanup(); const error = new ApacuanaWebError((amplifyError === null || amplifyError === void 0 ? void 0 : amplifyError.error) || "Error en la prueba de vida.", ApacuanaWebErrorCode.UNKNOWN_ERROR); reject(error); }; livenessElement.addEventListener("liveness-complete", onComplete); livenessElement.addEventListener("liveness-error", onError); // 3. Añadimos el componente al contenedor (el nuestro o el del cliente). targetContainer.appendChild(livenessElement); } catch (error) { // Si algo falla, nos aseguramos de limpiar el contenedor modal si lo creamos. if (isModal && targetContainer && document.body.contains(targetContainer)) { document.body.removeChild(targetContainer); } const unknownError = new ApacuanaWebError("Ocurrió un error inesperado durante la verificación.", ApacuanaWebErrorCode.UNKNOWN_ERROR); console.error("Error en startLivenessCheck:", error); reject(unknownError); } }); } async importCertificate(params) { this.ensureInitialized(); const { file, pin } = params; // --- Validación de la extensión .apacuana --- const fileName = file.name; const fileExtension = fileName .substring(fileName.lastIndexOf(".") + 1) .toLowerCase(); if (fileExtension !== "apacuana") { throw new ApacuanaWebError("Por favor, selecciona un archivo con extensión .apacuana.", ApacuanaWebErrorCode.VALIDATION_ERROR); } const fileContent = await this.readFileAsText(file); let jsonContent; try { jsonContent = JSON.parse(fileContent); } catch (error) { console.log(error); throw new ApacuanaWebError("Error al procesar el archivo de certificado. Asegúrate de que sea un archivo válido.", ApacuanaWebErrorCode.PARSE_ERROR); } // --- Validación de la estructura del JSON --- const requiredKeys = ["c", "pk", "id", "serialize"]; const requiredEncryptedKeys = ["iv", "key", "salt"]; const requiredSerializeKeys = ["data", "salt", "iv"]; const isValid = requiredKeys.every((key) => key in jsonContent) && requiredEncryptedKeys.every((key) => key in jsonContent.c) && requiredEncryptedKeys.every((key) => key in jsonContent.pk) && requiredSerializeKeys.every((key) => key in jsonContent.serialize); if (!isValid) { throw new ApacuanaWebError("La estructura del archivo JSON no es válida.", ApacuanaWebErrorCode.VALIDATION_ERROR); } // --- Verificación de integridad --- const toUint8Array = (obj) => new Uint8Array(Object.values(obj)); const encryptedPayload = { key: toUint8Array(jsonContent.serialize.data), salt: toUint8Array(jsonContent.serialize.salt), iv: toUint8Array(jsonContent.serialize.iv), }; let decryptedDataBuffer; try { decryptedDataBuffer = await decryptData(encryptedPayload, pin); } catch (error) { console.log(error); throw new ApacuanaWebError("Credenciales incorrectas.", ApacuanaWebErrorCode.VALIDATION_ERROR); } const decryptedDataString = new TextDecoder().decode(decryptedDataBuffer); // --- CORRECCIÓN: Reconstruye el objeto de verificación con los datos desencriptados const decryptedDataObject = JSON.parse(decryptedDataString); const dataForIntegrityCheck = { id: decryptedDataObject.id, c: decryptedDataObject.c, pk: decryptedDataObject.pk, }; const canonicalString = JSON.stringify(dataForIntegrityCheck); if (decryptedDataString !== canonicalString) { throw new ApacuanaWebError("Error de verificación de integridad. El archivo ha sido alterado o la contraseña es incorrecta.", ApacuanaWebErrorCode.VALIDATION_ERROR); } const customerId = jsonContent.id; const config = this.getConfig(); if (config.customerId !== customerId) { throw new ApacuanaWebError("Error de verificación de usuario. El certificado a importar no pertenece al usuario.", ApacuanaWebErrorCode.VALIDATION_ERROR); } // --- Nuevo flujo: No se comprueba si el registro existe. Se sobreescribe directamente. --- jsonContent.c = { iv: toUint8Array(jsonContent.c.iv), key: toUint8Array(jsonContent.c.key), salt: toUint8Array(jsonContent.c.salt), }; jsonContent.pk = { iv: toUint8Array(jsonContent.pk.iv), key: toUint8Array(jsonContent.pk.key), salt: toUint8Array(jsonContent.pk.salt), }; createObjectStore(jsonContent); new ApacuanaSuccess({ isImport: true, }); } // ================================================================= // // PRIVATE METHODS // // ================================================================= handleLivenessSuccess(sessionId) { return ApacuanaCore.validateFaceLiveness({ sessionId }); } getConfig() { this.ensureInitialized(); return ApacuanaCore.getConfig(); } ensureInitialized() { if (!this.isInitialized) { throw new ApacuanaWebError("ApacuanaWeb SDK has not been initialized. Please call init() first.", ApacuanaWebErrorCode.NOT_INITIALIZED); } } async exportCertificateInP12(params) { this.ensureInitialized(); const { customerId } = this.getConfig(); try { const privateKey = await retrieveAndDecryptValue("pk", customerId, params.pin); const cert = await retrieveAndDecryptValue("c", customerId, params.pin); let formattedPrivateKey = privateKey; if (formattedPrivateKey && cert) { if (!formattedPrivateKey.includes("-----BEGIN PRIVATE KEY-----")) { formattedPrivateKey = `-----BEGIN PRIVATE KEY-----\n${formattedPrivateKey}\n-----END PRIVATE KEY-----`; } const p12Base64 = await createPKCS12(formattedPrivateKey, cert, params.pin); const element = document.createElement("a"); element.setAttribute("href", "data:application/x-pkcs12;base64," + p12Base64); element.setAttribute("download", params.filename); element.style.display = "none"; document.body.appendChild(element); element.click(); document.body.removeChild(element); return true; } else { throw new ApacuanaWebError("Error in privateKey || cert", ApacuanaWebErrorCode.UNKNOWN_ERROR); } } catch (e) { throw e; } } readFileAsText(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result); reader.onerror = () => reject(reader.error); reader.readAsText(file); }); } } export const apacuanaWeb = new ApacuanaWeb(); //# sourceMappingURL=index.js.map