diff --git a/README.md b/README.md index 3b66e35..970589f 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ var port = process.env.PORT || 8080; var cors_proxy = require('cors-anywhere'); cors_proxy.createServer({ + originWhitelist: [], // Allow all origins requireHeader: ['origin', 'x-requested-with'], removeHeaders: ['cookie', 'cookie2'] }).listen(port, host, function() { @@ -89,6 +90,11 @@ The module exports two properties: `getHandler` and `createServer`. The following options are recognized by both methods: +* array of strings `originBlacklist` - If set, requests whose origin is listed are blocked. + Example: `['https://bad.example.com', 'http://bad.example.com']` +* array of strings `originWhitelist` - If set, requests whose origin is not listed are blocked. + If this list is empty, all origins are allowed. + Example: `['https://good.example.com', 'http://good.example.com']` * array of strings `requireHeader` - If set, the request must include this header or the API will refuse to proxy. Recommended if you want to prevent users from using the proxy for normal browsing. Example: `['Origin', 'X-Requested-With']`. diff --git a/lib/cors-anywhere.js b/lib/cors-anywhere.js index e2a93b2..d90753c 100644 --- a/lib/cors-anywhere.js +++ b/lib/cors-anywhere.js @@ -197,6 +197,8 @@ function parseURL(req_url) { var getHandler = exports.getHandler = function(options, proxy) { var corsAnywhere = { maxRedirects: 5, // Maximum number of redirects to be followed. + originBlacklist: [], // Requests from these origins will be blocked. + originWhitelist: [], // If non-empty, requests not from an origin in this list will be blocked. requireHeader: null, // Require a header to be set? removeHeaders: [] // Strip these request headers }; @@ -271,6 +273,19 @@ var getHandler = exports.getHandler = function(options, proxy) { return; } + var origin = req.headers.origin || ''; + if (corsAnywhere.originBlacklist.indexOf(origin) >= 0) { + res.writeHead(403, 'Forbidden', cors_headers); + res.end('The origin "' + origin + '" was blacklisted by the operator of this proxy.'); + return; + } + + if (corsAnywhere.originWhitelist.length && corsAnywhere.originWhitelist.indexOf(origin) === -1) { + res.writeHead(403, 'Forbidden', cors_headers); + res.end('The origin "' + origin + '" was not whitelisted by the operator of this proxy.'); + return; + } + var isRequestedOverHttps = req.connection.encrypted || /^\s*https/.test(req.headers['x-forwarded-proto']); var proxyBaseUrl = (isRequestedOverHttps ? 'https://' : 'http://') + req.headers.host; diff --git a/server.js b/server.js index 59f2902..2df8fb8 100644 --- a/server.js +++ b/server.js @@ -2,8 +2,15 @@ var host = process.env.PORT ? '0.0.0.0' : '127.0.0.1'; var port = process.env.PORT || 8080; +// Grab the blacklist from the command-line so that we can update the blacklist without deploying +// again. CORS Anywhere is open by design, and this blacklist is not used, except for countering +// immediate abuse (e.g. denial of service). If you want to block all origins except for some, +// use originWhitelist instead. +var originBlacklist = (process.env.CORSANYWHERE_BLACKLIST || '').split(','); + var cors_proxy = require('./lib/cors-anywhere'); cors_proxy.createServer({ + originBlacklist: originBlacklist, requireHeader: ['origin', 'x-requested-with'], removeHeaders: [ 'cookie', diff --git a/test/test.js b/test/test.js index 5c1714d..595a839 100644 --- a/test/test.js +++ b/test/test.js @@ -345,6 +345,72 @@ describe('server on https', function() { }); }); +describe('originBlacklist', function() { + before(function() { + cors_anywhere = createServer({ + originBlacklist: ['http://denied.origin.test'], + }); + cors_anywhere_port = cors_anywhere.listen(0).address().port; + }); + after(stopServer); + + it('GET /example.com with denied origin', function(done) { + request(cors_anywhere) + .get('/example.com/') + .set('Origin', 'http://denied.origin.test') + .expect('Access-Control-Allow-Origin', '*') + .expect(403, done); + }); + + it('GET /example.com without denied origin', function(done) { + request(cors_anywhere) + .get('/example.com/') + .set('Origin', 'https://denied.origin.test') // Note: different scheme! + .expect('Access-Control-Allow-Origin', '*') + .expect(200, done); + }); + + it('GET /example.com without origin', function(done) { + request(cors_anywhere) + .get('/example.com/') + .expect('Access-Control-Allow-Origin', '*') + .expect(200, done); + }); +}); + +describe('originWhitelist', function() { + before(function() { + cors_anywhere = createServer({ + originWhitelist: ['https://permitted.origin.test'], + }); + cors_anywhere_port = cors_anywhere.listen(0).address().port; + }); + after(stopServer); + + it('GET /example.com with permitted origin', function(done) { + request(cors_anywhere) + .get('/example.com/') + .set('Origin', 'https://permitted.origin.test') + .expect('Access-Control-Allow-Origin', '*') + .expect(200, done); + }); + + it('GET /example.com without permitted origin', function(done) { + request(cors_anywhere) + .get('/example.com/') + .set('Origin', 'http://permitted.origin.test') // Note: different scheme! + .expect('Access-Control-Allow-Origin', '*') + .expect(403, done); + }); + + it('GET /example.com without origin', function(done) { + request(cors_anywhere) + .get('/example.com/') + .expect('Access-Control-Allow-Origin', '*') + .expect(403, done); + }); +}); + describe('requireHeader', function() { before(function() { cors_anywhere = createServer({