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
};