app/models/account-model.js

/**
 * @module account-model
 */

const utils = require('../lib/utils');
const config = require('./config-model').server;

const customGetAccount = config['account lib']
    ? require(config['account lib']).getAccount
    : undefined;
// var debug = require( 'debug' )( 'account-model' );

/**
 * @typedef AccountObj
 * @property { string } linkedServer
 * @property { string } [openRosaServer]
 * @property { string } key
 * @property {number} quota
 */

/**
 * Obtains account.
 *
 * @static
 * @param {module:survey-model~SurveyObject} survey - survey object
 * @return {Promise<Error|AccountObj>} promise that resolves in {@link module:account-model~AccountObj|Account object}
 */
function get(survey) {
    let error;
    const server = _getServer(survey);

    if (!server) {
        error = new Error('Bad Request. Server URL missing.');
        error.status = 400;

        return Promise.reject(error);
    }
    if (!utils.isValidUrl(server)) {
        error = new Error('Bad Request. Server URL is not a valid URL.');
        error.status = 400;

        return Promise.reject(error);
    }
    if (/https?:\/\/testserver.com\/bob/.test(server)) {
        return Promise.resolve({
            linkedServer: server,
            key: 'abc',
            quota: 100,
        });
    }
    if (/https?:\/\/testserver.com\/noquota/.test(server)) {
        error = new Error('Forbidden. No quota left.');
        error.status = 403;

        return Promise.reject(error);
    }
    if (/https?:\/\/testserver.com\/noapi/.test(server)) {
        error = new Error('Forbidden. No API access granted.');
        error.status = 405;

        return Promise.reject(error);
    }
    if (/https?:\/\/testserver.com\/noquotanoapi/.test(server)) {
        error = new Error('Forbidden. No API access granted.');
        error.status = 405;

        return Promise.reject(error);
    }
    if (/https?:\/\/testserver.com\/notpaid/.test(server)) {
        error = new Error('Forbidden. The account is not active.');
        error.status = 403;

        return Promise.reject(error);
    }

    return _getAccount(server);
}

/**
 * Check if account for passed survey is active, and not exceeding quota.
 * This passes back the original survey object and therefore differs from the get function!
 *
 * @static
 * @param {module:survey-model~SurveyObject} survey - survey object
 * @return {Promise<module:survey-model~SurveyObject>} updated SurveyObject
 */
function check(survey) {
    return get(survey).then((account) => {
        survey.account = account;

        return survey;
    });
}

/**
 * Checks if the provided serverUrl is part of the allowed 'linked' OpenRosa Server.
 *
 * @param { AccountObj } account - an account object
 * @param { string } serverUrl - server URL
 * @return { boolean } Whether server URL is allowed
 */
function _isAllowed(account, serverUrl) {
    return (
        account.linkedServer === '' ||
        new RegExp(`https?://${_stripProtocol(account.linkedServer)}`).test(
            serverUrl
        )
    );
}

/**
 * Strips http(s):// from the provided url
 *
 * @param { string } url - URL
 * @return {string|null} stripped url
 */
function _stripProtocol(url) {
    if (!url) {
        return null;
    }

    // strip http(s)://
    if (/https?:\/\//.test(url)) {
        url = url.substring(url.indexOf('://') + 3);
    }

    return url;
}

/**
 * Obtains account from either configuration (hardcoded) or via custom function
 *
 * @param { string } serverUrl - The serverUrl to be used to look up the account.
 * @return { AccountObj } {@link module:account-model~AccountObj|Account object}
 */
function _getAccount(serverUrl) {
    const hardcodedAccount = _getHardcodedAccount();

    if (_isAllowed(hardcodedAccount, serverUrl)) {
        return Promise.resolve(hardcodedAccount);
    }

    if (customGetAccount) {
        return customGetAccount(serverUrl, config['account api url']);
    }

    const error = new Error(
        'Forbidden. This server is not linked with Enketo.'
    );
    error.status = 403;

    return Promise.reject(error);
}

/**
 * Obtains the hardcoded account from the config
 *
 * @return { null|AccountObj } `null` or {@link module:account-model~AccountObj|Account object}
 */
function _getHardcodedAccount() {
    const app = require('../../config/express');
    const linkedServer = app.get('linked form and data server');

    // check if configuration is acceptable
    if (
        !linkedServer ||
        typeof linkedServer['server url'] === 'undefined' ||
        typeof linkedServer['api key'] === 'undefined'
    ) {
        return null;
    }

    // do not add default branding
    return {
        linkedServer: linkedServer['server url'],
        key: linkedServer['api key'],
        quota: linkedServer.quota || Infinity,
    };
}

/**
 * Extracts the server from a survey object or server string.
 *
 * @param  { string|module:survey-model~SurveyObject } survey - Server string or survey object.
 * @return { string|null } server
 */
function _getServer(survey) {
    if (!survey || (typeof survey === 'object' && !survey.openRosaServer)) {
        return null;
    }

    return typeof survey === 'string' ? survey : survey.openRosaServer;
}

module.exports = {
    get,
    check,
};