Source: operations/originators.js

'use strict';
/**
 * @module lib/operations/originators
 * @summary Whiteflag API originators endpoints handler module
 * @description Module with api originators endpoint handlers
 * @tutorial modules
 * @tutorial openapi
 */
module.exports = {
    getAllOriginators,
    getOriginator,
    updateOriginator,
    deleteOriginator,
    getPreSharedKey,
    storePreSharedKey,
    deletePreSharedKey
};

/* Common internal functions and classes */
const arr = require('../_common/arrays');
const { hash } = require('../_common/crypto');
const { ProcessingError } = require('../_common/errors');
const { createBody,
        addRelatedResource,
        sendImperative,
        sendIndicative } = require('./_common/response');

/* Whiteflag modules */
const wfState = require('../protocol/state');

/* Module constants */
const R_ORIGINATOR = 'originator';
const R_PRESHAREDKEY = 'psk';
const KEYIDLENGTH = 12;

/* MAIN MODULE FUNCTIONS */
/**
 * Provides current state of all originators from state module
 * @function getAllOriginators
 * @alias module:lib/operations/originators.getAllOriginators
 * @param {Object} req the http request
 * @param {Object} res the http response
 * @param {string} operationId the operation id as defined in the openapi definition
 * @param {logEndpointEventCb} callback
 */
function getAllOriginators(req, res, operationId, callback) {
    wfState.getOriginators(function opsGetOriginatorsCb(err, originatorsState = null) {
        // Create response body and preserve information before responding
        let resBody = createBody(req, operationId, R_ORIGINATOR);

        // Prepare and send response using common endpoint response function
        if (!err && !originatorsState) err = new Error('Could not retrieve known originators from state');
        if (originatorsState) resBody.meta.info = arr.addItem(resBody.meta.info, 'Currently known originators');
        return sendIndicative(res, err, resBody, originatorsState, callback);
    });
}

/**
 * Provides current originator state from state module
 * @function getOriginator
 * @alias module:lib/operations/originators.getOriginator
 * @param {Object} req the http request
 * @param {Object} res the http response
 * @param {string} operationId the operation id as defined in the openapi definition
 * @param {logEndpointEventCb} callback
 */
function getOriginator(req, res, operationId, callback) {
    const originatorAddress = req.params.address;
    wfState.getOriginatorData(originatorAddress, function opsGetOriginatorCb(err, originatorData = null) {
        // Create response body and preserve information before responding
        let resBody = createBody(req, operationId, R_ORIGINATOR);

        // Prepare and send response using common endpoint response function
        if (!err && !originatorData) err = new ProcessingError(`Unknown originator: ${originatorAddress}`, null, 'WF_API_NO_RESOURCE');
        if (!err) {
            resBody.meta.resource.id = originatorData.address;
            resBody.meta.resource.location = `${resBody.meta.request.url}`;
            resBody.meta = addRelatedResource(originatorData.blockchain, null, null, resBody.meta);
            resBody.meta.info = arr.addItem(resBody.meta.info, 'Current originator state');
        }
        return sendIndicative(res, err, resBody, originatorData, callback);
    });
}

/**
 * Updates originator state data
 * @function updateOriginator
 * @alias module:lib/operations/originators.updateOriginator
 * @param {Object} req the http request
 * @param {Object} res the http response
 * @param {string} operationId the operation id as defined in the openapi definition
 * @param {logEndpointEventCb} callback
 */
function updateOriginator(req, res, operationId, callback) {
    const originatorAddress = req.params.address;
    const originatorData = req.body;
    wfState.getOriginatorData(originatorAddress, function opsUpdateOriginatorCb(err, data = null) {
        // Create response body and preserve information before responding
        let resBody = createBody(req, operationId, R_ORIGINATOR);

        // Check orginator data
        if (!err) {
            if (!data) {
                err = new ProcessingError(`Unknown originator: ${originatorAddress}`, null, 'WF_API_NO_RESOURCE');
            } else {
                resBody.meta.resource.id = originatorAddress;
            }
            // Add and check address for correct update
            if (!originatorData.address) originatorData.address = originatorAddress;
            if (originatorData.address !== originatorAddress) {
                err = new ProcessingError(`Different originator address in request body: ${originatorData.address}`, null, 'WF_API_BAD_REQUEST');
            }
        }
        // Upsert originator data
        if (!err) wfState.upsertOriginatorData(originatorData);

        // Prepare and send response using common endpoint response function
        if (!err) {
            resBody.meta.resource.id = originatorData.address;
            resBody.meta.resource.location = `${resBody.meta.request.url}`;
            resBody.meta = addRelatedResource(originatorData.blockchain, null, null, resBody.meta);
            resBody.meta.info = arr.addItem(resBody.meta.info, 'Accepted request to update originator');
        }
        return sendIndicative(res, err, resBody, originatorData, callback);
    });
}

/**
 * Updates originator state data
 * @function deleteOriginator
 * @alias module:lib/operations/originators.deleteOriginator
 * @param {Object} req the http request
 * @param {Object} res the http response
 * @param {string} operationId the operation id as defined in the openapi definition
 * @param {logEndpointEventCb} callback
 */
function deleteOriginator(req, res, operationId, callback) {
    const originatorAddress = req.params.address;
    wfState.getOriginatorData(originatorAddress, function opsUpdateOriginatorCb(err, originatorData) {
        // Create response body and preserve information before responding
        let resBody = createBody(req, operationId, R_ORIGINATOR);

        // Check and delete orginator data
        if (!err && !originatorData) err = new ProcessingError(`Unknown originator: ${originatorAddress}`, null, 'WF_API_NO_RESOURCE');
        if (!err) wfState.removeOriginatorData(originatorAddress);

        // Prepare and send response using common endpoint response function
        if (!err) {
            resBody.meta.resource.id =  originatorData.address;
            resBody.meta = addRelatedResource(originatorData.blockchain, null, null, resBody.meta);
            resBody.meta.info = arr.addItem(resBody.meta.info, 'Accepted request to delete originator');
        }
        return sendIndicative(res, err, resBody, originatorData, callback);
    });
}

/**
 * Checks for pre-shared key for an originator
 * @function getPreSharedKey
 * @alias module:lib/operations/originators.getPreSharedKey
 * @param {Object} req the http request
 * @param {Object} res the http response
 * @param {string} operationId the operation id as defined in the openapi definition
 * @param {logEndpointEventCb} callback
 */
function getPreSharedKey(req, res, operationId, callback) {
    const originatorAddress = req.params.address;
    const accountAddress = req.params.account;
    let resBody = createBody(req, operationId, R_PRESHAREDKEY);

    // Get originator and check for errors
    wfState.getOriginatorData(originatorAddress, function opsGetOriginatorCb(err, originatorData = null) {
        if (!err && !originatorData) err = new ProcessingError(`Unknown originator: ${originatorAddress}`, null, 'WF_API_NO_RESOURCE');
        if (!err && !originatorData.blockchain) err = new Error('Could not determine blockchain for originator');
        if (err) return sendImperative(res, err, resBody, originatorData, callback);

        // Get pre-shared key
        const keyId = hash(originatorData.blockchain + originatorAddress + accountAddress, KEYIDLENGTH);
        wfState.getKey('presharedKeys', keyId, function opsGetOriginatorKeyCb(err, key) {
            if (!err && !key) err = new ProcessingError(`No pre-shared key with this originator available for use with blockchain account ${accountAddress}`, null, 'WF_API_NO_RESOURCE');
            key = null;

            // Prepare and send response using common endpoint response function
            if (!err) {
                resBody.meta.resource.id = keyId;
                resBody.meta = addRelatedResource(originatorData.blockchain, accountAddress, originatorAddress, resBody.meta);
                resBody.meta.info = arr.addItem(resBody.meta.info, 'Pre-shared key exists for this account and originator');
                resBody.meta.info = arr.addItem(resBody.meta.info, 'Returning originator data related to pre-shared key');
            }
            return sendImperative(res, err, resBody, originatorData, callback);
        });
    });
}

