]> git.cworth.org Git - obsolete/notmuch-web/blob - node_modules/express/node_modules/connect/lib/middleware/session.js
Install the "express" node module via npm
[obsolete/notmuch-web] / node_modules / express / node_modules / connect / lib / middleware / session.js
1
2 /*!
3  * Connect - session
4  * Copyright(c) 2010 Sencha Inc.
5  * Copyright(c) 2011 TJ Holowaychuk
6  * MIT Licensed
7  */
8
9 /**
10  * Module dependencies.
11  */
12
13 var Session = require('./session/session')
14   , MemoryStore = require('./session/memory')
15   , Cookie = require('./session/cookie')
16   , Store = require('./session/store')
17   , utils = require('./../utils')
18   , parse = require('url').parse
19   , crypto = require('crypto');
20
21 // environment
22
23 var env = process.env.NODE_ENV;
24
25 /**
26  * Expose the middleware.
27  */
28
29 exports = module.exports = session;
30
31 /**
32  * Expose constructors.
33  */
34
35 exports.Store = Store;
36 exports.Cookie = Cookie;
37 exports.Session = Session;
38 exports.MemoryStore = MemoryStore;
39
40 /**
41  * Warning message for `MemoryStore` usage in production.
42  */
43
44 var warning = 'Warning: connection.session() MemoryStore is not\n'
45   + 'designed for a production environment, as it will leak\n'
46   + 'memory, and obviously only work within a single process.';
47
48 /**
49  * Default finger-printing function.
50  */
51
52 function defaultFingerprint(req) {
53   return req.headers['user-agent'] || '';
54 };
55
56 /**
57  * Paths to ignore, defaulting to `/favicon.ico`.
58  */
59
60 exports.ignore = ['/favicon.ico'];
61
62 /**
63  * Setup session store with the given `options`.
64  *
65  * Session data is _not_ saved in the cookie itself, however
66  * cookies are used, so we must use the [cookieParser()](middleware-cookieParser.html)
67  * middleware _before_ `session()`.
68  *
69  * Examples:
70  *
71  *     connect.createServer(
72  *         connect.cookieParser()
73  *       , connect.session({ secret: 'keyboard cat' })
74  *     );
75  *
76  * Options:
77  *
78  *   - `key`           cookie name defaulting to `connect.sid`
79  *   - `store`         Session store instance
80  *   - `fingerprint`   Custom fingerprint generating function
81  *   - `cookie`        Session cookie settings, defaulting to `{ path: '/', httpOnly: true, maxAge: 14400000 }`
82  *   - `secret`        Secret string used to compute hash
83  *
84  * Ignore Paths:
85  *
86  *  By default `/favicon.ico` is the only ignored path, all others
87  *  will utilize sessions, to manipulate the paths ignored, use
88  * `connect.session.ignore.push('/my/path')`. This works for _full_
89  *  pathnames only, not segments nor substrings.
90  *
91  *     connect.session.ignore.push('/robots.txt');
92  *
93  * ## req.session
94  *
95  *  To store or access session data, simply use the request property `req.session`,
96  *  which is (generally) serialized as JSON by the store, so nested objects 
97  *  are typically fine. For example below is a user-specific view counter:
98  *
99  *       connect(
100  *           connect.cookieParser()
101  *         , connect.session({ secret: 'keyboard cat', cookie: { maxAge: 60000 }})
102  *         , connect.favicon()
103  *         , function(req, res, next){
104  *           var sess = req.session;
105  *           if (sess.views) {
106  *             res.setHeader('Content-Type', 'text/html');
107  *             res.write('<p>views: ' + sess.views + '</p>');
108  *             res.write('<p>expires in: ' + (sess.cookie.maxAge / 1000) + 's</p>');
109  *             res.end();
110  *             sess.views++;
111  *           } else {
112  *             sess.views = 1;
113  *             res.end('welcome to the session demo. refresh!');
114  *           }
115  *         }
116  *       ).listen(3000);
117  *
118  * ## Session#regenerate()
119  *
120  *  To regenerate the session simply invoke the method, once complete
121  *  a new SID and `Session` instance will be initialized at `req.session`.
122  *
123  *      req.session.regenerate(function(err){
124  *        // will have a new session here
125  *      });
126  *
127  * ## Session#destroy()
128  *
129  *  Destroys the session, removing `req.session`, will be re-generated next request.
130  *
131  *      req.session.destroy(function(err){
132  *        // cannot access session here
133  *      });
134  *
135  * ## Session#touch()
136  *
137  *   Updates the `.maxAge`, and `.lastAccess` properties. Typically this is
138  *   not necessary to call, as the session middleware does this for you.
139  *
140  * ## Session#cookie
141  *
142  *  Each session has a unique cookie object accompany it. This allows
143  *  you to alter the session cookie per visitor. For example we can
144  *  set `req.session.cookie.expires` to `false` to enable the cookie
145  *  to remain for only the duration of the user-agent.
146  *
147  * ## Session#maxAge
148  *
149  *  Alternatively `req.session.cookie.maxAge` will return the time
150  *  remaining in milliseconds, which we may also re-assign a new value
151  *  to adjust the `.expires` property appropriately. The following
152  *  are essentially equivalent
153  *
154  *     var hour = 3600000;
155  *     req.session.cookie.expires = new Date(Date.now() + hour);
156  *     req.session.cookie.maxAge = hour;
157  *
158  * For example when `maxAge` is set to `60000` (one minute), and 30 seconds
159  * has elapsed it will return `30000` until the current request has completed,
160  * at which time `req.session.touch()` is called to update `req.session.lastAccess`,
161  * and reset `req.session.maxAge` to its original value.
162  *
163  *     req.session.cookie.maxAge;
164  *     // => 30000
165  *
166  * Session Store Implementation:
167  *
168  * Every session store _must_ implement the following methods
169  *
170  *    - `.get(sid, callback)`
171  *    - `.set(sid, session, callback)`
172  *    - `.destroy(sid, callback)`
173  *
174  * Recommended methods include, but are not limited to:
175  *
176  *    - `.length(callback)`
177  *    - `.clear(callback)`
178  *
179  * For an example implementation view the [connect-redis](http://github.com/visionmedia/connect-redis) repo.
180  *
181  * @param {Object} options
182  * @return {Function}
183  * @api public
184  */
185
186 function session(options){
187   var options = options || {}
188     , key = options.key || 'connect.sid'
189     , secret = options.secret
190     , store = options.store || new MemoryStore
191     , fingerprint = options.fingerprint || defaultFingerprint
192     , cookie = options.cookie;
193
194   // notify user that this store is not
195   // meant for a production environment
196   if ('production' == env && store instanceof MemoryStore) {
197     console.warn(warning);
198   }
199
200   // ensure secret is present
201   if (!secret) {
202     throw new Error('connect.session({ secret: "string" }) required for security');
203   }
204
205   // session hashing function
206   store.hash = function(req, base) {
207     return crypto
208       .createHmac('sha256', secret)
209       .update(base + fingerprint(req))
210       .digest('base64')
211       .replace(/=*$/, '');
212   };
213
214   // generates the new session
215   store.generate = function(req){
216     var base = utils.uid(24);
217     var sessionID = base + '.' + store.hash(req, base);
218     req.sessionID = sessionID;
219     req.session = new Session(req);
220     req.session.cookie = new Cookie(cookie);
221   };
222
223   return function session(req, res, next) {
224     // self-awareness
225     if (req.session) return next();
226
227     // parse url
228     var url = parse(req.url)
229       , path = url.pathname;
230
231     // ignorable paths
232     if (~exports.ignore.indexOf(path)) return next();
233
234     // expose store
235     req.sessionStore = store;
236
237     // proxy writeHead() to Set-Cookie
238     var writeHead = res.writeHead;
239     res.writeHead = function(status, headers){
240       if (req.session) {
241         var cookie = req.session.cookie;
242         // only send secure session cookies when there is a secure connection.
243         // proxySecure is a custom attribute to allow for a reverse proxy
244         // to handle SSL connections and to communicate to connect over HTTP that
245         // the incoming connection is secure.
246         var secured = cookie.secure && (req.connection.encrypted || req.connection.proxySecure);
247         if (secured || !cookie.secure) {
248           res.setHeader('Set-Cookie', cookie.serialize(key, req.sessionID));
249         }
250       }
251
252       res.writeHead = writeHead;
253       return res.writeHead(status, headers);
254     };
255
256     // proxy end() to commit the session
257     var end = res.end;
258     res.end = function(data, encoding){
259       res.end = end;
260       if (req.session) {
261         // HACK: ensure Set-Cookie for implicit writeHead()
262         if (!res._header) res._implicitHeader();
263         req.session.resetMaxAge();
264         req.session.save(function(){
265           res.end(data, encoding);
266         });
267       } else {
268         res.end(data, encoding);
269       }
270     };
271
272     // session hashing
273     function hash(base) {
274       return store.hash(req, base);
275     }
276
277     // generate the session
278     function generate() {
279       store.generate(req);
280     }
281
282     // get the sessionID from the cookie
283     req.sessionID = req.cookies[key];
284
285     // make a new session if the browser doesn't send a sessionID
286     if (!req.sessionID) {
287       generate();
288       next();
289       return;
290     }
291
292     // check the fingerprint
293     var parts = req.sessionID.split('.');
294     if (parts[1] != hash(parts[0])) {
295       generate();
296       next();
297       return;
298     }
299
300     // generate the session object
301     var pause = utils.pause(req);
302     store.get(req.sessionID, function(err, sess){
303       // proxy to resume() events
304       var _next = next;
305       next = function(err){
306         _next(err);
307         pause.resume();
308       }
309
310       // error handling
311       if (err) {
312         if ('ENOENT' == err.code) {
313           generate();
314           next();
315         } else {
316           next(err);
317         }
318       // no session
319       } else if (!sess) {
320         generate();
321         next();
322       // populate req.session
323       } else {
324         store.createSession(req, sess);
325         next();
326       }
327     });
328   };
329 };