diff --git a/.travis.yml b/.travis.yml index 54ed12f..48a74eb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,7 @@ node_js: - "9.11" - "10.16" - "11.15" + - "12.11" sudo: false cache: directories: @@ -68,8 +69,8 @@ before_install: fi - | # restify framework - # - remove on Node.js < 0.10 - if [[ "$(cut -d. -f1 <<< "$TRAVIS_NODE_VERSION")" -eq 0 && "$(cut -d. -f2 <<< "$TRAVIS_NODE_VERSION")" -lt 10 ]]; then + # - remove on Node.js < 8 + if [[ "$(cut -d. -f1 <<< "$TRAVIS_NODE_VERSION")" -lt 8 ]]; then npm rm --silent --save-dev restify fi - | diff --git a/HISTORY.md b/HISTORY.md index 9eaa182..7b8bf48 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,3 +1,15 @@ +unreleased +========== + + * Fix check for default `secure` option behavior + * Fix `maxAge` option preventing cookie deletion + * Support `"none"` in `sameSite` option + * deps: depd@~2.0.0 + - Replace internal `eval` usage with `Function` constructor + - Use instance methods on `process` to check for listeners + * deps: keygrip@~1.1.0 + - Use `tsscmp` module for timing-safe signature verification + 0.7.3 / 2018-11-04 ================== diff --git a/index.js b/index.js index 9c468ae..3dd6728 100644 --- a/index.js +++ b/index.js @@ -26,7 +26,7 @@ var fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/; * RegExp to match Same-Site cookie attribute value. */ -var sameSiteRegExp = /^(?:lax|strict)$/i +var SAME_SITE_REGEXP = /^(?:lax|none|strict)$/i function Cookies(request, response, options) { if (!(this instanceof Cookies)) return new Cookies(request, response, options) @@ -94,8 +94,9 @@ Cookies.prototype.set = function(name, value, opts) { throw new Error('Cannot send secure cookie over unencrypted connection') } - cookie.secure = secure - if (opts && "secure" in opts) cookie.secure = opts.secure + cookie.secure = opts && opts.secure !== undefined + ? opts.secure + : secure if (opts && "secureProxy" in opts) { deprecate('"secureProxy" option; use "secure" option, provide "secure" to constructor if needed') @@ -125,8 +126,6 @@ function Cookie(name, value, attrs) { throw new TypeError('argument value is invalid'); } - value || (this.expires = new Date(0)) - this.name = name this.value = value || "" @@ -134,6 +133,11 @@ function Cookie(name, value, attrs) { this[name] = attrs[name] } + if (!this.value) { + this.expires = new Date(0) + this.maxAge = null + } + if (this.path && !fieldContentRegExp.test(this.path)) { throw new TypeError('option path is invalid'); } @@ -142,7 +146,7 @@ function Cookie(name, value, attrs) { throw new TypeError('option domain is invalid'); } - if (this.sameSite && this.sameSite !== true && !sameSiteRegExp.test(this.sameSite)) { + if (this.sameSite && this.sameSite !== true && !SAME_SITE_REGEXP.test(this.sameSite)) { throw new TypeError('option sameSite is invalid') } } diff --git a/package.json b/package.json index 93b2a39..b57f7d2 100644 --- a/package.json +++ b/package.json @@ -9,15 +9,15 @@ "license": "MIT", "repository": "pillarjs/cookies", "dependencies": { - "depd": "~1.1.2", - "keygrip": "~1.0.3" + "depd": "~2.0.0", + "keygrip": "~1.1.0" }, "devDependencies": { "eslint": "4.19.1", - "express": "4.16.4", - "mocha": "6.1.4", - "nyc": "14.0.0", - "restify": "6.4.0", + "express": "4.17.1", + "mocha": "6.2.1", + "nyc": "14.1.1", + "restify": "8.4.0", "supertest": "4.0.2" }, "files": [ diff --git a/test/cookie.js b/test/cookie.js index 347c2e8..b966d01 100644 --- a/test/cookie.js +++ b/test/cookie.js @@ -95,6 +95,13 @@ describe('new Cookie(name, value, [options])', function () { }) }) + describe('when set to "none"', function () { + it('should set "samesite=none" attribute in header', function () { + var cookie = new cookies.Cookie('foo', 'bar', { sameSite: 'none' }) + assert.equal(cookie.toHeader(), 'foo=bar; path=/; samesite=none; httponly') + }) + }) + describe('when set to "strict"', function () { it('should set "samesite=strict" attribute in header', function () { var cookie = new cookies.Cookie('foo', 'bar', { sameSite: 'strict' }) diff --git a/test/test.js b/test/test.js index d857b6f..39c066f 100644 --- a/test/test.js +++ b/test/test.js @@ -278,6 +278,16 @@ describe('new Cookies(req, res, [options])', function () { .expect(shouldSetCookieWithoutAttribute('foo', 'maxAge')) .end(done) }) + + it('should not affect cookie deletion', function (done) { + request(createServer(setCookieHandler('foo', null, { maxAge: 86400000 }))) + .get('/') + .expect(200) + .expect(shouldSetCookieCount(1)) + .expect(shouldSetCookieToValue('foo', '')) + .expect(shouldSetCookieWithAttributeAndValue('foo', 'expires', 'Thu, 01 Jan 1970 00:00:00 GMT')) + .end(done) + }) }) describe('"overwrite" option', function () { @@ -401,6 +411,45 @@ describe('new Cookies(req, res, [options])', function () { }) }) }) + + describe('when undefined', function () { + it('should set secure attribute on encrypted connection', function (done) { + var server = createSecureServer(setCookieHandler('foo', 'bar', { secure: undefined })) + + request(server) + .get('/') + .ca(server.cert) + .expect(200) + .expect(shouldSetCookieWithAttribute('foo', 'Secure')) + .end(done) + }) + + describe('with "secure: undefined" constructor option', function () { + it('should not set secure attribute on unencrypted connection', function (done) { + var opts = { secure: undefined } + + request(createServer(opts, setCookieHandler('foo', 'bar', { secure: undefined }))) + .get('/') + .expect(200) + .expect(shouldSetCookieWithoutAttribute('foo', 'Secure')) + .end(done) + }) + }) + + describe('with req.protocol === "https"', function () { + it('should set secure attribute on unencrypted connection', function (done) { + request(createServer(function (req, res, cookies) { + req.protocol = 'https' + cookies.set('foo', 'bar', { secure: undefined }) + res.end() + })) + .get('/') + .expect(200) + .expect(shouldSetCookieWithAttribute('foo', 'Secure')) + .end(done) + }) + }) + }) }) describe('"secureProxy" option', function () {