diff --git a/.npmignore b/.npmignore deleted file mode 100644 index ac0bfb9..0000000 --- a/.npmignore +++ /dev/null @@ -1,2 +0,0 @@ -test/ -.travis.yml diff --git a/History.md b/History.md index 28be558..2ebf063 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,13 @@ +0.6.x +===== + + * Add `secure` constructor option for secure connection checking + * Change constructor to signature `new Cookies(req, res, [options])` + - Replace `new Cookies(req, res, key)` with `new Cookies(req, res, {'keys': keys})` + * Change prototype construction for proper "constructor" property + * Deprecate `secureProxy` option in `.set`; use `secure` option instead + - If `secure: true` throws even over SSL, use the `secure` constructor option + 0.5.1 / 2014-07-27 ================== diff --git a/README.md b/README.md index c6e4c61..f53ea4e 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ Cookies ======= -[![NPM Version](https://badge.fury.io/js/cookies.svg)](https://badge.fury.io/js/cookies) -[![Build Status](https://travis-ci.org/pillarjs/cookies.svg?branch=master)](https://travis-ci.org/pillarjs/cookies) +[![NPM Version][npm-image]][npm-url] +[![NPM Downloads][downloads-image]][downloads-url] +[![Node.js Version][node-version-image]][node-version-url] +[![Build Status][travis-image]][travis-url] Cookies is a [node.js](http://nodejs.org/) module for getting and setting HTTP(S) cookies. Cookies can be signed to prevent tampering, using [Keygrip](https://www.npmjs.com/package/keygrip). It can be used with the built-in node.js HTTP library, or as Connect/Express middleware. @@ -26,9 +28,13 @@ Cookies is a [node.js](http://nodejs.org/) module for getting and setting HTTP(S ## API -### cookies = new Cookies( request, response, [ keys ] ) +### cookies = new Cookies( request, response, [ options ] ) -This creates a cookie jar corresponding to the current _request_ and _response_. A [Keygrip](https://www.npmjs.com/package/keygrip) object or an array of keys can optionally be passed as the third argument _keygrip_ to enable cryptographic signing based on SHA1 HMAC, using rotated credentials. +This creates a cookie jar corresponding to the current _request_ and _response_, additionally passing an object _options_. + +A [Keygrip](https://www.npmjs.com/package/keygrip) object or an array of keys can optionally be passed as _options.keys_ to enable cryptographic signing based on SHA1 HMAC, using rotated credentials. + +A Boolean can optionally be passed as _options.secure_ to explicitally specify if the connection is secure, rather than this module examining _request_. Note that since this only saves parameters without any other processing, it is very lightweight. Cookies are only parsed on demand when they are accessed. @@ -61,7 +67,6 @@ If the _options_ object is provided, it will be used to generate the outbound co * `path`: a string indicating the path of the cookie (`/` by default). * `domain`: a string indicating the domain of the cookie (no default). * `secure`: a boolean indicating whether the cookie is only to be sent over HTTPS (`false` by default for HTTP, `true` by default for HTTPS). -* `secureProxy`: a boolean indicating whether the cookie is only to be sent over HTTPS (use this if you handle SSL not in your node process). * `httpOnly`: a boolean indicating whether the cookie is only to be sent over HTTP(S), and not made available to client JavaScript (`true` by default). * `signed`: a boolean indicating whether the cookie is to be signed (`false` by default). If this is true, another cookie of the same name with the `.sig` suffix appended will also be sent, with a 27-byte url-safe base64 SHA1 value representing the hash of _cookie-name_=_cookie-value_ against the first [Keygrip](https://www.npmjs.com/package/keygrip) key. This signature key is used to detect tampering the next time a cookie is received. * `overwrite`: a boolean indicating whether to overwrite previously set cookies of the same name (`false` by default). If this is true, all cookies set during the same request with the same name (regardless of path or domain) are filtered out of the Set-Cookie header when setting this cookie. @@ -73,7 +78,7 @@ var http = require( "http" ) var Cookies = require( "cookies" ) server = http.createServer( function( req, res ) { - var cookies = new Cookies( req, res, keys ) + var cookies = new Cookies( req, res, { "keys": keys } ) , unsigned, signed, tampered if ( req.url == "/set" ) { @@ -119,3 +124,12 @@ Copyright Copyright (c) 2014 Jed Schmidt. See LICENSE.txt for details. Send any questions or comments [here](http://twitter.com/jedschmidt). + +[npm-image]: https://img.shields.io/npm/v/cookies.svg +[npm-url]: https://npmjs.org/package/cookies +[downloads-image]: https://img.shields.io/npm/dm/cookies.svg +[downloads-url]: https://npmjs.org/package/cookies +[node-version-image]: https://img.shields.io/node/v/cookies.svg +[node-version-url]: https://nodejs.org/en/download/ +[travis-image]: https://img.shields.io/travis/pillarjs/cookies/master.svg +[travis-url]: https://travis-ci.org/pillarjs/cookies diff --git a/lib/cookies.js b/lib/cookies.js index 201b93c..871abf0 100644 --- a/lib/cookies.js +++ b/lib/cookies.js @@ -1,3 +1,4 @@ +var deprecate = require('depd')('cookies') var Keygrip = require('keygrip') var http = require('http') var cache = {} @@ -12,82 +13,93 @@ var cache = {} var fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/; -function Cookies(request, response, keys) { - if (!(this instanceof Cookies)) return new Cookies(request, response, keys) +function Cookies(request, response, options) { + if (!(this instanceof Cookies)) return new Cookies(request, response, options) + this.secure = undefined this.request = request this.response = response - if (keys) { - // array of key strings - if (Array.isArray(keys)) - this.keys = new Keygrip(keys) - // any keygrip constructor to allow different versions - else if (keys.constructor && keys.constructor.name === 'Keygrip') - this.keys = keys + + if (options) { + if (Array.isArray(options)) { + // array of key strings + deprecate('"keys" argument; provide using options {"key": [...]}') + this.keys = new Keygrip(options) + } else if (options.constructor && options.constructor.name === 'Keygrip') { + // any keygrip constructor to allow different versions + deprecate('"keys" argument; provide using options {"key": keygrip}') + this.keys = options + } else { + this.keys = options.keys + this.secure = options.secure + } } } -Cookies.prototype = { - get: function(name, opts) { - var sigName = name + ".sig" - , header, match, value, remote, data, index - , signed = opts && opts.signed !== undefined ? opts.signed : !!this.keys +Cookies.prototype.get = function(name, opts) { + var sigName = name + ".sig" + , header, match, value, remote, data, index + , signed = opts && opts.signed !== undefined ? opts.signed : !!this.keys - header = this.request.headers["cookie"] - if (!header) return + header = this.request.headers["cookie"] + if (!header) return - match = header.match(getPattern(name)) - if (!match) return + match = header.match(getPattern(name)) + if (!match) return - value = match[1] - if (!opts || !signed) return value + value = match[1] + if (!opts || !signed) return value - remote = this.get(sigName) - if (!remote) return + remote = this.get(sigName) + if (!remote) return - data = name + "=" + value - if (!this.keys) throw new Error('.keys required for signed cookies'); - index = this.keys.index(data, remote) + data = name + "=" + value + if (!this.keys) throw new Error('.keys required for signed cookies'); + index = this.keys.index(data, remote) - if (index < 0) { - this.set(sigName, null, {path: "/", signed: false }) - } else { - index && this.set(sigName, this.keys.sign(data), { signed: false }) - return value - } - }, + if (index < 0) { + this.set(sigName, null, {path: "/", signed: false }) + } else { + index && this.set(sigName, this.keys.sign(data), { signed: false }) + return value + } +}; - set: function(name, value, opts) { - var res = this.response - , req = this.request - , headers = res.getHeader("Set-Cookie") || [] - , secure = req.protocol === 'https' || req.connection.encrypted - , cookie = new Cookie(name, value, opts) - , signed = opts && opts.signed !== undefined ? opts.signed : !!this.keys +Cookies.prototype.set = function(name, value, opts) { + var res = this.response + , req = this.request + , headers = res.getHeader("Set-Cookie") || [] + , secure = this.secure !== undefined ? !!this.secure : req.protocol === 'https' || req.connection.encrypted + , cookie = new Cookie(name, value, opts) + , signed = opts && opts.signed !== undefined ? opts.signed : !!this.keys - if (typeof headers == "string") headers = [headers] + if (typeof headers == "string") headers = [headers] - if (!secure && opts && opts.secure) { - throw new Error('Cannot send secure cookie over unencrypted connection') - } + if (!secure && opts && opts.secure) { + throw new Error('Cannot send secure cookie over unencrypted connection') + } - cookie.secure = secure - if (opts && "secure" in opts) cookie.secure = opts.secure - if (opts && "secureProxy" in opts) cookie.secure = opts.secureProxy - headers = pushCookie(headers, cookie) + cookie.secure = secure + if (opts && "secure" in opts) cookie.secure = opts.secure - if (opts && signed) { - if (!this.keys) throw new Error('.keys required for signed cookies'); - cookie.value = this.keys.sign(cookie.toString()) - cookie.name += ".sig" - headers = pushCookie(headers, cookie) - } + if (opts && "secureProxy" in opts) { + deprecate('"secureProxy" option; use "secure" option, provide "secure" to constructor if needed') + cookie.secure = opts.secureProxy + } + + headers = pushCookie(headers, cookie) - var setHeader = res.set ? http.OutgoingMessage.prototype.setHeader : res.setHeader - setHeader.call(res, 'Set-Cookie', headers) - return this + if (opts && signed) { + if (!this.keys) throw new Error('.keys required for signed cookies'); + cookie.value = this.keys.sign(cookie.toString()) + cookie.name += ".sig" + headers = pushCookie(headers, cookie) } -} + + var setHeader = res.set ? http.OutgoingMessage.prototype.setHeader : res.setHeader + setHeader.call(res, 'Set-Cookie', headers) + return this +}; function Cookie(name, value, attrs) { if (!fieldContentRegExp.test(name)) { @@ -116,32 +128,30 @@ function Cookie(name, value, attrs) { } } -Cookie.prototype = { - path: "/", - expires: undefined, - domain: undefined, - httpOnly: true, - secure: false, - overwrite: false, +Cookie.prototype.path = "/"; +Cookie.prototype.expires = undefined; +Cookie.prototype.domain = undefined; +Cookie.prototype.httpOnly = true; +Cookie.prototype.secure = false; +Cookie.prototype.overwrite = false; - toString: function() { - return this.name + "=" + this.value - }, +Cookie.prototype.toString = function() { + return this.name + "=" + this.value +}; - toHeader: function() { - var header = this.toString() +Cookie.prototype.toHeader = function() { + var header = this.toString() - if (this.maxAge) this.expires = new Date(Date.now() + this.maxAge); + if (this.maxAge) this.expires = new Date(Date.now() + this.maxAge); - if (this.path ) header += "; path=" + this.path - if (this.expires ) header += "; expires=" + this.expires.toUTCString() - if (this.domain ) header += "; domain=" + this.domain - if (this.secure ) header += "; secure" - if (this.httpOnly ) header += "; httponly" + if (this.path ) header += "; path=" + this.path + if (this.expires ) header += "; expires=" + this.expires.toUTCString() + if (this.domain ) header += "; domain=" + this.domain + if (this.secure ) header += "; secure" + if (this.httpOnly ) header += "; httponly" - return header - } -} + return header +}; // back-compat so maxage mirrors maxAge Object.defineProperty(Cookie.prototype, 'maxage', { @@ -171,7 +181,10 @@ function pushCookie(cookies, cookie) { Cookies.connect = Cookies.express = function(keys) { return function(req, res, next) { - req.cookies = res.cookies = new Cookies(req, res, keys) + req.cookies = res.cookies = new Cookies(req, res, { + keys: keys + }) + next() } } diff --git a/package.json b/package.json index 284727c..b578902 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "Cookies, optionally signed using Keygrip.", "main": "./lib/cookies", "dependencies": { + "depd": "~1.1.0", "keygrip": "~1.0.0" }, "devDependencies": { @@ -12,13 +13,19 @@ "restify": "2.8.1", "supertest": "1.1.0" }, - "engines": { - "node": ">= 0.8.0" - }, "license": "MIT", "author": "Jed Schmidt