Skip to content

Commit

Permalink
feat: Support cookie.parse()'s decode option
Browse files Browse the repository at this point in the history
  • Loading branch information
nwoltman committed Jun 20, 2019
1 parent 80e75ee commit c88baf4
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 29 deletions.
67 changes: 49 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,8 @@
## Installation

```sh
# npm
npm install @medley/cookie --save

# yarn
# or
yarn add @medley/cookie
```

Expand All @@ -25,22 +23,55 @@ yarn add @medley/cookie
const medley = require('@medley/medley');
const app = medley();

app.register(require('@medley/cookie'));

app.get('/', (req, res) => {
if (req.cookies.foo === undefined) {
res.setCookie('foo', 'bar');
res.send('cookie set');
} else {
res.send(`cookie: foo = ${req.cookies.foo}`);
}
});
```

### Plugin Options

#### `secret`

Type: `string`

Used for signing/unsigning cookies.

```js
app.register(require('@medley/cookie'), {
secret: 'to everybody', // `secret` should be a long, random string
});

app.get('/', (req, res) => {
if (req.cookies.foo === undefined) {
res.setCookie('foo', 'bar');
const fooCookie = res.signCookie('foo-value')
res.setCookie('foo', foo);
res.send('cookie set');
} else {
res.send('foo = ' + req.cookies.foo);
// Sends: 'foo = bar'
const fooCookie = req.unsignCookie(req.cookies.foo);
res.send(`foo = ${fooCookie}`);
}
});
```

The cookie plugin takes one option called `secret` which is used for signing/unsigning cookies.
#### `decode`

Type: `function`<br>
Default: [`decodeURIComponent`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent)

A function that will be used to decode each cookies' value.

```js
app.register(require('@medley/cookie'), {
decode: require('safe-decode-uri-component'),
});
```


## API
Expand All @@ -57,12 +88,11 @@ The cookie plugin takes one option called `secret` which is used for signing/uns
An object of parsed cookies.

```js
req.cookies // { cookieName: 'cookieValue', foo: 'bar' }
app.get('/', (req, res) => {
req.cookies // { cookieName: 'cookieValue', foo: 'bar' }
});
```

Note that cookies are parsed in an `onRequest` hook, so `req.cookies` will not
be available in any `onRequest` hooks added before registering this plugin.

### `req.unsignCookie(value)`

+ `value` *(string)* - A cookie value.
Expand All @@ -88,16 +118,17 @@ Signs a cookie value.
```js
const signedValue = res.signCookie('hello');
console.log(signedValue); // 'hello.DGDUkGlIkCzPz+C0B064FNgHdEjox7ch8tOBGslZ5QI'

res.setCookie('greeting', signedValue);
```

The signed value will be different depending on the `secret` option used when
The signed value will be different depending on the [`secret`](#secret) option used when
registering the plugin.

### `res.setCookie(name, value[, options])`

+ `name` *(string)* - The name of the cookie.
+ `value` *(string)* - A cookie value.
+ `value` *(string)* - The cookie value.
+ `options` *(object)* - See the [options](#options) below.
+ chainable

Expand Down Expand Up @@ -128,13 +159,13 @@ res.setCookie('cross_domain_cookie', 'value!', {
| Property | Type | Description |
|----------|------|-------------|
| `domain` | *string* | Domain name for the cookie.
| `encode` | *function* | A synchronous function used for encoding the cookie value. Default: [`encodeURIComponent`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent).
| `encode` | *function* | A synchronous function used for encoding the cookie value.<br>Default: [`encodeURIComponent`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent)
| `expires` | *Date* | Expiry date of the cookie in GMT. If not specified (and `maxAge` is not specified), a session cookie is created.
| `httpOnly` | *boolean* | Flags the cookie to be accessible only by the web server (and not by JavaScript in the browser). Default: `false`.
| `httpOnly` | *boolean* | Flags the cookie to be accessible only by the web server (and not by JavaScript in the browser).<br>Default: `false`
| `maxAge` | *number* | Convenient option for setting the expiry time relative to the current time in **seconds**. If not specified (and `expires` is not specified), a session cookie is created.
| `path` | *string* | URL path prefix at which the cookie will be available. Default: `'/'`.
| `sameSite` | *string* | Value of the [*SameSite*](https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00#section-4.1.1) `Set-Cookie` attribute (either `'strict'` or `'lax'`).
| `secure` | *boolean* | Flags the cookie to be used with HTTPS only. Default: `false`.
| `path` | *string* | URL path prefix at which the cookie will be available.<br>Default: `'/'`
| `sameSite` | *string*\|*boolean* | Value for the [`SameSite`](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-5.3.7) `Set-Cookie` attribute. Can be any of the values supported by the [`cookie` module](https://github.com/jshttp/cookie#samesite): `true`, `false`, `'strict'`, `'lax'`, `'none'`.
| `secure` | *boolean* | Flags the cookie to be used with HTTPS only.<br>Default: `false`

### `res.clearCookie(name[, options])`

Expand Down
22 changes: 11 additions & 11 deletions cookie.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@
const {parse, serialize} = require('cookie');
const {sign, unsign} = require('cookie-signature');

function cookie(app, {secret} = {}) {
function cookie(app, {decode, secret} = {}) {
app.decorateRequest('cookies', null);

app.addHook('onRequest', onRequest);
const parseOpts = {decode};

app.decorateResponse('setCookie', setCookie);
app.decorateResponse('clearCookie', clearCookie);
app.addHook('onRequest', function onRequest(req, res, next) {
const cookieHeader = req.headers.cookie;

req.cookies = cookieHeader === undefined ? {} : parse(cookieHeader, parseOpts);

next();
});

app.decorateRequest('unsignCookie', function unsignCookie(value) {
return unsign(value, secret);
Expand All @@ -18,14 +23,9 @@ function cookie(app, {secret} = {}) {
app.decorateResponse('signCookie', function signCookie(value) {
return sign(value, secret);
});
}

function onRequest(req, res, next) {
const cookieHeader = req.headers.cookie;

req.cookies = cookieHeader === undefined ? {} : parse(cookieHeader);

next();
app.decorateResponse('setCookie', setCookie);
app.decorateResponse('clearCookie', clearCookie);
}

function setCookie(name, value, options) {
Expand Down
23 changes: 23 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,29 @@ describe('req.cookies', () => {
assert.strictEqual(res.body, 'success');
});

it('should support parsed cookies using the decode option', async () => {
const app = makeApp({
decode: str => str.replace(/-/g, '_'),
});

app.get('/', (req, res) => {
assert.deepStrictEqual(req.cookies, {
foo: 'bar_buzz',
zab: '_a%20buzz_buzz',
});

res.send('success');
});

const res = await app.request({
url: '/',
headers: {
Cookie: 'foo=bar-buzz; zab=-a%20buzz-buzz',
},
});
assert.strictEqual(res.body, 'success');
});

});


Expand Down

0 comments on commit c88baf4

Please sign in to comment.