From 62c29f5182bb9cba290bbd38f263ac62007e2054 Mon Sep 17 00:00:00 2001 From: sjones6 Date: Tue, 2 May 2017 20:39:28 -0400 Subject: [PATCH] Verification options to limit WS connection --- lib/server.js | 1 + lib/verify.js | 146 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 lib/verify.js diff --git a/lib/server.js b/lib/server.js index fea98648..ca1159fa 100644 --- a/lib/server.js +++ b/lib/server.js @@ -4,6 +4,7 @@ require('./nts'); require('./s3'); try{require('./ws');}catch(e){require('./wsp/server');} + require('./verify'); require('./file'); module.exports = Gun; }()); diff --git a/lib/verify.js b/lib/verify.js new file mode 100644 index 00000000..7f80bbc8 --- /dev/null +++ b/lib/verify.js @@ -0,0 +1,146 @@ +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 || {}; + + /** + * 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 (!verify) { + this.to.next(context); + return; + } + + 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;