comunicationLeap/node_modules/connect/lib/middleware/session.js
node_modules/connect/lib/middleware/session.jsRaw
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
13var Session = require('./session/session')
14 , debug = require('debug')('connect:session')
15 , MemoryStore = require('./session/memory')
16 , signature = require('cookie-signature')
17 , Cookie = require('./session/cookie')
18 , Store = require('./session/store')
19 , utils = require('./../utils')
20 , parse = utils.parseUrl
21 , crc32 = require('buffer-crc32');
22
23// environment
24
25var env = process.env.NODE_ENV;
26
27/**
28 * Expose the middleware.
29 */
30
31exports = module.exports = session;
32
33/**
34 * Expose constructors.
35 */
36
37exports.Store = Store;
38exports.Cookie = Cookie;
39exports.Session = Session;
40exports.MemoryStore = MemoryStore;
41
42/**
43 * Warning message for `MemoryStore` usage in production.
44 */
45
46var warning = 'Warning: connection.session() MemoryStore is not\n'
47 + 'designed for a production environment, as it will leak\n'
48 + 'memory, and will not scale past a single process.';
49
50/**
51 * Session:
52 *
53 * Setup session store with the given `options`.
54 *
55 * Session data is _not_ saved in the cookie itself, however
56 * cookies are used, so we must use the [cookieParser()](cookieParser.html)
57 * middleware _before_ `session()`.
58 *
59 * Examples:
60 *
61 * connect()
62 * .use(connect.cookieParser())
63 * .use(connect.session({ secret: 'keyboard cat', key: 'sid', cookie: { secure: true }}))
64 *
65 * Options:
66 *
67 * - `key` cookie name defaulting to `connect.sid`
68 * - `store` session store instance
69 * - `secret` session cookie is signed with this secret to prevent tampering
70 * - `cookie` session cookie settings, defaulting to `{ path: '/', httpOnly: true, maxAge: null }`
71 * - `proxy` trust the reverse proxy when setting secure cookies (via "x-forwarded-proto")
72 *
73 * Cookie option:
74 *
75 * By default `cookie.maxAge` is `null`, meaning no "expires" parameter is set
76 * so the cookie becomes a browser-session cookie. When the user closes the
77 * browser the cookie (and session) will be removed.
78 *
79 * ## req.session
80 *
81 * To store or access session data, simply use the request property `req.session`,
82 * which is (generally) serialized as JSON by the store, so nested objects
83 * are typically fine. For example below is a user-specific view counter:
84 *
85 * connect()
86 * .use(connect.favicon())
87 * .use(connect.cookieParser())
88 * .use(connect.session({ secret: 'keyboard cat', cookie: { maxAge: 60000 }}))
89 * .use(function(req, res, next){
90 * var sess = req.session;
91 * if (sess.views) {
92 * res.setHeader('Content-Type', 'text/html');
93 * res.write('<p>views: ' + sess.views + '</p>');
94 * res.write('<p>expires in: ' + (sess.cookie.maxAge / 1000) + 's</p>');
95 * res.end();
96 * sess.views++;
97 * } else {
98 * sess.views = 1;
99 * res.end('welcome to the session demo. refresh!');
100 * }
101 * }
102 * )).listen(3000);
103 *
104 * ## Session#regenerate()
105 *
106 * To regenerate the session simply invoke the method, once complete
107 * a new SID and `Session` instance will be initialized at `req.session`.
108 *
109 * req.session.regenerate(function(err){
110 * // will have a new session here
111 * });
112 *
113 * ## Session#destroy()
114 *
115 * Destroys the session, removing `req.session`, will be re-generated next request.
116 *
117 * req.session.destroy(function(err){
118 * // cannot access session here
119 * });
120 *
121 * ## Session#reload()
122 *
123 * Reloads the session data.
124 *
125 * req.session.reload(function(err){
126 * // session updated
127 * });
128 *
129 * ## Session#save()
130 *
131 * Save the session.
132 *
133 * req.session.save(function(err){
134 * // session saved
135 * });
136 *
137 * ## Session#touch()
138 *
139 * Updates the `.maxAge` property. Typically this is
140 * not necessary to call, as the session middleware does this for you.
141 *
142 * ## Session#cookie
143 *
144 * Each session has a unique cookie object accompany it. This allows
145 * you to alter the session cookie per visitor. For example we can
146 * set `req.session.cookie.expires` to `false` to enable the cookie
147 * to remain for only the duration of the user-agent.
148 *
149 * ## Session#maxAge
150 *
151 * Alternatively `req.session.cookie.maxAge` will return the time
152 * remaining in milliseconds, which we may also re-assign a new value
153 * to adjust the `.expires` property appropriately. The following
154 * are essentially equivalent
155 *
156 * var hour = 3600000;
157 * req.session.cookie.expires = new Date(Date.now() + hour);
158 * req.session.cookie.maxAge = hour;
159 *
160 * For example when `maxAge` is set to `60000` (one minute), and 30 seconds
161 * has elapsed it will return `30000` until the current request has completed,
162 * at which time `req.session.touch()` is called to reset `req.session.maxAge`
163 * to its original value.
164 *
165 * req.session.cookie.maxAge;
166 * // => 30000
167 *
168 * Session Store Implementation:
169 *
170 * Every session store _must_ implement the following methods
171 *
172 * - `.get(sid, callback)`
173 * - `.set(sid, session, callback)`
174 * - `.destroy(sid, callback)`
175 *
176 * Recommended methods include, but are not limited to:
177 *
178 * - `.length(callback)`
179 * - `.clear(callback)`
180 *
181 * For an example implementation view the [connect-redis](http://github.com/visionmedia/connect-redis) repo.
182 *
183 * @param {Object} options
184 * @return {Function}
185 * @api public
186 */
187
188function session(options){
189 var options = options || {}
190 , key = options.key || 'connect.sid'
191 , store = options.store || new MemoryStore
192 , cookie = options.cookie || {}
193 , trustProxy = options.proxy
194 , storeReady = true;
195
196 // notify user that this store is not
197 // meant for a production environment
198 if ('production' == env && store instanceof MemoryStore) {
199 console.warn(warning);
200 }
201
202 // generates the new session
203 store.generate = function(req){
204 req.sessionID = utils.uid(24);
205 req.session = new Session(req);
206 req.session.cookie = new Cookie(cookie);
207 };
208
209 store.on('disconnect', function(){ storeReady = false; });
210 store.on('connect', function(){ storeReady = true; });
211
212 return function session(req, res, next) {
213 // self-awareness
214 if (req.session) return next();
215
216 // Handle connection as if there is no session if
217 // the store has temporarily disconnected etc
218 if (!storeReady) return debug('store is disconnected'), next();
219
220 // pathname mismatch
221 if (0 != req.originalUrl.indexOf(cookie.path || '/')) return next();
222
223 // backwards compatibility for signed cookies
224 // req.secret is passed from the cookie parser middleware
225 var secret = options.secret || req.secret;
226
227 // ensure secret is available or bail
228 if (!secret) throw new Error('`secret` option required for sessions');
229
230 // parse url
231 var originalHash
232 , originalId;
233
234 // expose store
235 req.sessionStore = store;
236
237 // grab the session cookie value and check the signature
238 var rawCookie = req.cookies[key];
239
240 // get signedCookies for backwards compat with signed cookies
241 var unsignedCookie = req.signedCookies[key];
242
243 if (!unsignedCookie && rawCookie) {
244 unsignedCookie = utils.parseSignedCookie(rawCookie, secret);
245 }
246
247 // set-cookie
248 res.on('header', function(){
249 if (!req.session) return;
250 var cookie = req.session.cookie
251 , proto = (req.headers['x-forwarded-proto'] || '').split(',')[0].toLowerCase().trim()
252 , tls = req.connection.encrypted || (trustProxy && 'https' == proto)
253 , secured = cookie.secure && tls
254 , isNew = unsignedCookie != req.sessionID;
255
256 // only send secure cookies via https
257 if (cookie.secure && !secured) return debug('not secured');
258
259 // long expires, handle expiry server-side
260 if (!isNew && cookie.hasLongExpires) return debug('already set cookie');
261
262 // browser-session length cookie
263 if (null == cookie.expires) {
264 if (!isNew) return debug('already set browser-session cookie');
265 // compare hashes and ids
266 } else if (originalHash == hash(req.session) && originalId == req.session.id) {
267 return debug('unmodified session');
268 }
269
270 var val = 's:' + signature.sign(req.sessionID, secret);
271 val = cookie.serialize(key, val);
272 debug('set-cookie %s', val);
273 res.setHeader('Set-Cookie', val);
274 });
275
276 // proxy end() to commit the session
277 var end = res.end;
278 res.end = function(data, encoding){
279 res.end = end;
280 if (!req.session) return res.end(data, encoding);
281 debug('saving');
282 req.session.resetMaxAge();
283 req.session.save(function(err){
284 if (err) console.error(err.stack);
285 debug('saved');
286 res.end(data, encoding);
287 });
288 };
289
290 // generate the session
291 function generate() {
292 store.generate(req);
293 }
294
295 // get the sessionID from the cookie
296 req.sessionID = unsignedCookie;
297
298 // generate a session if the browser doesn't send a sessionID
299 if (!req.sessionID) {
300 debug('no SID sent, generating session');
301 generate();
302 next();
303 return;
304 }
305
306 // generate the session object
307 var pause = utils.pause(req);
308 debug('fetching %s', req.sessionID);
309 store.get(req.sessionID, function(err, sess){
310 // proxy to resume() events
311 var _next = next;
312 next = function(err){
313 _next(err);
314 pause.resume();
315 };
316
317 // error handling
318 if (err) {
319 debug('error %j', err);
320 if ('ENOENT' == err.code) {
321 generate();
322 next();
323 } else {
324 next(err);
325 }
326 // no session
327 } else if (!sess) {
328 debug('no session found');
329 generate();
330 next();
331 // populate req.session
332 } else {
333 debug('session found');
334 store.createSession(req, sess);
335 originalId = req.sessionID;
336 originalHash = hash(sess);
337 next();
338 }
339 });
340 };
341};
342
343/**
344 * Hash the given `sess` object omitting changes
345 * to `.cookie`.
346 *
347 * @param {Object} sess
348 * @return {String}
349 * @api private
350 */
351
352function hash(sess) {
353 return crc32.signed(JSON.stringify(sess, function(key, val){
354 if ('cookie' != key) return val;
355 }));
356}