hyperswitch
Advanced tools
Comparing version 0.3.5 to 0.4.0
"use strict"; | ||
var utils = require('../utils'); | ||
var HTTPError = require('../exports').HTTPError; | ||
/** | ||
* From a list of uri Regex and values, constructs a regex to check if the | ||
* request URI is in the white-list. | ||
*/ | ||
var CACHE = new Map(); | ||
function constructInternalRequestRegex(variants) { | ||
if (CACHE.has(variants)) { | ||
return CACHE.get(variants); | ||
} | ||
var regex = (variants || []).map(function(regexString) { | ||
if (/^\/.+\/$/.test(regexString)) { | ||
return '(:?' + regexString.substring(1, regexString.length - 1) + ')'; | ||
} else { | ||
// Instead of comparing strings | ||
return '(:?^' | ||
+ regexString.replace(/[\-\[\]\/\{}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&") | ||
+ '$)'; | ||
} | ||
}).join('|'); | ||
regex = regex && regex.length > 0 ? new RegExp(regex) : undefined; | ||
CACHE.set(variants, regex); | ||
return regex; | ||
} | ||
module.exports = function(hyper, req, next, options) { | ||
@@ -35,6 +12,7 @@ var errorMessage = options.error_message | ||
Object.keys(options.whitelist).forEach(function(headerName) { | ||
var valueRegex = constructInternalRequestRegex(options.whitelist[headerName]); | ||
options._cache[headerName] = options._cache[headerName] | ||
|| utils.constructRegex(options.whitelist[headerName]); | ||
var headerValue = req.headers && req.headers[headerName] | ||
|| hyper._rootReq.headers && hyper._rootReq.headers[headerName]; | ||
if (!valueRegex.test(headerValue)) { | ||
if (!options._cache[headerName].test(headerValue)) { | ||
throw new HTTPError({ | ||
@@ -41,0 +19,0 @@ status: options.error_status || 403, |
@@ -38,2 +38,2 @@ "use strict"; | ||
}); | ||
}; | ||
}; |
@@ -12,3 +12,2 @@ 'use strict'; | ||
var swaggerUI = require('./swaggerUI'); | ||
var AuthService = require('./auth'); | ||
@@ -56,4 +55,5 @@ | ||
this._rootReq = par._rootReq || req; | ||
this._forwardedHeaders = par._forwardedHeaders || this._rootReq.headers; | ||
this._authService = par._authService ? new AuthService(par._authService) : null; | ||
this._requestFilters = par._requestFilters; | ||
this._subRequestFilters = par._subRequestFilters; | ||
this.ctx = par.ctx || {}; | ||
} else { | ||
@@ -82,4 +82,5 @@ // Brand new instance | ||
this._rootReq = null; | ||
this._forwardedHeaders = null; | ||
this._authService = null; | ||
this._requestFilters = []; | ||
this._subRequestFilters = []; | ||
this.ctx = parOptions && parOptions.ctx || null; | ||
} | ||
@@ -170,26 +171,2 @@ } | ||
// Special handling for external web requests | ||
HyperSwitch.prototype.defaultWebRequestHandler = function(req) { | ||
// Enforce the usage of UA | ||
req.headers = req.headers || {}; | ||
req.headers['user-agent'] = req.headers['user-agent'] || this.config.user_agent; | ||
if (this._authService) { | ||
this._authService.prepareRequest(this, req); | ||
} | ||
this.setRequestId(req); | ||
this.log('trace/webrequest', { | ||
req: req, | ||
request_id: req.headers['x-request-id'] | ||
}); | ||
// Make sure we have a string | ||
req.uri = '' + req.uri; | ||
return preq(req) | ||
.then(function(res) { | ||
if (res && res.headers) { | ||
utils.removeHopToHopHeaders(res.headers, true); | ||
} | ||
return res; | ||
}); | ||
}; | ||
HyperSwitch.prototype._isSysRequest = function(req) { | ||
@@ -248,2 +225,29 @@ return ((req.uri.params && req.uri.params.api === 'sys') | ||
HyperSwitch.prototype._createFilteredHandler = function(handler, filters, specInfo) { | ||
if (!filters || !filters.length) { | ||
return handler; | ||
} | ||
var filterIdx = 0; | ||
return function handlerWrapper(hyper, req) { | ||
if (filters && filterIdx < filters.length) { | ||
var filter = filters[filterIdx]; | ||
filterIdx++; | ||
if (typeof filter === 'function') { | ||
return filter(hyper, req, handlerWrapper, filter.options, specInfo); | ||
} | ||
if (filter.method | ||
&& filter.method !== req.method | ||
&& !(filter.method === 'get' && req.method === 'head')) { | ||
return handlerWrapper(hyper, req); | ||
} | ||
return filter.filter(hyper, req, handlerWrapper, filter.options, specInfo); | ||
} else { | ||
return P.method(handler)(hyper, req); | ||
} | ||
}; | ||
}; | ||
HyperSwitch.prototype.request = function(req, options) { | ||
@@ -253,31 +257,9 @@ if (req.method) { | ||
} | ||
return this._request(req, options); | ||
return this._filteredRequest(req, options); | ||
}; | ||
HyperSwitch.prototype._wrapInAccessCheck = function(handlerPromise, match, childReq) { | ||
var self = this; | ||
// Don't need to check access restrictions on /sys requests, | ||
// as these endpoints are internal, so can be accessed only | ||
// within HyperSwitch. (See HyperSwitch.prototype.request) All required | ||
// checks should be added and made at the root of the request chain, | ||
// at /v1 level | ||
if (!this._isSysRequest(childReq) | ||
&& match.permissions | ||
&& Array.isArray(match.permissions) | ||
&& match.permissions.length) { | ||
self._authService = self._authService || new AuthService(match.value.specRoot); | ||
self._authService.addRequirements(match.permissions); | ||
if (childReq.method === 'get' || childReq.method === 'head') { | ||
return P.all([ | ||
handlerPromise, | ||
self._authService.checkPermissions(self, childReq) | ||
]) | ||
.then(function(res) { return res[0]; }); | ||
} else { | ||
return self._authService.checkPermissions(self, childReq) | ||
.then(function() { return handlerPromise; }); | ||
} | ||
} else { | ||
return handlerPromise; | ||
} | ||
HyperSwitch.prototype._filteredRequest = function(req, options) { | ||
return this._createFilteredHandler(function(hyper, req) { | ||
return hyper._request(req, options); | ||
}, this._recursionDepth === 0 ? this._requestFilters : this._subRequestFilters)(this, req); | ||
}; | ||
@@ -288,9 +270,2 @@ | ||
var self = this; | ||
// Special handling for https? requests | ||
var host = req.uri.constructor === String ? req.uri : req.uri.protoHost; | ||
if (/^https?:\/\//.test(host)) { | ||
return self.defaultWebRequestHandler(req); | ||
} | ||
self._checkMaxRecursionDepth(req); | ||
@@ -333,25 +308,6 @@ | ||
var childHyperSwitch = this.makeChild(childReq, options); | ||
var specInfo = { | ||
var reqHandler = childHyperSwitch._createFilteredHandler(handler, match.filters, { | ||
path: match.value.path, | ||
spec: handler.spec | ||
}; | ||
var filterIdx = 0; | ||
var reqHandler = function handlerWrapper(hyper, req) { | ||
if (filterIdx < match.filters.length) { | ||
var filter = match.filters[filterIdx]; | ||
filterIdx++; | ||
if (filter.method | ||
&& filter.method !== req.method | ||
&& !(filter.method === 'get' && req.method === 'head')) { | ||
return handlerWrapper(hyper, req); | ||
} | ||
return filter.filter(hyper, req, handlerWrapper, filter.options, specInfo); | ||
} else { | ||
return P.method(handler)(hyper, req); | ||
} | ||
}; | ||
}); | ||
// This is a hack. Pure P.try get's executed on this tick, but we wanna | ||
@@ -365,3 +321,3 @@ // wrap it in metrics and access checks and start execution only afterwards. | ||
reqHandlerPromise = reqHandlerPromise | ||
return reqHandlerPromise | ||
.then(function(res) { | ||
@@ -393,4 +349,2 @@ childHyperSwitch.log('trace/hyper/response', { | ||
}); | ||
return childHyperSwitch._wrapInAccessCheck(reqHandlerPromise, match, childReq); | ||
} else { | ||
@@ -442,3 +396,3 @@ // No handler found. | ||
HyperSwitch.prototype[method] = function(uri, req) { | ||
return this._request(makeRequest(uri, req, method)); | ||
return this._filteredRequest(makeRequest(uri, req, method)); | ||
}; | ||
@@ -445,0 +399,0 @@ }); |
@@ -120,3 +120,3 @@ "use strict"; | ||
Router.prototype._loadRouteFilter = function(filterDef, globals, method) { | ||
Router.prototype._loadFilter = function(filterDef, globals, method) { | ||
if (filterDef.type === 'default') { | ||
@@ -126,5 +126,8 @@ filterDef.path = __dirname + '/filters/' + filterDef.name + '.js'; | ||
var options = this._expandOptions(filterDef, globals); | ||
options._cache = {}; | ||
return { | ||
filter: this._requireModule(filterDef.path), | ||
options: this._expandOptions(filterDef, globals), | ||
options: options, | ||
method: method | ||
@@ -139,3 +142,3 @@ }; | ||
var filters = filtersDef.map(function(filterDef) { | ||
return self._loadRouteFilter(filterDef, scope.globals, method); | ||
return self._loadFilter(filterDef, scope.globals, method); | ||
}); | ||
@@ -588,2 +591,10 @@ node.value = node.value || {}; | ||
.then(function() { | ||
(spec['x-request-filters'] || []).forEach(function(filterDef) { | ||
hyper._requestFilters = hyper._requestFilters || []; | ||
hyper._requestFilters.push(self._loadFilter(filterDef, { options: hyper.config })); | ||
}); | ||
(spec['x-sub-request-filters'] || []).forEach(function(filterDef) { | ||
hyper._subRequestFilters = hyper._subRequestFilters || []; | ||
hyper._subRequestFilters.push(self._loadFilter(filterDef, { options: hyper.config })); | ||
}); | ||
// Only set the tree after loading everything | ||
@@ -590,0 +601,0 @@ self.tree = rootNode; |
@@ -103,3 +103,4 @@ "use strict"; | ||
function logResponse(opts, response) { | ||
function logResponse(opts, response, startTime) { | ||
var latency = Date.now() - startTime; | ||
var logLevel = 'trace/request'; | ||
@@ -110,2 +111,4 @@ if (response.status >= 500) { | ||
logLevel = 'info/request'; | ||
} else if (latency > 5000) { | ||
logLevel = 'trace/request/slow'; | ||
} | ||
@@ -115,3 +118,4 @@ opts.log(logLevel, { | ||
res: response, | ||
stack: response.stack | ||
stack: response.stack, | ||
latency: latency, | ||
}); | ||
@@ -154,3 +158,3 @@ } | ||
logResponse(opts, response); | ||
logResponse(opts, response, opts.startTime); | ||
@@ -188,3 +192,3 @@ var body; | ||
if (req.method === 'head') { | ||
delete response.body; | ||
response.body = null; | ||
} | ||
@@ -191,0 +195,0 @@ |
@@ -48,2 +48,22 @@ "use strict"; | ||
/** | ||
* From a list of uri Regex and values, constructs a regex to check if the | ||
* request URI is in the white-list. | ||
*/ | ||
utils.constructRegex = function(variants) { | ||
var regex = (variants || []).map(function(regexString) { | ||
regexString = regexString.trim(); | ||
if (/^\/.+\/$/.test(regexString)) { | ||
return '(:?' + regexString.substring(1, regexString.length - 1) + ')'; | ||
} else { | ||
// Instead of comparing strings | ||
return '(:?^' | ||
+ regexString.replace(/[\-\[\]\/\{}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&") | ||
+ ')'; | ||
} | ||
}).join('|'); | ||
regex = regex && regex.length > 0 ? new RegExp(regex) : undefined; | ||
return regex; | ||
}; | ||
module.exports = utils; |
{ | ||
"name": "hyperswitch", | ||
"version": "0.3.5", | ||
"version": "0.4.0", | ||
"description": "REST API creation framework", | ||
@@ -36,3 +36,4 @@ "main": "index.js", | ||
"json-stable-stringify": "git+https://github.com/wikimedia/json-stable-stringify#master", | ||
"ajv": "^3.7.2" | ||
"ajv": "^3.7.2", | ||
"regexp-utils": "^0.3.2" | ||
}, | ||
@@ -39,0 +40,0 @@ "devDependencies": { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 2 instances in 1 package
3
168138
11
3930
+ Addedregexp-utils@^0.3.2
+ Addedregexp-utils@0.3.2(transitive)