From ebf1a96f4285a7326e42fd21b33f53b64c65050e Mon Sep 17 00:00:00 2001 From: Jacco Flenter Date: Fri, 2 Nov 2018 07:38:16 +0100 Subject: [PATCH] [feat] Allow configuration of `Access-Control-Allow-Origin` value (#511) It's now possible to specify an origins value (default value is '*') when initialising the engine. This value will be returned as the Access-Control-Allow-Origin value. Related: #449 --- README.md | 1 + lib/server.js | 26 +++++++++++++++++--------- lib/transport.js | 4 +++- lib/transports/index.js | 6 +++--- lib/transports/polling-jsonp.js | 4 ++-- lib/transports/polling-xhr.js | 9 ++++----- lib/transports/polling.js | 4 ++-- lib/transports/websocket.js | 4 ++-- test/server.js | 16 ++++++++-------- 9 files changed, 42 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 1bc83d1ab..480c4ce8f 100644 --- a/README.md +++ b/README.md @@ -228,6 +228,7 @@ to a single process. - `maxHttpBufferSize` (`Number`): how many bytes or characters a message can be, before closing the session (to avoid DoS). Default value is `10E7`. + - `origins` (`String`): the allowed origins (`*`) - `allowRequest` (`Function`): A function that receives a given handshake or upgrade request as its first parameter, and can decide whether to continue or not. The second argument is a function that needs to be diff --git a/lib/server.js b/lib/server.js index 3884e9ce1..2ae3447e6 100644 --- a/lib/server.js +++ b/lib/server.js @@ -45,6 +45,7 @@ function Server (opts) { this.allowUpgrades = false !== opts.allowUpgrades; this.allowRequest = opts.allowRequest; this.cookie = false !== opts.cookie ? (opts.cookie || 'io') : false; + this.origins = opts.origins || '*'; this.cookiePath = false !== opts.cookiePath ? (opts.cookiePath || '/') : false; this.cookieHttpOnly = false !== opts.cookieHttpOnly; this.perMessageDeflate = false !== opts.perMessageDeflate ? (opts.perMessageDeflate || true) : false; @@ -221,7 +222,7 @@ Server.prototype.handleRequest = function (req, res) { var self = this; this.verify(req, false, function (err, success) { if (!success) { - sendErrorMessage(req, res, err); + self.sendErrorMessage(req, res, err); return; } @@ -242,7 +243,7 @@ Server.prototype.handleRequest = function (req, res) { * @api private */ -function sendErrorMessage (req, res, code) { +Server.prototype.sendErrorMessage = function (req, res, code) { var headers = { 'Content-Type': 'application/json' }; var isForbidden = !Server.errorMessages.hasOwnProperty(code); @@ -254,12 +255,13 @@ function sendErrorMessage (req, res, code) { })); return; } + + headers['Access-Control-Allow-Origin'] = this.origins; + headers['Vary'] = 'Origin'; if (req.headers.origin) { headers['Access-Control-Allow-Credentials'] = 'true'; - headers['Access-Control-Allow-Origin'] = req.headers.origin; - } else { - headers['Access-Control-Allow-Origin'] = '*'; } + if (res !== undefined) { res.writeHead(400, headers); res.end(JSON.stringify({ @@ -267,7 +269,7 @@ function sendErrorMessage (req, res, code) { message: Server.errorMessages[code] })); } -} +}; /** * generate a socket id. @@ -293,9 +295,12 @@ Server.prototype.handshake = function (transportName, req) { var id = this.generateId(req); debug('handshaking client "%s"', id); + var opts = { + origins: this.origins + }; try { - var transport = new transports[transportName](req); + var transport = new transports[transportName](req, opts); if ('polling' === transportName) { transport.maxHttpBufferSize = this.maxHttpBufferSize; transport.httpCompression = this.httpCompression; @@ -309,7 +314,7 @@ Server.prototype.handshake = function (transportName, req) { transport.supportsBinary = true; } } catch (e) { - sendErrorMessage(req, req.res, Server.errors.BAD_REQUEST); + this.sendErrorMessage(req, req.res, Server.errors.BAD_REQUEST); return; } var socket = new Socket(id, this, transport, req); @@ -404,7 +409,10 @@ Server.prototype.onWebSocket = function (req, socket) { // transport error handling takes over socket.removeListener('error', onUpgradeError); - var transport = new transports[req._query.transport](req); + var opts = { + origins: this.origins + }; + var transport = new transports[req._query.transport](req, opts); if (req._query && req._query.b64) { transport.supportsBinary = false; } else { diff --git a/lib/transport.js b/lib/transport.js index 933dad5bb..2f8501d9e 100644 --- a/lib/transport.js +++ b/lib/transport.js @@ -26,12 +26,14 @@ function noop () {} * Transport constructor. * * @param {http.IncomingMessage} request + * @param {Object} opts allows the origins option to be passed along * @api public */ -function Transport (req) { +function Transport (req, opts) { this.readyState = 'open'; this.discarded = false; + this.origins = opts.origins; } /** diff --git a/lib/transports/index.js b/lib/transports/index.js index fcff3223a..91ef8ab53 100644 --- a/lib/transports/index.js +++ b/lib/transports/index.js @@ -27,10 +27,10 @@ exports.polling.upgradesTo = ['websocket']; * @api private */ -function polling (req) { +function polling (req, opts) { if ('string' === typeof req._query.j) { - return new JSONP(req); + return new JSONP(req, opts); } else { - return new XHR(req); + return new XHR(req, opts); } } diff --git a/lib/transports/polling-jsonp.js b/lib/transports/polling-jsonp.js index 62e66e779..450953ce5 100644 --- a/lib/transports/polling-jsonp.js +++ b/lib/transports/polling-jsonp.js @@ -21,8 +21,8 @@ module.exports = JSONP; * @api public */ -function JSONP (req) { - Polling.call(this, req); +function JSONP (req, opts) { + Polling.call(this, req, opts); this.head = '___eio[' + (req._query.j || '').replace(/[^0-9]/g, '') + ']('; this.foot = ');'; diff --git a/lib/transports/polling-xhr.js b/lib/transports/polling-xhr.js index 3562524e1..ddcd5ed99 100644 --- a/lib/transports/polling-xhr.js +++ b/lib/transports/polling-xhr.js @@ -18,8 +18,8 @@ module.exports = XHR; * @api public */ -function XHR (req) { - Polling.call(this, req); +function XHR (req, opts) { + Polling.call(this, req, opts); } /** @@ -58,11 +58,10 @@ XHR.prototype.onRequest = function (req) { XHR.prototype.headers = function (req, headers) { headers = headers || {}; + headers['Access-Control-Allow-Origin'] = this.origins; + headers['Vary'] = 'Origin'; if (req.headers.origin) { headers['Access-Control-Allow-Credentials'] = 'true'; - headers['Access-Control-Allow-Origin'] = req.headers.origin; - } else { - headers['Access-Control-Allow-Origin'] = '*'; } return Polling.prototype.headers.call(this, req, headers); diff --git a/lib/transports/polling.js b/lib/transports/polling.js index 7c29c29ea..08048a914 100644 --- a/lib/transports/polling.js +++ b/lib/transports/polling.js @@ -27,8 +27,8 @@ module.exports = Polling; * @api public. */ -function Polling (req) { - Transport.call(this, req); +function Polling (req, opts) { + Transport.call(this, req, opts); this.closeTimeout = 30 * 1000; this.maxHttpBufferSize = null; diff --git a/lib/transports/websocket.js b/lib/transports/websocket.js index 7d5511b1d..64a3a5fdf 100644 --- a/lib/transports/websocket.js +++ b/lib/transports/websocket.js @@ -21,8 +21,8 @@ module.exports = WebSocket; * @api public */ -function WebSocket (req) { - Transport.call(this, req); +function WebSocket (req, opts) { + Transport.call(this, req, opts); var self = this; this.socket = req.websocket; this.socket.on('message', this.onData.bind(this)); diff --git a/test/server.js b/test/server.js index 9da4d109b..b694f81aa 100644 --- a/test/server.js +++ b/test/server.js @@ -58,7 +58,7 @@ describe('server', function () { expect(res.body.code).to.be(0); expect(res.body.message).to.be('Transport unknown'); expect(res.header['access-control-allow-credentials']).to.be('true'); - expect(res.header['access-control-allow-origin']).to.be('http://engine.io'); + expect(res.header['access-control-allow-origin']).to.be('*'); done(); }); }); @@ -75,7 +75,7 @@ describe('server', function () { expect(res.body.code).to.be(1); expect(res.body.message).to.be('Session ID unknown'); expect(res.header['access-control-allow-credentials']).to.be('true'); - expect(res.header['access-control-allow-origin']).to.be('http://engine.io'); + expect(res.header['access-control-allow-origin']).to.be('*'); done(); }); }); @@ -416,7 +416,7 @@ describe('server', function () { expect(res.body.code).to.be(3); expect(res.body.message).to.be('Bad request'); expect(res.header['access-control-allow-credentials']).to.be('true'); - expect(res.header['access-control-allow-origin']).to.be('http://engine.io'); + expect(res.header['access-control-allow-origin']).to.be('*'); done(); }); }); @@ -932,7 +932,7 @@ describe('server', function () { it('should trigger transport close before open for ws', function (done) { var opts = { transports: ['websocket'] }; listen(opts, function (port) { - var url = 'ws://%s:%d'.s('0.0.0.50', port); + var url = 'ws://%s:%d'.s('0.0.0.0', port); var socket = new eioc.Socket(url); socket.on('open', function () { done(new Error('Test invalidation')); @@ -2589,7 +2589,7 @@ describe('server', function () { describe('cors', function () { it('should handle OPTIONS requests', function (done) { - listen({handlePreflightRequest: true}, function (port) { + listen({handlePreflightRequest: true, origins: 'engine.io:*'}, function (port) { request.options('http://localhost:%d/engine.io/default/'.s(port)) .set('Origin', 'http://engine.io') .query({ transport: 'polling' }) @@ -2599,7 +2599,7 @@ describe('server', function () { expect(res.body.code).to.be(2); expect(res.body.message).to.be('Bad handshake method'); expect(res.header['access-control-allow-credentials']).to.be('true'); - expect(res.header['access-control-allow-origin']).to.be('http://engine.io'); + expect(res.header['access-control-allow-origin']).to.be('engine.io:*'); done(); }); }); @@ -2624,7 +2624,7 @@ describe('server', function () { var headers = {}; if (req.headers.origin) { headers['Access-Control-Allow-Credentials'] = 'true'; - headers['Access-Control-Allow-Origin'] = req.headers.origin; + headers['Access-Control-Allow-Origin'] = '*'; } else { headers['Access-Control-Allow-Origin'] = '*'; } @@ -2642,7 +2642,7 @@ describe('server', function () { expect(res.status).to.be(200); expect(res.body).to.be.empty(); expect(res.header['access-control-allow-credentials']).to.be('true'); - expect(res.header['access-control-allow-origin']).to.be('http://engine.io'); + expect(res.header['access-control-allow-origin']).to.be('*'); expect(res.header['access-control-allow-methods']).to.be('GET,HEAD,PUT,PATCH,POST,DELETE'); expect(res.header['access-control-allow-headers']).to.be('origin, content-type, accept'); done();