arnaucube
arnaucube/comunicationLeapPublic Self-hosted repository
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}