Skip to content

Commit

Permalink
Implement support for cookies with JSON values
Browse files Browse the repository at this point in the history
  • Loading branch information
jthomerson committed Feb 13, 2019
1 parent 5e93314 commit 29e505b
Show file tree
Hide file tree
Showing 5 changed files with 35 additions and 8 deletions.
16 changes: 14 additions & 2 deletions src/Request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export default class Request {
* Contains cookies sent by the request (key/value pairs). If the request contains no
* cookies, it defaults to `{}`.
*/
public readonly cookies: StringMap;
public readonly cookies: { [key: string]: any };

/**
* Contains the hostname derived from the `Host` HTTP header.
Expand Down Expand Up @@ -358,7 +358,19 @@ export default class Request {
return {};
}

return cookie.parse(cookieHeader);
const cookies = cookie.parse(cookieHeader);

_.each(cookies, (v, k): void => {
if (_.isString(v) && v.substring(0, 2) === 'j:') {
try {
cookies[k] = JSON.parse(v.substring(2));
} catch(e) {
// no-op
}
}
});

return cookies;
}

private _parseHostname(): string | undefined {
Expand Down
10 changes: 9 additions & 1 deletion src/Response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,13 +219,21 @@ export default class Response {
* Sets cookie `name` to `value`, optionally with the specified cookie options. See
* `CookieOpts`.
*
* Generally cookie values are strings, but you can also supply a JS object, which will
* be stringified with `JSON.stringify(userVal)` and prefixed with `j:`. This matches
* what Express does with response cookies and what their cookie parser middleware does
* with incoming (request) cookie headers.
*
* @see https://github.com/expressjs/cookie-parser/blob/1dc306b0ebe86ab98521811cc090740b4bef48e7/index.js#L84-L86
* @see https://github.com/expressjs/express/blob/dc538f6e810bd462c98ee7e6aae24c64d4b1da93/lib/response.js#L836-L838
*
* TODO: how does a user see the documentation for `CookieOpts`?
*
* @param name the name of the cookie
* @param userVal the value of the cookie
* @param userOpts the options (such as domain, path, etc)
*/
public cookie(name: string, userVal: string, userOpts?: CookieOpts): Response {
public cookie(name: string, userVal: any, userOpts?: CookieOpts): Response {
const opts = _.extend({ path: '/' }, userOpts) as CookieOpts,
value = (_.isObject(userVal) ? `j:${JSON.stringify(userVal)}` : String(userVal));

Expand Down
3 changes: 3 additions & 0 deletions tests/Request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ describe('Request', () => {
_.each(allRequestTypes, (req) => {
expect(req.cookies.uid).to.eql('abc');
expect(req.cookies.baz).to.eql('foo[a]');
expect(req.cookies.obj).to.eql({ abc: 123 });
expect(req.cookies.onechar).to.eql('j');
expect(req.cookies.bad).to.eql('j:{a}');
});
});

Expand Down
6 changes: 5 additions & 1 deletion tests/Response.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ describe('Request', () => {
});

describe('cookie', () => {
const test = (name: string, val: string, opts: CookieOpts | undefined, expectedHeader: string): void => {
const test = (name: string, val: any, opts: CookieOpts | undefined, expectedHeader: string): void => {
it(`sets cookie headers correctly - ${expectedHeader}`, () => {
expect(sampleResp.getHeaders()).to.eql({});
sampleResp.cookie(name, val, opts);
Expand All @@ -294,6 +294,10 @@ describe('Request', () => {
test('foo', 'bar', { path: '/', httpOnly: true }, 'foo=bar; Path=/; HttpOnly');
test('foo', 'url encoded', { path: '/', httpOnly: true }, 'foo=url%20encoded; Path=/; HttpOnly');
test('foo', 'bar baz', { secure: true, domain: 'example.com' }, 'foo=bar%20baz; Domain=example.com; Path=/; Secure');
test('foo', { abc: 123 }, undefined, `foo=${encodeURIComponent('j:{"abc":123}')}; Path=/`);
test('foo', { abc: 123 }, { path: '/foo', httpOnly: true }, `foo=${encodeURIComponent('j:{"abc":123}')}; Path=/foo; HttpOnly`);
test('foo', [ 'a', 'b' ], undefined, `foo=${encodeURIComponent('j:["a","b"]')}; Path=/`);
test('foo', [ 'a', 'b' ], { path: '/foo', httpOnly: true }, `foo=${encodeURIComponent('j:["a","b"]')}; Path=/foo; HttpOnly`);

const encoder = (v: string): string => { return v.toUpperCase(); };

Expand Down
8 changes: 4 additions & 4 deletions tests/samples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export const apiGatewayRequest = (): APIGatewayRequestEvent => {
'X-Forwarded-Port': '443',
'X-Forwarded-Proto': 'https',
Referer: 'https://en.wikipedia.org/wiki/HTTP_referer',
Cookie: 'uid=abc; ga=1234; foo=bar; baz=foo%5Ba%5D',
Cookie: 'uid=abc; ga=1234; foo=bar; baz=foo%5Ba%5D; obj=j%3A%7B%22abc%22%3A123%7D; onechar=j; bad=j%3A%7Ba%7D',
},
multiValueHeaders: {
Accept: [ '*/*' ],
Expand All @@ -100,7 +100,7 @@ export const apiGatewayRequest = (): APIGatewayRequestEvent => {
'X-Forwarded-Port': [ '443' ],
'X-Forwarded-Proto': [ 'https' ],
Referer: [ 'https://en.wikipedia.org/wiki/HTTP_referer' ],
Cookie: [ 'uid=abc; ga=1234; foo=bar; baz=foo%5Ba%5D' ],
Cookie: [ 'uid=abc; ga=1234; foo=bar; baz=foo%5Ba%5D; obj=j%3A%7B%22abc%22%3A123%7D; onechar=j; bad=j%3A%7Ba%7D' ],
},
queryStringParameters: {
'foo[a]': 'bar b',
Expand Down Expand Up @@ -151,7 +151,7 @@ export const albRequest = (): ApplicationLoadBalancerRequestEvent => {
'x-forwarded-proto': 'http',
// Using "referer" (one "r") on this request, and "referrer" (two) below
referer: 'https://en.wikipedia.org/wiki/HTTP_referer',
cookie: 'uid=abc; ga=1234; foo=bar; baz=foo%5Ba%5D',
cookie: 'uid=abc; ga=1234; foo=bar; baz=foo%5Ba%5D; obj=j%3A%7B%22abc%22%3A123%7D; onechar=j; bad=j%3A%7Ba%7D',
},
});
};
Expand All @@ -174,7 +174,7 @@ export const albMultiValHeadersRequest = (): ApplicationLoadBalancerRequestEvent
'x-forwarded-proto': [ 'http' ],
// Using "referrer" (two "r"s) on this request, and "referer" (one) above
referrer: [ 'https://en.wikipedia.org/wiki/HTTP_referer' ],
cookie: [ 'uid=abc; ga=1234; foo=bar; baz=foo%5Ba%5D' ],
cookie: [ 'uid=abc; ga=1234; foo=bar; baz=foo%5Ba%5D; obj=j%3A%7B%22abc%22%3A123%7D; onechar=j; bad=j%3A%7Ba%7D' ],
},
});
};

0 comments on commit 29e505b

Please sign in to comment.