app/lib/router-utils.js

/**
 * @module router-utils
 */

const utils = require('./utils');
const config = require('../models/config-model').server;
/**
 * @static
 * @name idEncryptionKeys
 * @constant
 * @property { string } singleOnce
 * @property { string } view
 */
const keys = {
    singleOnce: config['less secure encryption key'],
    view: `${config['less secure encryption key']}view`,
};

/**
 * @static
 * @name enketoId
 * @function
 * @param {module:api-controller~ExpressRequest} req - HTTP request
 * @param {module:api-controller~ExpressResponse} res - HTTP response
 * @param {Function} next - Express callback
 * @param { string } id - Enketo ID
 */
function enketoIdParam(req, res, next, id) {
    if (/^[A-z0-9]{4,31}$/.test(id)) {
        req.enketoId = id;
        next();
    } else {
        next('route');
    }
}

/**
 * Wrapper function for {@link module:router-utils~_encryptedEnketoIdParam|_encryptedEnketoIdParam}
 *
 * @static
 * @name encryptedEnketoIdSingle
 * @function
 * @param {module:api-controller~ExpressRequest} req - HTTP request
 * @param {module:api-controller~ExpressResponse} res - HTTP response
 * @param {Function} next - Express callback
 * @param { string } id - Enketo ID
 */
function encryptedEnketoIdParamSingle(req, res, next, id) {
    _encryptedEnketoIdParam(req, res, next, id, keys.singleOnce);
}

/**
 * Wrapper function for {@link module:router-utils~_encryptedEnketoIdParam|_encryptedEnketoIdParam}
 *
 * @static
 * @name encryptedEnketoIdView
 * @function
 * @param {module:api-controller~ExpressRequest} req - HTTP request
 * @param {module:api-controller~ExpressResponse} res - HTTP response
 * @param {Function} next - Express callback
 * @param { string } id - Enketo ID
 */
function encryptedEnketoIdParamView(req, res, next, id) {
    _encryptedEnketoIdParam(req, res, next, id, keys.view);
}

/**
 * Returns decrypted Enketo ID
 *
 * @param {module:api-controller~ExpressRequest} req - HTTP request
 * @param {module:api-controller~ExpressResponse} res - HTTP response
 * @param {Function} next - Express callback
 * @param { string } id - Enketo ID
 * @param { string } key - Encryption key
 */
function _encryptedEnketoIdParam(req, res, next, id, key) {
    // Do not do a size check because we now have a configurable id size which can be used on an existing server,
    // and therefore old (encrypted) IDs may have different lengths as new (encrypted) IDs.
    // Routing takes care of FIRST checking whether the ID is a regular unencrypted ID.
    try {
        // Just see if it can be decrypted. Storing the encrypted value might
        // increases chance of leaking underlying enketo_id but for now this is used
        // in the submission controller and transformation controller.
        const decrypted = utils.insecureAes192Decrypt(id, key);
        // Sometimes decryption by incorrect keys works and results in gobledigook.
        // A really terrible way of working around this is to check if the result is
        // alphanumeric (as Enketo IDs always are).
        if (/^[a-z0-9]+$/i.test(decrypted)) {
            req.enketoId = decrypted;
            req.encryptedEnketoId = id;
            next();
        } else {
            console.error(
                `decryption with "${key}" worked but result is not alphanumeric, ignoring result:`,
                decrypted
            );
            next('route');
        }
    } catch (e) {
        // console.error( 'Could not decrypt:', req.encryptedEnketoId );
        next('route');
    }
}

module.exports = {
    enketoId: enketoIdParam,
    idEncryptionKeys: keys,
    encryptedEnketoIdSingle: encryptedEnketoIdParamSingle,
    encryptedEnketoIdView: encryptedEnketoIdParamView,
};