cookieSession.js 3.2 KB

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