verify.js 4.44 KB
var url = require('url');
var Gun = require('../gun');

/**
 * Verify the origin
 * 
 * @param  {RegExp|Array|String|Function} allowed   The allowed origins
 * @param  {String}              origin    String representation of the request URL
 * @return {Boolean}             Whether or not the origin is valid
 */
var verifyOrigin = function(allowed, origin) {
    var isValid = false;
    if (allowed instanceof RegExp) {
        isValid = allowed.test(origin);
    } else if (allowed instanceof Array) {
        isValid = allowed.indexOf(origin) !== -1;
    } else if (allowed instanceof Function) {
        isValid = allowed(origin);
    } else {
        isValid = allowed === origin;
    }
    return isValid;
};

/**
 * Verify the authentication header
 *
 * @todo  make this callback based
 * 
 * @param  {Function|String} check       Check option passed in
 * @param  {String}          authToken   The auth token passed in query string
 * @param  {Object}          query       Full query string as an object
 * @return {Boolean}         Whether or not the auth header is valid
 */
var verifyAuth = function(check, authToken, query) {
    var isValid = false;
    if (check instanceof Function) {
        isValid = check(authToken, query);
    } else {
        isValid = check === authToken;
    }
    return isValid === true;
};

Gun.on('opt', function(context) {
    var opt = context.opt || {};
    var ws = opt.ws || {};

    if (!opt.verify) {
        this.to.next(context);
        return;
    }

    /**
     *  verify when instantiating Gun can contain the following keys:
     *      allowOrigins: Array|RegExp|String
     *      auth:         String|Function
     *      authKey:      String
     *      check:        Function
     */
    var verify = opt.verify;
    if (ws.verifyClient && !verify.override) {
        throw Error('Cannot override existing verifyClient option in `ws` configuration.');
    }

    /**
     * Attach a verifyClient to the WS configuration.
     * 
     * @param  {Object}   info      Request information
     * @param  {Function} callback  Called when verification is complete
     */
    ws.verifyClient = function(info, callback) {

        // Callback Definitions
        var errorCallback = (errorCode, message) => {
            callback(false, errorCode, message);
        };
        var successCallback = () => {
            callback(true);
        };

        // 0. Verify security
        if (verify.requireSecure && !info.secure) {
            errorCallback(400, 'Insecure connection');
            return;
        }

        // 1. Verify request origin
        if (verify.allowOrigins && !verifyOrigin(verify.allowOrigins, info.origin)) {
            errorCallback(403, 'Origin forbidden');
            return;
        }

        // 2. Check authentication
        if (verify.auth) {

            // Retrieve parameters from the query string
            // and convert into an object
            var queryUrl = url.parse(info.req.url, true);
            queryUrl.query = queryUrl.query || {};

            // Get the header defined by the user
            // Or use authorization by default.
            var token = (verify.authKey)
                            ? queryUrl.query[verify.authKey]
                                : queryUrl.query.authorization;

            // Check the token against the verification function
            if (!token || !verifyAuth(verify.auth, token, queryUrl.query)) {
                errorCallback(403, 'Forbidden');
                return;
            }
        }

        // If no additional verification check is provided, 
        // simply return true at this point since all 
        // provided verifications have passed.
        if (!verify.check) {
            successCallback();
            return;
        }

        // 3. Pass to generic check handler
        // This can return a value; alternatively, this can use the
        // callback functionality
        var isValid = verify.check(info, successCallback, errorCallback); 

        // Check returned a response, pass this to the callback
        // If not, assume the user will call
        if (typeof isValid !== 'undefined') {
            if (typeof isValid === 'boolean') {
                if (isValid === true) {
                    successCallback();
                } else {
                    errorCallback(400);
                }
            }
        }          
    };
    context.opt.ws = ws;

    // Pass to next plugins
    this.to.next(context);
});

module.exports = Gun;