Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

oauth_reverse_proxy

Package Overview
Dependencies
Maintainers
1
Versions
36
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

oauth_reverse_proxy - npm Package Compare versions

Comparing version 1.1.0 to 1.2.0

test/auth_proxy_https_test.js

44

lib/proxy/config.js

@@ -27,2 +27,9 @@ var fs = require('fs');

// Whether this proxy listens on an HTTPS socket on from_port. Defaults to false.
Object.defineProperty(this_obj, 'https', { 'value': config.https != undefined || false, writable: false });
if (this_obj.https) {
Object.defineProperty(this_obj, 'https_key_file', { 'value': config.https.key, writable: false });
Object.defineProperty(this_obj, 'https_cert_file', { 'value': config.https.cert, writable: false });
}
// An optional object defining quotas to apply to inbound requests.

@@ -33,10 +40,10 @@ Object.defineProperty(this_obj, 'quotas', { 'value': (config.quotas || {thresholds:{}}), writable: false});

var whitelist = config.whitelist ||
[{
path: "/livecheck",
methods: [ "GET" ]
},
{
path: "/healthcheck",
methods: [ "GET" ]
}];
[{
path: "/livecheck",
methods: [ "GET" ]
},
{
path: "/healthcheck",
methods: [ "GET" ]
}];

@@ -90,2 +97,23 @@ Object.defineProperty(this_obj, 'whitelist', { 'value': whitelist, writable: false });

if (this.https) {
// Validate that the key and cert files exist
if (this.https_key_file == undefined)
return "no ssl key file provided";
try {
fs.statSync(this.https_key_file);
} catch(e) {
return "https key file " + this.https_key_file + " does not exist";
}
if (this.https_cert_file == undefined)
return "no ssl cert file provided";
try {
fs.statSync(this.https_cert_file);
} catch(e) {
return "https cert file " + this.https_cert_file + " does not exist";
}
}
var my_from_port = parseInt(this.from_port);

@@ -92,0 +120,0 @@ var my_to_port = parseInt(this.to_port);

170

lib/proxy/index.js
var _ = require('underscore');
var fs = require('fs');
var http = require('http');
var https = require('https');
var connect = require('connect');

@@ -44,2 +46,78 @@ var httpProxy = require('http-proxy');