/**
 * Stores or updates pre-shared key for an originator
 * @function storePreSharedKey
 * @alias module:lib/operations/originators.storePreSharedKey
 * @param {Object} req the http request
 * @param {Object} res the http response
 * @param {string} operationId the operation id as defined in the openapi definition
 * @param {logEndpointEventCb} callback
 */
function storePreSharedKey(req, res, operationId, callback) {
    const originatorAddress = req.params.address;
    const accountAddress = req.params.account;
    let resBody = createBody(req, operationId, R_PRESHAREDKEY);
    let psk = req.body.preSharedKey || null;
    req.body.preSharedKey = null;

    // Get originator and check for errors
    wfState.getOriginatorData(originatorAddress, function opsGetOriginatorCb(err, originatorData = null) {
        if (!err && !originatorData) err = new ProcessingError(`Unknown originator: ${originatorAddress}`, null, 'WF_API_NO_RESOURCE');
        if (!err && !psk) err = new ProcessingError('No pre-shared key provided', null, 'WF_API_BAD_REQUEST');
        if (!err && !originatorData.blockchain) err = new Error('Could not determine blockchain for originator');
        if (err) return sendImperative(res, err, resBody, originatorData, callback);

        // Check blockchain account
        wfState.getBlockchainData(originatorData.blockchain, function opsGetBlockchainCb(err, bcState) {
            if (!err && !bcState) err = new Error(`Blockchain ${originatorData.blockchain} does not exist in state`);
            if (!err) {
                const index = bcState.accounts.findIndex(account => account.address.toLowerCase() === accountAddress.toLowerCase());
                if (index < 0) err = new ProcessingError(`Blockchain account ${accountAddress} does not exist`, null, 'WF_API_NO_RESOURCE');
            }
            if (!err) {
                // If no errors, store the pre-shared key (async)
                const keyId = hash(originatorData.blockchain + originatorAddress + accountAddress, KEYIDLENGTH);
                wfState.upsertKey('presharedKeys', keyId, psk);
                psk = null;

                // Prepare and send response using common endpoint response function
                resBody.meta.resource.id = keyId;
                resBody.meta = addRelatedResource(originatorData.blockchain, accountAddress, originatorAddress, resBody.meta);
                resBody.meta.info = arr.addItem(resBody.meta.info, 'Accepted request to store pre-shared key');
                resBody.meta.info = arr.addItem(resBody.meta.info, 'Returning originator data related to pre-shared key');
            }
            return sendImperative(res, err, resBody, originatorData, callback);
        });
    });
}

/**
 * Deletes pre-shared key for an originator
 * @function deletePreSharedKey
 * @alias module:lib/operations/originators.deletePreSharedKey
 * @param {Object} req the http request
 * @param {Object} res the http response
 * @param {string} operationId the operation id as defined in the openapi definition
 * @param {logEndpointEventCb} callback
 */
function deletePreSharedKey(req, res, operationId, callback) {
    const originatorAddress = req.params.address;
    const accountAddress = req.params.account;
    let resBody = createBody(req, operationId, R_PRESHAREDKEY);

    // Get originator and delete pre-shared key
    wfState.getOriginatorData(originatorAddress, function opsGetOriginatorCb(err, originatorData = null) {
        if (!err && !originatorData) err = new ProcessingError(`Unknown originator: ${originatorAddress}`, null, 'WF_API_NO_RESOURCE');
        if (!err && !originatorData.blockchain) err = new Error('Unknown blockchain for originator');
        if (!err) {
            // If no errors, delete the pre-shared key (async)
            const keyId = hash(originatorData.blockchain + originatorAddress + accountAddress, KEYIDLENGTH);
            wfState.removeKey('presharedKeys', keyId);

            // Prepare and send response using common endpoint response function
            resBody.meta.resource.id = keyId;
            resBody.meta = addRelatedResource(originatorData.blockchain, accountAddress, originatorAddress, resBody.meta);
            resBody.meta.info = arr.addItem(resBody.meta.info, 'Accepted request to delete pre-shared key');
            resBody.meta.info = arr.addItem(resBody.meta.info, 'Returning originator data related to pre-shared key');
        }
        return sendImperative(res, err, resBody, originatorData, callback);
    });
}