Ohm-Management - Projektarbeit B-ME
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.

index.js 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664
  1. /*!
  2. * express-session
  3. * Copyright(c) 2010 Sencha Inc.
  4. * Copyright(c) 2011 TJ Holowaychuk
  5. * Copyright(c) 2014-2015 Douglas Christopher Wilson
  6. * MIT Licensed
  7. */
  8. 'use strict';
  9. /**
  10. * Module dependencies.
  11. * @private
  12. */
  13. var cookie = require('cookie');
  14. var crc = require('crc').crc32;
  15. var debug = require('debug')('express-session');
  16. var deprecate = require('depd')('express-session');
  17. var parseUrl = require('parseurl');
  18. var uid = require('uid-safe').sync
  19. , onHeaders = require('on-headers')
  20. , signature = require('cookie-signature')
  21. var Session = require('./session/session')
  22. , MemoryStore = require('./session/memory')
  23. , Cookie = require('./session/cookie')
  24. , Store = require('./session/store')
  25. // environment
  26. var env = process.env.NODE_ENV;
  27. /**
  28. * Expose the middleware.
  29. */
  30. exports = module.exports = session;
  31. /**
  32. * Expose constructors.
  33. */
  34. exports.Store = Store;
  35. exports.Cookie = Cookie;
  36. exports.Session = Session;
  37. exports.MemoryStore = MemoryStore;
  38. /**
  39. * Warning message for `MemoryStore` usage in production.
  40. * @private
  41. */
  42. var warning = 'Warning: connect.session() MemoryStore is not\n'
  43. + 'designed for a production environment, as it will leak\n'
  44. + 'memory, and will not scale past a single process.';
  45. /**
  46. * Node.js 0.8+ async implementation.
  47. * @private
  48. */
  49. /* istanbul ignore next */
  50. var defer = typeof setImmediate === 'function'
  51. ? setImmediate
  52. : function(fn){ process.nextTick(fn.bind.apply(fn, arguments)) }
  53. /**
  54. * Setup session store with the given `options`.
  55. *
  56. * @param {Object} [options]
  57. * @param {Object} [options.cookie] Options for cookie
  58. * @param {Function} [options.genid]
  59. * @param {String} [options.name=connect.sid] Session ID cookie name
  60. * @param {Boolean} [options.proxy]
  61. * @param {Boolean} [options.resave] Resave unmodified sessions back to the store
  62. * @param {Boolean} [options.rolling] Enable/disable rolling session expiration
  63. * @param {Boolean} [options.saveUninitialized] Save uninitialized sessions to the store
  64. * @param {String|Array} [options.secret] Secret for signing session ID
  65. * @param {Object} [options.store=MemoryStore] Session store
  66. * @param {String} [options.unset]
  67. * @return {Function} middleware
  68. * @public
  69. */
  70. function session(options) {
  71. var opts = options || {}
  72. // get the cookie options
  73. var cookieOptions = opts.cookie || {}
  74. // get the session id generate function
  75. var generateId = opts.genid || generateSessionId
  76. // get the session cookie name
  77. var name = opts.name || opts.key || 'connect.sid'
  78. // get the session store
  79. var store = opts.store || new MemoryStore()
  80. // get the trust proxy setting
  81. var trustProxy = opts.proxy
  82. // get the resave session option
  83. var resaveSession = opts.resave;
  84. // get the rolling session option
  85. var rollingSessions = Boolean(opts.rolling)
  86. // get the save uninitialized session option
  87. var saveUninitializedSession = opts.saveUninitialized
  88. // get the cookie signing secret
  89. var secret = opts.secret
  90. if (typeof generateId !== 'function') {
  91. throw new TypeError('genid option must be a function');
  92. }
  93. if (resaveSession === undefined) {
  94. deprecate('undefined resave option; provide resave option');
  95. resaveSession = true;
  96. }
  97. if (saveUninitializedSession === undefined) {
  98. deprecate('undefined saveUninitialized option; provide saveUninitialized option');
  99. saveUninitializedSession = true;
  100. }
  101. if (opts.unset && opts.unset !== 'destroy' && opts.unset !== 'keep') {
  102. throw new TypeError('unset option must be "destroy" or "keep"');
  103. }
  104. // TODO: switch to "destroy" on next major
  105. var unsetDestroy = opts.unset === 'destroy'
  106. if (Array.isArray(secret) && secret.length === 0) {
  107. throw new TypeError('secret option array must contain one or more strings');
  108. }
  109. if (secret && !Array.isArray(secret)) {
  110. secret = [secret];
  111. }
  112. if (!secret) {
  113. deprecate('req.secret; provide secret option');
  114. }
  115. // notify user that this store is not
  116. // meant for a production environment
  117. /* istanbul ignore next: not tested */
  118. if ('production' == env && store instanceof MemoryStore) {
  119. console.warn(warning);
  120. }
  121. // generates the new session
  122. store.generate = function(req){
  123. req.sessionID = generateId(req);
  124. req.session = new Session(req);
  125. req.session.cookie = new Cookie(cookieOptions);
  126. if (cookieOptions.secure === 'auto') {
  127. req.session.cookie.secure = issecure(req, trustProxy);
  128. }
  129. };
  130. var storeImplementsTouch = typeof store.touch === 'function';
  131. // register event listeners for the store to track readiness
  132. var storeReady = true
  133. store.on('disconnect', function ondisconnect() {
  134. storeReady = false
  135. })
  136. store.on('connect', function onconnect() {
  137. storeReady = true
  138. })
  139. return function session(req, res, next) {
  140. // self-awareness
  141. if (req.session) {
  142. next()
  143. return
  144. }
  145. // Handle connection as if there is no session if
  146. // the store has temporarily disconnected etc
  147. if (!storeReady) {
  148. debug('store is disconnected')
  149. next()
  150. return
  151. }
  152. // pathname mismatch
  153. var originalPath = parseUrl.original(req).pathname || '/'
  154. if (originalPath.indexOf(cookieOptions.path || '/') !== 0) return next();
  155. // ensure a secret is available or bail
  156. if (!secret && !req.secret) {
  157. next(new Error('secret option required for sessions'));
  158. return;
  159. }
  160. // backwards compatibility for signed cookies
  161. // req.secret is passed from the cookie parser middleware
  162. var secrets = secret || [req.secret];
  163. var originalHash;
  164. var originalId;
  165. var savedHash;
  166. var touched = false
  167. // expose store
  168. req.sessionStore = store;
  169. // get the session ID from the cookie
  170. var cookieId = req.sessionID = getcookie(req, name, secrets);
  171. // set-cookie
  172. onHeaders(res, function(){
  173. if (!req.session) {
  174. debug('no session');
  175. return;
  176. }
  177. if (!shouldSetCookie(req)) {
  178. return;
  179. }
  180. // only send secure cookies via https
  181. if (req.session.cookie.secure && !issecure(req, trustProxy)) {
  182. debug('not secured');
  183. return;
  184. }
  185. if (!touched) {
  186. // touch session
  187. req.session.touch()
  188. touched = true
  189. }
  190. // set cookie
  191. setcookie(res, name, req.sessionID, secrets[0], req.session.cookie.data);
  192. });
  193. // proxy end() to commit the session
  194. var _end = res.end;
  195. var _write = res.write;
  196. var ended = false;
  197. res.end = function end(chunk, encoding) {
  198. if (ended) {
  199. return false;
  200. }
  201. ended = true;
  202. var ret;
  203. var sync = true;
  204. function writeend() {
  205. if (sync) {
  206. ret = _end.call(res, chunk, encoding);
  207. sync = false;
  208. return;
  209. }
  210. _end.call(res);
  211. }
  212. function writetop() {
  213. if (!sync) {
  214. return ret;
  215. }
  216. if (chunk == null) {
  217. ret = true;
  218. return ret;
  219. }
  220. var contentLength = Number(res.getHeader('Content-Length'));
  221. if (!isNaN(contentLength) && contentLength > 0) {
  222. // measure chunk
  223. chunk = !Buffer.isBuffer(chunk)
  224. ? new Buffer(chunk, encoding)
  225. : chunk;
  226. encoding = undefined;
  227. if (chunk.length !== 0) {
  228. debug('split response');
  229. ret = _write.call(res, chunk.slice(0, chunk.length - 1));
  230. chunk = chunk.slice(chunk.length - 1, chunk.length);
  231. return ret;
  232. }
  233. }
  234. ret = _write.call(res, chunk, encoding);
  235. sync = false;
  236. return ret;
  237. }
  238. if (shouldDestroy(req)) {
  239. // destroy session
  240. debug('destroying');
  241. store.destroy(req.sessionID, function ondestroy(err) {
  242. if (err) {
  243. defer(next, err);
  244. }
  245. debug('destroyed');
  246. writeend();
  247. });
  248. return writetop();
  249. }
  250. // no session to save
  251. if (!req.session) {
  252. debug('no session');
  253. return _end.call(res, chunk, encoding);
  254. }
  255. if (!touched) {
  256. // touch session
  257. req.session.touch()
  258. touched = true
  259. }
  260. if (shouldSave(req)) {
  261. req.session.save(function onsave(err) {
  262. if (err) {
  263. defer(next, err);
  264. }
  265. writeend();
  266. });
  267. return writetop();
  268. } else if (storeImplementsTouch && shouldTouch(req)) {
  269. // store implements touch method
  270. debug('touching');
  271. store.touch(req.sessionID, req.session, function ontouch(err) {
  272. if (err) {
  273. defer(next, err);
  274. }
  275. debug('touched');
  276. writeend();
  277. });
  278. return writetop();
  279. }
  280. return _end.call(res, chunk, encoding);
  281. };
  282. // generate the session
  283. function generate() {
  284. store.generate(req);
  285. originalId = req.sessionID;
  286. originalHash = hash(req.session);
  287. wrapmethods(req.session);
  288. }
  289. // wrap session methods
  290. function wrapmethods(sess) {
  291. var _reload = sess.reload
  292. var _save = sess.save;
  293. function reload(callback) {
  294. debug('reloading %s', this.id)
  295. _reload.call(this, function () {
  296. wrapmethods(req.session)
  297. callback.apply(this, arguments)
  298. })
  299. }
  300. function save() {
  301. debug('saving %s', this.id);
  302. savedHash = hash(this);
  303. _save.apply(this, arguments);
  304. }
  305. Object.defineProperty(sess, 'reload', {
  306. configurable: true,
  307. enumerable: false,
  308. value: reload,
  309. writable: true
  310. })
  311. Object.defineProperty(sess, 'save', {
  312. configurable: true,
  313. enumerable: false,
  314. value: save,
  315. writable: true
  316. });
  317. }
  318. // check if session has been modified
  319. function isModified(sess) {
  320. return originalId !== sess.id || originalHash !== hash(sess);
  321. }
  322. // check if session has been saved
  323. function isSaved(sess) {
  324. return originalId === sess.id && savedHash === hash(sess);
  325. }
  326. // determine if session should be destroyed
  327. function shouldDestroy(req) {
  328. return req.sessionID && unsetDestroy && req.session == null;
  329. }
  330. // determine if session should be saved to store
  331. function shouldSave(req) {
  332. // cannot set cookie without a session ID
  333. if (typeof req.sessionID !== 'string') {
  334. debug('session ignored because of bogus req.sessionID %o', req.sessionID);
  335. return false;
  336. }
  337. return !saveUninitializedSession && cookieId !== req.sessionID
  338. ? isModified(req.session)
  339. : !isSaved(req.session)
  340. }
  341. // determine if session should be touched
  342. function shouldTouch(req) {
  343. // cannot set cookie without a session ID
  344. if (typeof req.sessionID !== 'string') {
  345. debug('session ignored because of bogus req.sessionID %o', req.sessionID);
  346. return false;
  347. }
  348. return cookieId === req.sessionID && !shouldSave(req);
  349. }
  350. // determine if cookie should be set on response
  351. function shouldSetCookie(req) {
  352. // cannot set cookie without a session ID
  353. if (typeof req.sessionID !== 'string') {
  354. return false;
  355. }
  356. return cookieId != req.sessionID
  357. ? saveUninitializedSession || isModified(req.session)
  358. : rollingSessions || req.session.cookie.expires != null && isModified(req.session);
  359. }
  360. // generate a session if the browser doesn't send a sessionID
  361. if (!req.sessionID) {
  362. debug('no SID sent, generating session');
  363. generate();
  364. next();
  365. return;
  366. }
  367. // generate the session object
  368. debug('fetching %s', req.sessionID);
  369. store.get(req.sessionID, function(err, sess){
  370. // error handling
  371. if (err) {
  372. debug('error %j', err);
  373. if (err.code !== 'ENOENT') {
  374. next(err);
  375. return;
  376. }
  377. generate();
  378. // no session
  379. } else if (!sess) {
  380. debug('no session found');
  381. generate();
  382. // populate req.session
  383. } else {
  384. debug('session found');
  385. store.createSession(req, sess);
  386. originalId = req.sessionID;
  387. originalHash = hash(sess);
  388. if (!resaveSession) {
  389. savedHash = originalHash
  390. }
  391. wrapmethods(req.session);
  392. }
  393. next();
  394. });
  395. };
  396. };
  397. /**
  398. * Generate a session ID for a new session.
  399. *
  400. * @return {String}
  401. * @private
  402. */
  403. function generateSessionId(sess) {
  404. return uid(24);
  405. }
  406. /**
  407. * Get the session ID cookie from request.
  408. *
  409. * @return {string}
  410. * @private
  411. */
  412. function getcookie(req, name, secrets) {
  413. var header = req.headers.cookie;
  414. var raw;
  415. var val;
  416. // read from cookie header
  417. if (header) {
  418. var cookies = cookie.parse(header);
  419. raw = cookies[name];
  420. if (raw) {
  421. if (raw.substr(0, 2) === 's:') {
  422. val = unsigncookie(raw.slice(2), secrets);
  423. if (val === false) {
  424. debug('cookie signature invalid');
  425. val = undefined;
  426. }
  427. } else {
  428. debug('cookie unsigned')
  429. }
  430. }
  431. }
  432. // back-compat read from cookieParser() signedCookies data
  433. if (!val && req.signedCookies) {
  434. val = req.signedCookies[name];
  435. if (val) {
  436. deprecate('cookie should be available in req.headers.cookie');
  437. }
  438. }
  439. // back-compat read from cookieParser() cookies data
  440. if (!val && req.cookies) {
  441. raw = req.cookies[name];
  442. if (raw) {
  443. if (raw.substr(0, 2) === 's:') {
  444. val = unsigncookie(raw.slice(2), secrets);
  445. if (val) {
  446. deprecate('cookie should be available in req.headers.cookie');
  447. }
  448. if (val === false) {
  449. debug('cookie signature invalid');
  450. val = undefined;
  451. }
  452. } else {
  453. debug('cookie unsigned')
  454. }
  455. }
  456. }
  457. return val;
  458. }
  459. /**
  460. * Hash the given `sess` object omitting changes to `.cookie`.
  461. *
  462. * @param {Object} sess
  463. * @return {String}
  464. * @private
  465. */
  466. function hash(sess) {
  467. return crc(JSON.stringify(sess, function (key, val) {
  468. // ignore sess.cookie property
  469. if (this === sess && key === 'cookie') {
  470. return
  471. }
  472. return val
  473. }))
  474. }
  475. /**
  476. * Determine if request is secure.
  477. *
  478. * @param {Object} req
  479. * @param {Boolean} [trustProxy]
  480. * @return {Boolean}
  481. * @private
  482. */
  483. function issecure(req, trustProxy) {
  484. // socket is https server
  485. if (req.connection && req.connection.encrypted) {
  486. return true;
  487. }
  488. // do not trust proxy
  489. if (trustProxy === false) {
  490. return false;
  491. }
  492. // no explicit trust; try req.secure from express
  493. if (trustProxy !== true) {
  494. var secure = req.secure;
  495. return typeof secure === 'boolean'
  496. ? secure
  497. : false;
  498. }
  499. // read the proto from x-forwarded-proto header
  500. var header = req.headers['x-forwarded-proto'] || '';
  501. var index = header.indexOf(',');
  502. var proto = index !== -1
  503. ? header.substr(0, index).toLowerCase().trim()
  504. : header.toLowerCase().trim()
  505. return proto === 'https';
  506. }
  507. /**
  508. * Set cookie on response.
  509. *
  510. * @private
  511. */
  512. function setcookie(res, name, val, secret, options) {
  513. var signed = 's:' + signature.sign(val, secret);
  514. var data = cookie.serialize(name, signed, options);
  515. debug('set-cookie %s', data);
  516. var prev = res.getHeader('set-cookie') || [];
  517. var header = Array.isArray(prev) ? prev.concat(data) : [prev, data];
  518. res.setHeader('set-cookie', header)
  519. }
  520. /**
  521. * Verify and decode the given `val` with `secrets`.
  522. *
  523. * @param {String} val
  524. * @param {Array} secrets
  525. * @returns {String|Boolean}
  526. * @private
  527. */
  528. function unsigncookie(val, secrets) {
  529. for (var i = 0; i < secrets.length; i++) {
  530. var result = signature.unsign(val, secrets[i]);
  531. if (result !== false) {
  532. return result;
  533. }
  534. }
  535. return false;
  536. }