apacuana-sdk-web
Version:
Apacuana SDK for Web
481 lines • 20.8 kB
JavaScript
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/apacuana-liveness-detector.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.cert, userData.id, pin);
await storeValue("serial", result === null || result === void 0 ? void 0 : result.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 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);
}
});
}
// =================================================================
//
// PRIVATE METHODS
//
// =================================================================
handleLivenessSuccess(sessionId) {
return ApacuanaCore.validateFaceLiveness({ sessionId });
}
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 dataForIntegrityCheck = {
id: jsonContent.id,
c: jsonContent.c,
pk: jsonContent.pk,
};
const canonicalString = JSON.stringify(dataForIntegrityCheck, Object.keys(dataForIntegrityCheck).sort());
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);
console.log({
jsonContent,
decryptedDataString,
canonicalString,
});
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,
});
}
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