tuain-ecosystem-lib
Version:
Servicio de gestión mensajería instantanea de la plataforma Tuain
398 lines (372 loc) • 16.3 kB
JavaScript
const { ObjectId } = require('mongodb');
const { v4: uuidv4 } = require('uuid');
const forge = require('node-forge');
const {
dbQueries: { devices: deviceQueries },
collections: { devices: devicesColl, stores: storesColl },
} = require('../../config');
const deviceStates = Object.freeze({
DRAFT: 'draft',
ACTIVE: 'active',
REJECTED: 'rejected',
BLOCKED: 'blocked',
});
const modErrs = {
findStore: {
notFound: ['01', 'No se encontró sucursal que se ajuste a la búsqueda'],
},
findDevice: {
notFound: ['01', 'No se encontró dispositivo que se ajuste a la búsqueda'],
notBlocked: ['02', 'Dispositivo no está bloqueado'],
},
};
async function generateKeyPair() {
return new Promise((resolve, reject) => {
forge.pki.rsa.generateKeyPair(2048, (err, keypair) => {
if (err) {
return reject(new Error('Error generando llaves'));
}
return resolve(keypair);
});
});
}
class DeviceManager {
constructor(getDb, logger, errMgr, options) {
this.options = options;
this.getDb = getDb;
this.logger = logger;
this.errMgr = errMgr;
this.errMgr.addModuleSet('lib-devices', modErrs);
}
async getDevices(reqData) {
const { deviceIds, storeIds, state, deviceData } = reqData;
const deviceCol = this.getDb().collection(devicesColl);
const devices = await deviceCol
.find(deviceQueries.findDevices(deviceIds, storeIds, state, deviceData))
.sort({ _id: -1 })
.toArray();
return [null, devices];
}
async requestDeviceId(reqData) {
const { storeId } = reqData;
const deviceCol = this.getDb().collection(devicesColl);
const storeCol = this.getDb().collection(storesColl);
const storeDetail = await storeCol.findOne(deviceQueries.findById(storeId));
if (!storeDetail) {
const errorDetail = `Tercero con Id ${storeId} no encontrado`;
const errorObj = this.errMgr.get(modErrs.findStore.notFound, errorDetail);
return [errorObj, null];
}
const keypair = await generateKeyPair();
const keyPairPem = {
publicKey: forge.pki.publicKeyToPem(keypair.publicKey),
privateKey: forge.pki.privateKeyToPem(keypair.privateKey),
};
const newDeviceInfo = {
...reqData,
storeId: ObjectId(storeId),
...keyPairPem,
state: deviceStates.DRAFT,
};
const insert = await deviceCol.insertOne(newDeviceInfo);
const deviceId = insert?.insertedId;
return [null, { deviceId, publicKey: keyPairPem.publicKey }];
}
async modifyDevice(reqData) {
const { deviceId, storeId, name, lastActivityDate, expireAt, additionalData = {} } = reqData;
const deviceCol = this.getDb().collection(devicesColl);
const deviceDetail = await deviceCol.findOne(deviceQueries.findById(deviceId));
if (!deviceDetail) {
const errorDetail = `Dispositivo con Id ${deviceId} no encontrado`;
const errorObj = this.errMgr.get(modErrs.findDevice.notFound, errorDetail);
return [errorObj, null];
}
const objectToUpdate = { additionalData };
if (storeId) {
objectToUpdate.storeId = ObjectId(storeId);
}
if (name) {
objectToUpdate.name = name;
}
if (lastActivityDate) {
objectToUpdate.lastActivityDate = lastActivityDate;
}
if (expireAt) {
objectToUpdate.expireAt = expireAt;
}
const actionResult = await deviceCol.updateOne(deviceQueries.findById(deviceId), deviceQueries.updateObj(objectToUpdate));
const result = actionResult?.result?.nModified > 0;
return [null, { result }];
}
async deleteDevice(reqData) {
const { deviceId } = reqData;
const deviceCol = this.getDb().collection(devicesColl);
const deviceDetail = await deviceCol.findOne(deviceQueries.findById(deviceId));
if (!deviceDetail) {
const errorDetail = `Dispositivo con Id ${deviceId} no encontrado`;
const errorObj = this.errMgr.get(modErrs.findDevice.notFound, errorDetail);
return [errorObj, null];
}
const actionResult = await deviceCol.deleteOne(deviceQueries.findById(deviceId));
const result = actionResult?.result?.n > 0;
return [null, { result }];
}
async checkDeviceSignature(reqData) {
const { deviceId, deviceChallenge, signedChallenge } = reqData;
const [devErr, devRes] = await this.getDevice({ deviceId });
const deviceKey = !devErr && devRes ? forge.pki.publicKeyFromPem(devRes?.deviceKey) : null;
if (!deviceKey) {
const errorDetail = `Dispositivo con Id ${deviceId} no encontrado o sin llave`;
const errorObj = this.errMgr.get(modErrs.findDevice.notFound, errorDetail);
return [errorObj, null];
}
const sign = forge.util.decode64(signedChallenge);
const md = forge.md.sha256.create();
md.update(deviceChallenge);
const data = md.digest().bytes();
return [null, { validation: deviceKey.verify(data, sign) }];
}
async deviceSign(reqData) {
const { deviceId, dataToSign } = reqData;
const [devErr, devRes] = await this.getDevice({ deviceId });
const privateKey = !devErr && devRes ? forge.pki.publicKeyFromPem(devRes?.privateKey) : null;
if (!privateKey) {
const errorDetail = `Dispositivo con Id ${deviceId} no encontrado o sin llave`;
const errorObj = this.errMgr.get(modErrs.findDevice.notFound, errorDetail);
return [errorObj, null];
}
const md = forge.md.sha256.create();
md.update(dataToSign);
const sign = privateKey.sign(md);
const signedData = forge.util.encode64(sign);
return [null, { signedData }];
}
async deviceEncrypt(reqData) {
const { deviceId, dataToEncrypt } = reqData;
const [devErr, devRes] = await this.getDevice({ deviceId });
const deviceKey = !devErr && devRes ? forge.pki.publicKeyFromPem(devRes?.deviceKey) : null;
if (!deviceKey) {
const errorDetail = `Dispositivo con Id ${deviceId} no encontrado o sin llave`;
const errorObj = this.errMgr.get(modErrs.findDevice.notFound, errorDetail);
return [errorObj, null];
}
const encryptedDataRaw = deviceKey.encrypt(dataToEncrypt);
const encryptedData = forge.util.encode64(encryptedDataRaw);
return [null, encryptedData];
}
async deviceDecrypt(reqData) {
const { deviceId, dataToDecrypt } = reqData;
const [devErr, devRes] = await this.getDevice({ deviceId });
const privateKey = !devErr && devRes ? forge.pki.publicKeyFromPem(devRes?.privateKey) : null;
if (!privateKey) {
const errorDetail = `Dispositivo con Id ${deviceId} no encontrado o sin llave`;
const errorObj = this.errMgr.get(modErrs.findDevice.notFound, errorDetail);
return [errorObj, null];
}
const dataToDecryptRaw = forge.util.decode64(dataToDecrypt);
const decryptedData = privateKey.decrypt(dataToDecryptRaw);
return [null, decryptedData];
}
async registerDeviceKey(reqData) {
const { storeId, deviceId, additionalData, deviceKey } = reqData;
const deviceCol = this.getDb().collection(devicesColl);
const storeCol = this.getDb().collection(storesColl);
const storeDetail = await storeCol.findOne(deviceQueries.findById(storeId));
if (!storeDetail) {
const errorDetail = `Establecimiento ${storeId} no encontrado`;
const errorObj = this.errMgr.get(modErrs.findStore.notFound, errorDetail);
return [errorObj, null];
}
const deviceDetail = await deviceCol.findOne(deviceQueries.findById(deviceId));
if (!deviceDetail) {
const errorDetail = `Dispositivo ${deviceId} no encontrado`;
const errorObj = this.errMgr.get(modErrs.findDevice.notFound, errorDetail);
return [errorObj, null];
}
const actionResult = await deviceCol.updateOne(
deviceQueries.findById(deviceId),
deviceQueries.updateObj({ deviceKey, additionalData }),
);
const result = actionResult?.result?.nModified > 0;
return [null, { result }];
}
async activateDevice(reqData) {
const { deviceId } = reqData;
const deviceCol = this.getDb().collection(devicesColl);
const deviceDetail = await deviceCol.findOne(deviceQueries.findById(deviceId));
if (!deviceDetail) {
const errorDetail = `Dispositivo con Id ${deviceId} no encontrado`;
const errorObj = this.errMgr.get(modErrs.findDevice.notFound, errorDetail);
return [errorObj, null];
}
const actionResult = await deviceCol.updateOne(
deviceQueries.findById(deviceId),
deviceQueries.updateObj({ state: deviceStates.ACTIVE }),
);
const result = actionResult?.result?.nModified > 0;
return [null, { result }];
}
async rejectDevice(reqData) {
const { deviceId } = reqData;
const deviceCol = this.getDb().collection(devicesColl);
const deviceDetail = await deviceCol.findOne(deviceQueries.findById(deviceId));
if (!deviceDetail) {
const errorDetail = `Dispositivo con Id ${deviceId} no encontrado`;
const errorObj = this.errMgr.get(modErrs.findDevice.notFound, errorDetail);
return [errorObj, null];
}
const actionResult = await deviceCol.updateOne(
deviceQueries.findById(deviceId),
deviceQueries.updateObj({ state: deviceStates.REJECTED }),
);
const result = actionResult?.result?.nModified > 0;
return [null, { result }];
}
async blockDevice(reqData) {
const { deviceId } = reqData;
const deviceCol = this.getDb().collection(devicesColl);
const deviceDetail = await deviceCol.findOne(deviceQueries.findById(deviceId));
if (!deviceDetail) {
const errorDetail = `Dispositivo con Id ${deviceId} no encontrado`;
const errorObj = this.errMgr.get(modErrs.findDevice.notFound, errorDetail);
return [errorObj, null];
}
const actionResult = await deviceCol.updateOne(
deviceQueries.findById(deviceId),
deviceQueries.updateObj({ state: deviceStates.BLOCKED }),
);
const result = actionResult?.result?.nModified > 0;
return [null, { result }];
}
async unlockDevice(reqData) {
const { deviceId } = reqData;
const deviceCol = this.getDb().collection(devicesColl);
const deviceDetail = await deviceCol.findOne(deviceQueries.findById(deviceId));
if (!deviceDetail) {
const errorDetail = `Dispositivo con Id ${deviceId} no encontrado`;
const errorObj = this.errMgr.get(modErrs.findDevice.notFound, errorDetail);
return [errorObj, null];
}
if (deviceDetail.state !== 'blocked') {
const errorDetail = `Dispositivo con Id ${deviceId} no se encuentra bloqueado`;
const errorObj = this.errMgr.get(modErrs.findDevice.notBlocked, errorDetail);
return [errorObj, null];
}
const actionResult = await deviceCol.updateOne(
deviceQueries.findById(deviceId),
deviceQueries.updateObj({ state: deviceStates.DRAFT }),
);
const result = actionResult?.result?.nModified > 0;
return [null, { result }];
}
async generateSessionKey(reqData) {
const { deviceId } = reqData;
const deviceCol = this.getDb().collection(devicesColl);
const device = await deviceCol.findOne(deviceQueries.findById(deviceId));
if (!device) {
const errorDetail = `Dispositivo con Id ${deviceId} no encontrado`;
const errorObj = this.errMgr.get(modErrs.findDevice.notFound, errorDetail);
return [errorObj, null];
}
const pub2 = forge.pki.publicKeyFromPem(device.deviceKey);
const sessionKey = uuidv4();
const encrypted = pub2.encrypt(sessionKey);
return [null, { encrypted, sessionKey }];
}
async addExternalReference(reqData) {
const { deviceId, externalSource, externalCode, externalDetail } = reqData;
const deviceCol = this.getDb().collection(devicesColl);
const deviceDetail = await deviceCol.findOne(deviceQueries.findById(deviceId));
if (!deviceDetail) {
const errorDetail = `Dispositivo con Id ${deviceId} no encontrado`;
const errorObj = this.errMgr.get(modErrs.findDevice.notFound, errorDetail);
return [errorObj, null];
}
const currentCoding = deviceDetail.externalCoding?.find((item) => item.externalSource === externalSource);
if (currentCoding) {
await deviceCol.updateOne(deviceQueries.findById(deviceId), deviceQueries.removeExternalRef(externalSource));
}
const externalRefData = { externalSource, externalCode, externalDetail: externalDetail || {} };
const actionResult = await deviceCol.updateOne(
deviceQueries.findById(deviceId),
deviceQueries.addExternalRef(externalRefData),
);
const result = actionResult?.result?.nModified > 0;
this.logger.debug(`Adición de una referencia externa a un tercero ${externalRefData} con resultado ${result}`);
return [null, { result }];
}
async deleteExternalReference(reqData) {
const { deviceId, externalSource } = reqData;
const deviceCol = this.getDb().collection(devicesColl);
const deviceDetail = await deviceCol.findOne(deviceQueries.findById(deviceId));
if (!deviceDetail) {
const errorDetail = `Dispositivo con Id ${deviceId} no encontrado`;
const errorObj = this.errMgr.get(modErrs.findDevice.notFound, errorDetail);
return [errorObj, null];
}
const actionResult = await deviceCol.updateOne(
deviceQueries.findById(deviceId),
deviceQueries.removeExternalRef(externalSource),
);
const result = actionResult?.result?.nModified > 0;
this.logger.debug(`eliminación de una referencia externa a un tercero ${externalSource} con resultado ${result}`);
return [null, { result }];
}
async addExternalReferenceAttributes(reqData) {
const { deviceId, externalSource, externalDetail } = reqData;
const deviceCol = this.getDb().collection(devicesColl);
const deviceDetail = await deviceCol.findOne(deviceQueries.findById(deviceId));
if (!deviceDetail) {
const errorDetail = `Dispositivo con Id ${deviceId} no encontrado`;
const errorObj = this.errMgr.get(modErrs.findDevice.notFound, errorDetail);
return [errorObj, null];
}
const actionResult = await deviceCol.updateOne(
...deviceQueries.addExternalRefAttribute(deviceId, externalSource, externalDetail),
);
const result = actionResult?.result?.nModified > 0;
this.logger.debug(`Adición de attributos a referencia externa a un tercero ${externalDetail} con resultado ${result}`);
return [null, { result }];
}
async deleteExternalReferenceAttributes(reqData) {
const { deviceId, externalSource, externalName } = reqData;
const deviceCol = this.getDb().collection(devicesColl);
const deviceDetail = await deviceCol.findOne(deviceQueries.findById(deviceId));
if (!deviceDetail) {
const errorDetail = `Dispositivo con Id ${deviceId} no encontrado`;
const errorObj = this.errMgr.get(modErrs.findDevice.notFound, errorDetail);
return [errorObj, null];
}
const actionResult = await deviceCol.updateOne(
...deviceQueries.deleteExternalRefAttribute(deviceId, externalSource, externalName),
);
const result = actionResult?.result?.nModified > 0;
this.logger.debug(`Eliminación de attributos a referencia externa a un tercero ${externalName} con resultado ${result}`);
return [null, { result }];
}
async getStoreDevices(reqData) {
const { storeId, deviceId } = reqData;
const deviceCol = this.getDb().collection(devicesColl);
const deviceList = await deviceCol.find(deviceQueries.findStoreDevices(storeId, deviceId)).toArray();
if (!deviceList) {
const errorDetail = !deviceId
? `Establecimiento ${storeId} no encontrado`
: `Establecimiento ${storeId} y dispositivo ${deviceId} no relacionados`;
const errorObj = this.errMgr.get(modErrs.findStore.notFound, errorDetail);
return [errorObj, null];
}
return [null, deviceList];
}
async getDevice(reqData) {
const { deviceId } = reqData;
const deviceCol = this.getDb().collection(devicesColl);
const deviceDetail = await deviceCol.findOne(deviceQueries.findDevice(deviceId));
if (!deviceDetail) {
const errorDetail = `Dispositivo con Id ${deviceId} no encontrado`;
const errorObj = this.errMgr.get(modErrs.findDevice.notFound, errorDetail);
return [errorObj, null];
}
return [null, deviceDetail];
}
}
module.exports = DeviceManager;