Source: operations/tokens.js

'use strict';
/**
 * @module lib/operations/tokens
 * @summary Whiteflag API tokens endpoints handler module
 * @description Module with api tokens endpoint handlers
 * @tutorial modules
 * @tutorial openapi
 */
module.exports = {
    get: getAuthToken,
    store: storeAuthToken,
    delete: deleteAuthToken
};

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

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

/* Module constants */
const R_TOKEN = 'token';
const KEYIDLENGTH = 12;

/* MAIN MODULE FUNCTIONS */
/**
 * Checks for an authentication token of an originator
 * @function getAuthToken
 * @alias module:lib/operations/tokens.get
 * @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 getAuthToken(req, res, operationId, callback) {
    const authTokenId = req.params.authTokenId;
    let resBody = createBody(req, operationId, R_TOKEN);

    // Get authentication token
    wfState.getKey('authTokens', authTokenId, function getAuthTokenGetKeyCb(err, authToken) {
        if (!err && !authToken) {
            err = new ProcessingError(`No authentication token with token id ${authTokenId}`, null, 'WF_API_NO_RESOURCE');
            return sendImperative(res, err, resBody, null, callback);
        }
        authToken = null;
        resBody.meta.resource.id = authTokenId;
        resBody.meta.resource.location = `${resBody.meta.request.url}`;

        // Get originator data and send response
        wfState.getOriginatorAuthToken(authTokenId, function getOriginatorAuthTokenCb(err, originatorData) {
            if (!err && !originatorData) {
                resBody.meta.warnings = arr.addItem(resBody.meta.info, 'Authentication token exists but not related to an originator');
                err = new ProcessingError(`No originator found for token id ${authTokenId}`, null, 'WF_API_RESOURCE_CONFLICT');
            }
            if (!err) {
                resBody.meta.info = arr.addItem(resBody.meta.info, 'Authentication token exists');
                resBody.meta = addRelatedResource(originatorData.blockchain, null, originatorData.address, resBody.meta);
                resBody.meta.info = arr.addItem(resBody.meta.info, 'Returning originator data for authentication token');
            }
            return sendImperative(res, err, resBody, originatorData, callback);
        });
    });
}

/**
 * Stores an authentication token for an originator
 * @function storeAuthToken
 * @alias module:lib/operations/tokens.store
 * @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 storeAuthToken(req, res, operationId, callback) {
    let authToken = req.body.secret || null;
    let resBody = createBody(req, operationId, R_TOKEN);
    req.body.secret = null;

    // Check for errors
    let errors = [];
    if (!authToken) errors.push('No authentication token provided');
    if (!req.body.name) errors.push('No originator name provided');
    if (!req.body.blockchain) errors.push('No blockchain provided');
    if (errors.length > 0) {
        let err = new ProcessingError('Invalid authentication token data', errors, 'WF_API_BAD_REQUEST');
        return sendImperative(res, err, resBody, null, callback);
    }
    // Check for existing authentication token
    const authTokenId = hash(req.body.blockchain + authToken, KEYIDLENGTH);
    wfState.getKey('authTokens', authTokenId, function storeAuthTokenGetKeyCb(err, existingAuthToken) {
        // Metadata for response
        resBody.meta.resource.id = authTokenId;
        resBody.meta.resource.location = `${resBody.meta.request.url}/${resBody.meta.resource.id}`;
        resBody.meta = addRelatedResource(req.body.blockchain, null, null, resBody.meta);

        // Check for errors and existing authentication token
        if (err) return sendImperative(res, err, resBody, null, callback);
        if (existingAuthToken) {
            existingAuthToken = null;
            return wfState.getOriginatorAuthToken(authTokenId, function getOriginatorAuthTokenCb(err, originatorData) {
                err = new ProcessingError('Authentication token already exists', null, 'WF_API_RESOURCE_CONFLICT');
                if (originatorData) {
                    resBody.meta = addRelatedResource(originatorData.blockchain, null, originatorData.address, resBody.meta);
                }
                return sendImperative(res, err, resBody, originatorData, callback);
            });
        }
        // Store token
        resBody.meta.info = arr.addItem(resBody.meta.info, 'Accepted request to store authentication token');
        wfState.upsertKey('authTokens', authTokenId, authToken);
        authToken = null;

        // Store originator data
        let originatorData = {
            name: req.body.name,
            blockchain: req.body.blockchain,
            authTokenId: authTokenId
        };
        if (req.body.address) originatorData.address = req.body.address;
        resBody.meta.info = arr.addItem(resBody.meta.info, 'Updating originators state with authentication token');
        wfState.upsertOriginatorData(originatorData);

        // Add related resources and send response
        resBody.meta = addRelatedResource(originatorData.blockchain, null, originatorData.address, resBody.meta);
        return sendImperative(res, err, resBody, originatorData, callback);
    });
}

/**
 * Deletes an authentication token of an originator
 * @function deleteAuthToken
 * @alias module:lib/operations/tokens.delete
 * @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 deleteAuthToken(req, res, operationId, callback) {
    const authTokenId = req.params.authTokenId;
    let resBody = createBody(req, operationId, R_TOKEN);

    // Check if key exists before deleting
    wfState.getKey('authTokens', authTokenId, function getAuthTokenGetKeyCb(err, existingAuthToken) {
        if (!err && !existingAuthToken) {
            err = new ProcessingError(`No authentication token with token id ${authTokenId}`, null, 'WF_API_NO_RESOURCE');
            return sendImperative(res, err, resBody, null, callback);
        }
        resBody.meta.resource.id = authTokenId;
        existingAuthToken = null;

        // Get originator data before deleting
        wfState.getOriginatorAuthToken(authTokenId, function getOriginatorAuthTokenCb(err, originatorData) {
            if (!originatorData) originatorData = {};
            if (!err) {
                resBody.meta = addRelatedResource(originatorData.blockchain, null, originatorData.address, resBody.meta);
                resBody.meta.info = arr.addItem(resBody.meta.info, 'Accepted request to delete authentication token');
                wfState.removeOriginatorAuthToken(authTokenId);
            }
            return sendImperative(res, err, resBody, originatorData, callback);
        });
    });
}