Proxy.prototype.getConnectApp = function() {
var this_obj = this;
// Validators
var oauth_param_sanity_validator = require('./validators/oauth_param_sanity_validator.js')(this_obj);
var oauth_signature_validator = require('./validators/oauth_signature_validator.js')(this_obj);
var oauth_timestamp_validator = require('./validators/oauth_timestamp_validator.js')(this_obj);
var quota_validator = require('./validators/quota_validator.js')(this_obj);
var request_sanity_validator = require('./validators/request_sanity_validator.js')(this_obj);
var url_length_validator = require('./validators/url_length_validator.js')(this_obj);
var whitelist_validator = require('./validators/whitelist_validator.js')(this_obj);
// Mutators
var form_parser = connect.urlencoded();
var forward_header_mutator = require('./mutators/forward_header_mutator.js')(this_obj);
var host_header_mutator = require('./mutators/host_header_mutator.js')(this_obj);
var oauth_param_collector = require('./mutators/oauth_param_collector.js')(this_obj);
var query_string_parser = connect.query();
var restreamer = require('./mutators/restreamer.js')({stringify:require('querystring').stringify});
var url_parser = require('./mutators/url_parser.js')(this_obj);
var proxy = httpProxy.createProxyServer({});
// Handle connection errors to the underlying service. Normal errors returned by the
// service (like 404s) will get proxied through without any tampering.
proxy.on('error', function(err, req, res) {
this_obj.logger.info("Got error %s communicating with underlying server.", util.inspect(err));
res.writeHead(500, "Connection to " + this_obj.config.service_name + " failed");
res.end();
});
// Return an all-singing, all-dancing OAuth validating connect pipeline.
return connect(
// Test for minimum viable sanity for an inbound request. Pass in the proxy object so that
// the URI and Host header can be matched against the expected values, if provided.
request_sanity_validator,
// Reject request with URLs longer than 16kb
url_length_validator,
// Unpack the body of POSTs so we can use them in signatures. Note that this
// will implicitly limit POST size to 1mb. We may wish to add configuration around
// this in the future.
form_parser,
// Parse query string
query_string_parser,
// Parse url once so that it's available in a clean format for the oauth validator
url_parser,
// Gather the oauth params from the request
oauth_param_collector,
// Modify the request headers to add x-forwarded-*
forward_header_mutator,
// Check the request against our path/verb whitelist
whitelist_validator,
// Validate that the oauth params pass a set of viability checks (existence, version, etc)
oauth_param_sanity_validator,
// Validate that the request is within quota
quota_validator,
// Validate that the timestamp of the request is legal
oauth_timestamp_validator,
// Perform the oauth signature validation
oauth_signature_validator,
// Update the host header
host_header_mutator,
// Since connect messes with the input parameters and we want to pass them through
// unadulterated to the target, we need to add restreamer to the chain. But we only
// need to do this if we're given a formencoded request.
restreamer,
// Whew. After all of that, we're ready to proxy the request.
function(req, res) {
// Proxy a web request to the target port on localhost using the provided agent.
// If no agent is provided, node-http-proxy will return a connection: close.
proxy.web(req, res, {agent: HTTP_AGENT, target: { 'host' : 'localhost', 'port' : this_obj.config.to_port }});
}
);
};
/**

@@ -59,74 +137,6 @@ * Initialize the proxy by loading its keys and wiring up a connect pipeline to route

// Validators
var oauth_param_sanity_validator = require('./validators/oauth_param_sanity_validator.js')(this_obj);
var oauth_signature_validator = require('./validators/oauth_signature_validator.js')(this_obj);
var oauth_timestamp_validator = require('./validators/oauth_timestamp_validator.js')(this_obj);
var quota_validator = require('./validators/quota_validator.js')(this_obj);
var request_sanity_validator = require('./validators/request_sanity_validator.js')(this_obj);
var url_length_validator = require('./validators/url_length_validator.js')(this_obj);
var whitelist_validator = require('./validators/whitelist_validator.js')(this_obj);
// Mutators
var form_parser = connect.urlencoded();
var forward_header_mutator = require('./mutators/forward_header_mutator.js')(this_obj);
var host_header_mutator = require('./mutators/host_header_mutator.js')(this_obj);
var oauth_param_collector = require('./mutators/oauth_param_collector.js')(this_obj);
var query_string_parser = connect.query();
var restreamer = require('./mutators/restreamer.js')({stringify:require('querystring').stringify});
var url_parser = require('./mutators/url_parser.js')(this_obj);
var proxy = httpProxy.createProxyServer({});
// The express server is wired up with a list of mutators and validators that are applied to
// each inbound request.
this_obj.server = connect.createServer(
// Test for minimum viable sanity for an inbound request. Pass in the proxy object so that
// the URI and Host header can be matched against the expected values, if provided.
request_sanity_validator,
// Reject request with URLs longer than 16kb
url_length_validator,
// Unpack the body of POSTs so we can use them in signatures. Note that this
// will implicitly limit POST size to 1mb. We may wish to add configuration around
// this in the future.
form_parser,
// Parse query string
query_string_parser,
// Parse url once so that it's available in a clean format for the oauth validator
url_parser,
// Gather the oauth params from the request
oauth_param_collector,
// Modify the request headers to add x-forwarded-*
forward_header_mutator,
// Check the request against our path/verb whitelist
whitelist_validator,
// Validate that the oauth params pass a set of viability checks (existence, version, etc)
oauth_param_sanity_validator,
// Validate that the request is within quota
quota_validator,
// Validate that the timestamp of the request is legal
oauth_timestamp_validator,
// Perform the oauth signature validation
oauth_signature_validator,
// Update the host header
host_header_mutator,
// Since connect messes with the input parameters and we want to pass them through
// unadulterated to the target, we need to add restreamer to the chain. But we only
// need to do this if we're given a formencoded request.
restreamer,
// Whew. After all of that, we're ready to proxy the request.
function(req, res) {
// Proxy a web request to the target port on localhost using the provided agent.
// If no agent is provided, node-http-proxy will return a connection: close.
proxy.web(req, res, {agent: HTTP_AGENT, target: { 'host' : 'localhost', 'port' : this_obj.config.to_port }});
}
);
var app = this_obj.getConnectApp.apply(this_obj);
// Handle connection errors to the underlying service. Normal errors returned by the
// service (like 404s) will get proxied through without any tampering.
proxy.on('error', function(err, req, res) {
this_obj.logger.info("Got error %s communicating with underlying server.", util.inspect(err));
res.writeHead(500, "Connection to " + this_obj.config.service_name + " failed");
res.end();
});
// Start watching the key directory for changes

@@ -137,5 +147,23 @@ this_obj.keystore.setupWatcher();

this_obj.logger.info("Listening on port %s", this_obj.config.from_port);
this_obj.server.listen(this_obj.config.from_port, '0.0.0.0');
this_obj.server.listen(this_obj.config.from_port, '::');
// If the proxy config specifically asks for https, use https. Otherwise, use http.
if (this_obj.config.https) {
var ipv4_server = https.createServer({
key: fs.readFileSync(this_obj.config.https_key_file),
cert: fs.readFileSync(this_obj.config.https_cert_file)
}, app);
var ipv6_server = https.createServer({
key: fs.readFileSync(this_obj.config.https_key_file),
cert: fs.readFileSync(this_obj.config.https_cert_file)
}, app);
} else {
var ipv4_server = http.createServer(app);
var ipv6_server = http.createServer(app);
}
// Listen on our 2 servers. If we attempt to listen on a single server, the results will be non-deterministic.
// It works on node 0.10.30 but not on 0.10.35, for example. Separating the two servers appears to work everywhere.
ipv4_server.listen(this_obj.config.from_port, '0.0.0.0');
ipv6_server.listen(this_obj.config.from_port, '::');
cb(null, this_obj);

@@ -142,0 +170,0 @@ });

{
"name": "oauth_reverse_proxy",
"description": "An OAuth 1.0a authenticating reverse proxy",
"version": "1.1.0",
"version": "1.2.0",
"contributors": [

@@ -6,0 +6,0 @@ {

@@ -19,3 +19,4 @@ oauth_reverse_proxy is an authenticating service proxy that fronts any web server and enforces that callers present the correct OAuth credentials.

* Built to perform: A single node can authenticate around 10k requests per second on reasonable hardware.
* Flexible enough to front multiple services: If you run more than one HTTP server per system, as is common in the case of an nginx-fronted application, you can put an instance of `oauth_reverse_proxy` either in front of or behind nginx. A single instance of `oauth_reverse_proxy` can bind a separate proxy to any number of inbound ports.
* Supports inbound requests over http and https.
* Is flexible enough to front multiple services: If you run more than one HTTP server per system, as is common in the case of an nginx-fronted application, you can put an instance of `oauth_reverse_proxy` either in front of or behind nginx. A single instance of `oauth_reverse_proxy` can bind a separate proxy to any number of inbound ports.
* Supports configurable whitelisting: You likely have a load balancer that needs to perform health-checks against your application without performing authentication. `oauth_reverse_proxy` supports regex-based whitelists, so you can configure an un-authenticated path through to only those routes.

@@ -22,0 +23,0 @@ * Supports a quota per key, allowing you to define that a given key should only be allowed to make a certain number of hits per a given time interval.

@@ -95,3 +95,7 @@ var fs = require('fs');

{ 'filename': 'giant_from_port_service.json', 'expected_error': 'from_port must be a valid port number'},
{ 'filename': 'giant_to_port_service.json', 'expected_error': 'to_port must be a valid port number'}
{ 'filename': 'giant_to_port_service.json', 'expected_error': 'to_port must be a valid port number'},
{ 'filename': 'no_ssl_cert_service.json', 'expected_error': 'no ssl cert file provided'},
{ 'filename': 'no_ssl_key_service.json', 'expected_error': 'no ssl key file provided'},
{ 'filename': 'invalid_ssl_cert_service.json', 'expected_error': 'https cert file ./test/resources/cert_oops.pem does not exist'},
{ 'filename': 'invalid_ssl_key_service.json', 'expected_error': 'https key file ./test/resources/key_oops.pem does not exist'}
].forEach(function(validation) {

@@ -98,0 +102,0 @@ it ('should reject a proxy config with error: ' + validation.expected_error, function() {

@@ -105,3 +105,5 @@ var should = require('should');

'nonnumeric_quota_key_threshold_service.json', 'nonpositive_quota_default_threshold_service.json',
'nonpositive_quota_key_threshold_service.json', 'subsecond_quota_interval_service.json'
'nonpositive_quota_key_threshold_service.json', 'subsecond_quota_interval_service.json',
'no_ssl_cert_service.json', 'no_ssl_key_service.json',
'invalid_ssl_cert_service.json', 'invalid_ssl_key_service.json'
].forEach(function(invalid_config_file) {

@@ -108,0 +110,0 @@ it ('should reject invalid proxy config file ' + invalid_config_file, function() {

var _ = require('underscore');
var job_server = require('./server/test_server.js').JobServer;
// All the messy business of creating and sending requests (both authenticated and unauthenticated)

@@ -6,0 +4,0 @@ // lives in request_sender.

@@ -199,3 +199,4 @@ var should = require('should');

signature_components[0] = options.method;
signature_components[1] = 'http://' + options.hostname + ':' + options.port + options.pathname;
var proto = options.protocol || 'http:';
signature_components[1] = proto + '//' + options.hostname + ':' + options.port + options.pathname;

@@ -202,0 +203,0 @@ // We don't technically need to reset the options value, but it does make it more clear that

Future Items:
- [ ] Add support for HTTPs termination
- [ ] Add configurable location for SSL certs per proxy
- [x] Add support for HTTPs termination
- [x] Add configurable location for SSL certs per proxy
- [x] Add support for per-key rate-limit quotas
- [ ] Add data collection via statsd
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc