diff --git a/README.md b/README.md index 073ececa..dfd6332a 100644 --- a/README.md +++ b/README.md @@ -244,13 +244,10 @@ to a single process. - `httpCompression` (`Object|Boolean`): parameters of the http compression for the polling transports (see [zlib](http://nodejs.org/api/zlib.html#zlib_options) api docs). Set to `false` to disable. (`true`) - `threshold` (`Number`): data is compressed only if the byte size is above this value (`1024`) - - `cookie` (`String|Boolean`): name of the HTTP cookie that + - `cookie` (`Object|Boolean`): configuration of the cookie that contains the client sid to send as part of handshake response - headers. Set to `false` to not send one. (`io`) - - `cookiePath` (`String|Boolean`): path of the above `cookie` - option. If false, no path will be sent, which means browsers will only send the cookie on the engine.io attached path (`/engine.io`). - Set false to not save io cookie on all requests. (`/`) - - `cookieHttpOnly` (`Boolean`): If `true` HttpOnly io cookie cannot be accessed by client-side APIs, such as JavaScript. (`true`) _This option has no effect if `cookie` or `cookiePath` is set to `false`._ + headers. This cookie might be used for sticky-session. Defaults to not sending any cookie (`false`). + See [here](https://github.com/jshttp/cookie#options-1) for all supported options. - `wsEngine` (`String`): what WebSocket server implementation to use. Specified module must conform to the `ws` interface (see [ws module api docs](https://github.com/websockets/ws/blob/master/doc/ws.md)). Default value is `ws`. An alternative c++ addon is also available by installing `uws` module. - `initialPacket` (`Object`): an optional packet which will be concatenated to the handshake packet emitted by Engine.IO. - `close` diff --git a/lib/server.js b/lib/server.js index cc1959db..7cfb05b8 100644 --- a/lib/server.js +++ b/lib/server.js @@ -30,16 +30,26 @@ class Server extends EventEmitter { this.transports = opts.transports || Object.keys(transports); this.allowUpgrades = false !== opts.allowUpgrades; this.allowRequest = opts.allowRequest; - this.cookie = false !== opts.cookie ? opts.cookie || "io" : false; - this.cookiePath = - false !== opts.cookiePath ? opts.cookiePath || "/" : false; - this.cookieHttpOnly = false !== opts.cookieHttpOnly; this.perMessageDeflate = false !== opts.perMessageDeflate ? opts.perMessageDeflate || true : false; this.httpCompression = false !== opts.httpCompression ? opts.httpCompression || {} : false; this.initialPacket = opts.initialPacket; + this.opts = Object.assign({}, opts); + + if (opts.cookie) { + this.opts.cookie = Object.assign( + { + name: "io", + path: "/", + httpOnly: opts.cookie.path !== false, + sameSite: "lax" + }, + opts.cookie + ); + } + // initialize compression options ["perMessageDeflate", "httpCompression"].forEach(type => { let compression = this[type]; @@ -249,12 +259,13 @@ class Server extends EventEmitter { const socket = new Socket(id, this, transport, req); const self = this; - if (false !== this.cookie) { - transport.on("headers", function(headers) { - headers["Set-Cookie"] = cookieMod.serialize(self.cookie, id, { - path: self.cookiePath, - httpOnly: self.cookiePath ? self.cookieHttpOnly : false - }); + if (this.opts.cookie) { + transport.on("headers", headers => { + headers["Set-Cookie"] = cookieMod.serialize( + this.opts.cookie.name, + id, + this.opts.cookie + ); }); } diff --git a/test/server.js b/test/server.js index ef778b50..d36e50dd 100644 --- a/test/server.js +++ b/test/server.js @@ -141,133 +141,137 @@ describe("server", function() { }); describe("handshake", function() { - it("should send the io cookie", function(done) { - listen(function(port) { + it("should send the io cookie", done => { + listen({ cookie: true }, port => { request .get("http://localhost:%d/engine.io/default/".s(port)) .query({ transport: "polling", b64: 1 }) .end(function(err, res) { expect(err).to.be(null); // hack-obtain sid - var sid = res.text.match(/"sid":"([^"]+)"/)[1]; + const sid = res.text.match(/"sid":"([^"]+)"/)[1]; expect(res.headers["set-cookie"][0]).to.be( - "io=" + sid + "; Path=/; HttpOnly" + `io=${sid}; Path=/; HttpOnly; SameSite=Lax` ); done(); }); }); }); - it("should send the io cookie custom name", function(done) { - listen({ cookie: "woot" }, function(port) { + it("should send the io cookie custom name", done => { + listen({ cookie: { name: "woot" } }, port => { request .get("http://localhost:%d/engine.io/default/".s(port)) .query({ transport: "polling", b64: 1 }) .end(function(err, res) { expect(err).to.be(null); - var sid = res.text.match(/"sid":"([^"]+)"/)[1]; + const sid = res.text.match(/"sid":"([^"]+)"/)[1]; expect(res.headers["set-cookie"][0]).to.be( - "woot=" + sid + "; Path=/; HttpOnly" + `woot=${sid}; Path=/; HttpOnly; SameSite=Lax` ); done(); }); }); }); - it("should send the cookie with custom path", function(done) { - listen({ cookiePath: "/custom" }, function(port) { + it("should send the cookie with custom path", done => { + listen({ cookie: { path: "/custom" } }, port => { request .get("http://localhost:%d/engine.io/default/".s(port)) .query({ transport: "polling", b64: 1 }) .end(function(err, res) { expect(err).to.be(null); - var sid = res.text.match(/"sid":"([^"]+)"/)[1]; + const sid = res.text.match(/"sid":"([^"]+)"/)[1]; expect(res.headers["set-cookie"][0]).to.be( - "io=" + sid + "; Path=/custom; HttpOnly" + `io=${sid}; Path=/custom; HttpOnly; SameSite=Lax` ); done(); }); }); }); - it("should send the cookie with path=false", function(done) { - listen({ cookiePath: false }, function(port) { + it("should send the cookie with path=false", done => { + listen({ cookie: { path: false } }, port => { request .get("http://localhost:%d/engine.io/default/".s(port)) .query({ transport: "polling", b64: 1 }) .end(function(err, res) { expect(err).to.be(null); - var sid = res.text.match(/"sid":"([^"]+)"/)[1]; - expect(res.headers["set-cookie"][0]).to.be("io=" + sid); - done(); - }); - }); - }); - - it("should send the io cookie with httpOnly=true", function(done) { - listen({ cookieHttpOnly: true }, function(port) { - request - .get("http://localhost:%d/engine.io/default/".s(port)) - .query({ transport: "polling", b64: 1 }) - .end(function(err, res) { - expect(err).to.be(null); - var sid = res.text.match(/"sid":"([^"]+)"/)[1]; + const sid = res.text.match(/"sid":"([^"]+)"/)[1]; expect(res.headers["set-cookie"][0]).to.be( - "io=" + sid + "; Path=/; HttpOnly" + `io=${sid}; SameSite=Lax` ); done(); }); }); }); - it("should send the io cookie with httpOnly=true and path=false", function(done) { - listen({ cookieHttpOnly: true, cookiePath: false }, function(port) { + it("should send the io cookie with httpOnly=true", done => { + listen({ cookie: { httpOnly: true } }, port => { request .get("http://localhost:%d/engine.io/default/".s(port)) .query({ transport: "polling", b64: 1 }) .end(function(err, res) { expect(err).to.be(null); - var sid = res.text.match(/"sid":"([^"]+)"/)[1]; - expect(res.headers["set-cookie"][0]).to.be("io=" + sid); - done(); - }); - }); - }); - - it("should send the io cookie with httpOnly=false", function(done) { - listen({ cookieHttpOnly: false }, function(port) { - request - .get("http://localhost:%d/engine.io/default/".s(port)) - .query({ transport: "polling", b64: 1 }) - .end(function(err, res) { - expect(err).to.be(null); - var sid = res.text.match(/"sid":"([^"]+)"/)[1]; + const sid = res.text.match(/"sid":"([^"]+)"/)[1]; expect(res.headers["set-cookie"][0]).to.be( - "io=" + sid + "; Path=/" + `io=${sid}; Path=/; HttpOnly; SameSite=Lax` ); done(); }); }); }); - it("should send the io cookie with httpOnly not boolean", function(done) { - listen({ cookieHttpOnly: "no" }, function(port) { + it("should send the io cookie with sameSite=strict", done => { + listen({ cookie: { sameSite: "strict" } }, port => { request .get("http://localhost:%d/engine.io/default/".s(port)) .query({ transport: "polling", b64: 1 }) .end(function(err, res) { expect(err).to.be(null); - var sid = res.text.match(/"sid":"([^"]+)"/)[1]; + const sid = res.text.match(/"sid":"([^"]+)"/)[1]; expect(res.headers["set-cookie"][0]).to.be( - "io=" + sid + "; Path=/; HttpOnly" + `io=${sid}; Path=/; HttpOnly; SameSite=Strict` ); done(); }); }); }); - it("should not send the io cookie", function(done) { - listen({ cookie: false }, function(port) { + it("should send the io cookie with httpOnly=false", done => { + listen({ cookie: { httpOnly: false } }, port => { + request + .get("http://localhost:%d/engine.io/default/".s(port)) + .query({ transport: "polling", b64: 1 }) + .end(function(err, res) { + expect(err).to.be(null); + const sid = res.text.match(/"sid":"([^"]+)"/)[1]; + expect(res.headers["set-cookie"][0]).to.be( + `io=${sid}; Path=/; SameSite=Lax` + ); + done(); + }); + }); + }); + + it("should send the io cookie with httpOnly not boolean", done => { + listen({ cookie: { httpOnly: "no" } }, port => { + request + .get("http://localhost:%d/engine.io/default/".s(port)) + .query({ transport: "polling", b64: 1 }) + .end(function(err, res) { + expect(err).to.be(null); + const sid = res.text.match(/"sid":"([^"]+)"/)[1]; + expect(res.headers["set-cookie"][0]).to.be( + `io=${sid}; Path=/; HttpOnly; SameSite=Lax` + ); + done(); + }); + }); + }); + + it("should not send the io cookie", done => { + listen({ cookie: false }, port => { request .get("http://localhost:%d/engine.io/default/".s(port)) .query({ transport: "polling" }) @@ -2549,7 +2553,9 @@ describe("server", function() { } it("should compress by default", function(done) { - var engine = listen({ transports: ["polling"] }, function(port) { + var engine = listen({ cookie: true, transports: ["polling"] }, function( + port + ) { engine.on("connection", function(conn) { var buf = Buffer.allocUnsafe(1024); for (var i = 0; i < buf.length; i++) buf[i] = i % 0xff; @@ -2584,7 +2590,9 @@ describe("server", function() { }); it("should compress using deflate", function(done) { - var engine = listen({ transports: ["polling"] }, function(port) { + var engine = listen({ cookie: true, transports: ["polling"] }, function( + port + ) { engine.on("connection", function(conn) { var buf = Buffer.allocUnsafe(1024); for (var i = 0; i < buf.length; i++) buf[i] = i % 0xff; @@ -2620,7 +2628,11 @@ describe("server", function() { it("should set threshold", function(done) { var engine = listen( - { transports: ["polling"], httpCompression: { threshold: 0 } }, + { + cookie: true, + transports: ["polling"], + httpCompression: { threshold: 0 } + }, function(port) { engine.on("connection", function(conn) { var buf = Buffer.allocUnsafe(10); @@ -2654,7 +2666,7 @@ describe("server", function() { it("should disable compression", function(done) { var engine = listen( - { transports: ["polling"], httpCompression: false }, + { cookie: true, transports: ["polling"], httpCompression: false }, function(port) { engine.on("connection", function(conn) { var buf = Buffer.allocUnsafe(1024); @@ -2687,7 +2699,9 @@ describe("server", function() { }); it("should disable compression per message", function(done) { - var engine = listen({ transports: ["polling"] }, function(port) { + var engine = listen({ cookie: true, transports: ["polling"] }, function( + port + ) { engine.on("connection", function(conn) { var buf = Buffer.allocUnsafe(1024); for (var i = 0; i < buf.length; i++) buf[i] = i % 0xff; @@ -2718,7 +2732,9 @@ describe("server", function() { }); it("should not compress when the byte size is below threshold", function(done) { - var engine = listen({ transports: ["polling"] }, function(port) { + var engine = listen({ cookie: true, transports: ["polling"] }, function( + port + ) { engine.on("connection", function(conn) { var buf = Buffer.allocUnsafe(100); for (var i = 0; i < buf.length; i++) buf[i] = i % 0xff;