Skip to content

Commit

Permalink
feat: disable cookie by default and add sameSite attribute
Browse files Browse the repository at this point in the history
The cookie might be used for sticky-session, but is not mandatory so it
makes sense to disable it by default.

The change also add a SameSite=Lax attribute by default.

Breaking change: the syntax has changed from

```
new Server({
  cookieName: "test",
  cookieHttpOnly: false,
  cookiePath: "/custom"
})
```

to

```
new Server({
  cookie: {
    name: "test",
    httpOnly: false,
    path: "/custom"
  }
})
```

All other options (domain, maxAge, sameSite, ...) are now supported.

Reference: https://github.com/jshttp/cookie#options-1
  • Loading branch information
darrachequesne committed Feb 5, 2020
1 parent 31ff875 commit a374471
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 56 deletions.
9 changes: 3 additions & 6 deletions README.md
Expand Up @@ -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`
Expand Down
31 changes: 21 additions & 10 deletions lib/server.js
Expand Up @@ -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];
Expand Down Expand Up @@ -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
);
});
}

Expand Down
96 changes: 56 additions & 40 deletions test/server.js
Expand Up @@ -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);
const sid = res.text.match(/"sid":"([^"]+)"/)[1];
expect(res.headers["set-cookie"][0]).to.be(
`io=${sid}; SameSite=Lax`
);
done();
});
});
});

it("should send the io cookie with httpOnly=true", function(done) {
listen({ cookieHttpOnly: true }, 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];
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 with httpOnly=true and path=false", function(done) {
listen({ cookieHttpOnly: true, cookiePath: false }, 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];
expect(res.headers["set-cookie"][0]).to.be("io=" + sid);
const sid = res.text.match(/"sid":"([^"]+)"/)[1];
expect(res.headers["set-cookie"][0]).to.be(
`io=${sid}; Path=/; HttpOnly; SameSite=Strict`
);
done();
});
});
});

it("should send the io cookie with httpOnly=false", function(done) {
listen({ cookieHttpOnly: 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);
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=/; 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 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);
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 not send the io cookie", function(done) {
listen({ cookie: false }, function(port) {
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" })
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit a374471

Please sign in to comment.