index.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. 'use strict';
  2. const debug = require('debug')('koa-session');
  3. const ContextSession = require('./lib/context');
  4. const util = require('./lib/util');
  5. const assert = require('assert');
  6. const uuid = require('uuid/v4');
  7. const is = require('is-type-of');
  8. const CONTEXT_SESSION = Symbol('context#contextSession');
  9. const _CONTEXT_SESSION = Symbol('context#_contextSession');
  10. /**
  11. * Initialize session middleware with `opts`:
  12. *
  13. * - `key` session cookie name ["koa.sess"]
  14. * - all other options are passed as cookie options
  15. *
  16. * @param {Object} [opts]
  17. * @param {Application} app, koa application instance
  18. * @api public
  19. */
  20. module.exports = function(opts, app) {
  21. // session(app[, opts])
  22. if (opts && typeof opts.use === 'function') {
  23. [ app, opts ] = [ opts, app ];
  24. }
  25. // app required
  26. if (!app || typeof app.use !== 'function') {
  27. throw new TypeError('app instance required: `session(opts, app)`');
  28. }
  29. opts = formatOpts(opts);
  30. extendContext(app.context, opts);
  31. return async function session(ctx, next) {
  32. const sess = ctx[CONTEXT_SESSION];
  33. if (sess.store) await sess.initFromExternal();
  34. try {
  35. await next();
  36. } catch (err) {
  37. throw err;
  38. } finally {
  39. if (opts.autoCommit) {
  40. await sess.commit();
  41. }
  42. }
  43. };
  44. };
  45. /**
  46. * format and check session options
  47. * @param {Object} opts session options
  48. * @return {Object} new session options
  49. *
  50. * @api private
  51. */
  52. function formatOpts(opts) {
  53. opts = opts || {};
  54. // key
  55. opts.key = opts.key || 'koa.sess';
  56. // back-compat maxage
  57. if (!('maxAge' in opts)) opts.maxAge = opts.maxage;
  58. // defaults
  59. if (opts.overwrite == null) opts.overwrite = true;
  60. if (opts.httpOnly == null) opts.httpOnly = true;
  61. // delete null sameSite config
  62. if (opts.sameSite == null) delete opts.sameSite;
  63. if (opts.signed == null) opts.signed = true;
  64. if (opts.autoCommit == null) opts.autoCommit = true;
  65. debug('session options %j', opts);
  66. // setup encoding/decoding
  67. if (typeof opts.encode !== 'function') {
  68. opts.encode = util.encode;
  69. }
  70. if (typeof opts.decode !== 'function') {
  71. opts.decode = util.decode;
  72. }
  73. const store = opts.store;
  74. if (store) {
  75. assert(is.function(store.get), 'store.get must be function');
  76. assert(is.function(store.set), 'store.set must be function');
  77. assert(is.function(store.destroy), 'store.destroy must be function');
  78. }
  79. const externalKey = opts.externalKey;
  80. if (externalKey) {
  81. assert(is.function(externalKey.get), 'externalKey.get must be function');
  82. assert(is.function(externalKey.set), 'externalKey.set must be function');
  83. }
  84. const ContextStore = opts.ContextStore;
  85. if (ContextStore) {
  86. assert(is.class(ContextStore), 'ContextStore must be a class');
  87. assert(is.function(ContextStore.prototype.get), 'ContextStore.prototype.get must be function');
  88. assert(is.function(ContextStore.prototype.set), 'ContextStore.prototype.set must be function');
  89. assert(is.function(ContextStore.prototype.destroy), 'ContextStore.prototype.destroy must be function');
  90. }
  91. if (!opts.genid) {
  92. if (opts.prefix) opts.genid = () => `${opts.prefix}${uuid()}`;
  93. else opts.genid = uuid;
  94. }
  95. return opts;
  96. }
  97. /**
  98. * extend context prototype, add session properties
  99. *
  100. * @param {Object} context koa's context prototype
  101. * @param {Object} opts session options
  102. *
  103. * @api private
  104. */
  105. function extendContext(context, opts) {
  106. if (context.hasOwnProperty(CONTEXT_SESSION)) {
  107. return;
  108. }
  109. Object.defineProperties(context, {
  110. [CONTEXT_SESSION]: {
  111. get() {
  112. if (this[_CONTEXT_SESSION]) return this[_CONTEXT_SESSION];
  113. this[_CONTEXT_SESSION] = new ContextSession(this, opts);
  114. return this[_CONTEXT_SESSION];
  115. },
  116. },
  117. session: {
  118. get() {
  119. return this[CONTEXT_SESSION].get();
  120. },
  121. set(val) {
  122. this[CONTEXT_SESSION].set(val);
  123. },
  124. configurable: true,
  125. },
  126. sessionOptions: {
  127. get() {
  128. return this[CONTEXT_SESSION].opts;
  129. },
  130. },
  131. });
  132. }