You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

117 lines
3.3 KiB

  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)
  83. , secured = cookie.secure && tls;
  84. // only send secure cookies via https
  85. if (cookie.secure && !secured) return debug('not secured');
  86. // serialize
  87. debug('serializing %j', req.session);
  88. var val = 'j:' + JSON.stringify(req.session);
  89. // compare hashes, no need to set-cookie if unchanged
  90. if (originalHash == crc32.signed(val)) return debug('unmodified session');
  91. // set-cookie
  92. val = 's:' + signature.sign(val, secret);
  93. val = cookie.serialize(key, val);
  94. debug('set-cookie %j', cookie);
  95. res.setHeader('Set-Cookie', val);
  96. });
  97. next();
  98. };
  99. };