UNPKG

3.58 kBJavaScriptView Raw
1/*!
2 * Connect - cookieSession
3 * Copyright(c) 2011 Sencha Inc.
4 * MIT Licensed
5 */
6
7/**
8 * Module dependencies.
9 */
10
11var cookieParser = require('cookie-parser');
12var parseUrl = require('parseurl');
13var Cookie = require('express-session').Cookie
14 , debug = require('debug')('connect:cookieSession')
15 , signature = require('cookie-signature')
16 , onHeaders = require('on-headers')
17 , url = require('url');
18
19/**
20 * Cookie Session:
21 *
22 * Cookie session middleware.
23 *
24 * var app = connect();
25 * app.use(connect.cookieParser());
26 * app.use(connect.cookieSession({ secret: 'tobo!', cookie: { maxAge: 60 * 60 * 1000 }}));
27 *
28 * Options:
29 *
30 * - `key` cookie name defaulting to `connect.sess`
31 * - `secret` prevents cookie tampering
32 * - `cookie` session cookie settings, defaulting to `{ path: '/', httpOnly: true, maxAge: null }`
33 * - `proxy` trust the reverse proxy when setting secure cookies (via "x-forwarded-proto")
34 *
35 * Clearing sessions:
36 *
37 * To clear the session simply set its value to `null`,
38 * `cookieSession()` will then respond with a 1970 Set-Cookie.
39 *
40 * req.session = null;
41 *
42 * If you are interested in more sophisticated solutions,
43 * you may be interested in:
44 *
45 * - [client-sessions](https://github.com/mozilla/node-client-sessions)
46 *
47 * @param {Object} options
48 * @return {Function}
49 * @api public
50 */
51
52module.exports = function cookieSession(options){
53 // TODO: utilize Session/Cookie to unify API
54 options = options || {};
55 var key = options.key || 'connect.sess'
56 , trustProxy = options.proxy;
57
58 return function cookieSession(req, res, next) {
59
60 // req.secret is for backwards compatibility
61 var secret = options.secret || req.secret;
62 if (!secret) throw new Error('`secret` option required for cookie sessions');
63
64 // default session
65 req.session = {};
66 var cookie = req.session.cookie = new Cookie(options.cookie);
67
68 // pathname mismatch
69 var originalPath = parseUrl.original(req).pathname;
70 if (0 != originalPath.indexOf(cookie.path)) return next();
71
72 // cookieParser secret
73 if (!options.secret && req.secret) {
74 req.session = req.signedCookies[key] || {};
75 req.session.cookie = cookie;
76 } else {
77 // TODO: refactor
78 var rawCookie = req.cookies[key];
79 if (rawCookie) {
80 var unsigned = cookieParser.signedCookie(rawCookie, secret);
81 if (unsigned) {
82 var original = unsigned;
83 req.session = cookieParser.JSONCookie(unsigned) || {};
84 req.session.cookie = cookie;
85 }
86 }
87 }
88
89 onHeaders(res, function(){
90 // removed
91 if (!req.session) {
92 debug('clear session');
93 cookie.expires = new Date(0);
94 res.setHeader('Set-Cookie', cookie.serialize(key, ''));
95 return;
96 }
97
98 delete req.session.cookie;
99
100 // check security
101 var proto = (req.headers['x-forwarded-proto'] || '').toLowerCase()
102 , tls = req.connection.encrypted || (trustProxy && 'https' == proto.split(/\s*,\s*/)[0]);
103
104 // only send secure cookies via https
105 if (cookie.secure && !tls) return debug('not secured');
106
107 // serialize
108 debug('serializing %j', req.session);
109 var val = 'j:' + JSON.stringify(req.session);
110
111 // compare data, no need to set-cookie if unchanged
112 if (original == val) return debug('unmodified session');
113
114 // set-cookie
115 val = 's:' + signature.sign(val, secret);
116 val = cookie.serialize(key, val);
117 debug('set-cookie %j', cookie);
118 res.setHeader('Set-Cookie', val);
119 });
120
121 next();
122 };
123};