index.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. /*!
  2. * csurf
  3. * Copyright(c) 2011 Sencha Inc.
  4. * Copyright(c) 2014 Jonathan Ong
  5. * Copyright(c) 2014 Douglas Christopher Wilson
  6. * MIT Licensed
  7. */
  8. /**
  9. * Module dependencies.
  10. */
  11. var Cookie = require('cookie');
  12. var csrfTokens = require('csrf');
  13. var createError = require('http-errors');
  14. var sign = require('cookie-signature').sign;
  15. /**
  16. * CSRF protection middleware.
  17. *
  18. * This middleware adds a `req.csrfToken()` function to make a token
  19. * which should be added to requests which mutate
  20. * state, within a hidden form field, query-string etc. This
  21. * token is validated against the visitor's session.
  22. *
  23. * @param {Object} options
  24. * @return {Function} middleware
  25. * @api public
  26. */
  27. module.exports = function csurf(options) {
  28. options = options || {};
  29. // get cookie options
  30. var cookie = options.cookie !== true
  31. ? options.cookie || undefined
  32. : {}
  33. // get value getter
  34. var value = options.value || defaultValue
  35. // token repo
  36. var tokens = csrfTokens(options);
  37. // default cookie key
  38. if (cookie && !cookie.key) {
  39. cookie.key = '_csrf'
  40. }
  41. // ignored methods
  42. var ignoreMethods = options.ignoreMethods === undefined
  43. ? ['GET', 'HEAD', 'OPTIONS']
  44. : options.ignoreMethods
  45. if (!Array.isArray(ignoreMethods)) {
  46. throw new TypeError('option ignoreMethods must be an array')
  47. }
  48. // generate lookup
  49. var ignoreMethod = getIgnoredMethods(ignoreMethods)
  50. return function csrf(req, res, next) {
  51. var secret = getsecret(req, cookie)
  52. var token
  53. // lazy-load token getter
  54. req.csrfToken = function csrfToken() {
  55. var sec = !cookie
  56. ? getsecret(req, cookie)
  57. : secret
  58. // use cached token if secret has not changed
  59. if (token && sec === secret) {
  60. return token
  61. }
  62. // generate & set new secret
  63. if (sec === undefined) {
  64. sec = tokens.secretSync()
  65. setsecret(req, res, sec, cookie)
  66. }
  67. // update changed secret
  68. secret = sec
  69. // create new token
  70. token = tokens.create(secret)
  71. return token
  72. }
  73. // generate & set secret
  74. if (!secret) {
  75. secret = tokens.secretSync()
  76. setsecret(req, res, secret, cookie)
  77. }
  78. // verify the incoming token
  79. if (!ignoreMethod[req.method]) {
  80. verifytoken(req, tokens, secret, value(req))
  81. }
  82. next()
  83. }
  84. };
  85. /**
  86. * Default value function, checking the `req.body`
  87. * and `req.query` for the CSRF token.
  88. *
  89. * @param {IncomingMessage} req
  90. * @return {String}
  91. * @api private
  92. */
  93. function defaultValue(req) {
  94. return (req.body && req.body._csrf)
  95. || (req.query && req.query._csrf)
  96. || (req.headers['x-csrf-token'])
  97. || (req.headers['x-xsrf-token']);
  98. }
  99. /**
  100. * Get a lookup of ignored methods.
  101. *
  102. * @param {array} methods
  103. * @returns {object}
  104. * @api private
  105. */
  106. function getIgnoredMethods(methods) {
  107. var obj = Object.create(null)
  108. for (var i = 0; i < methods.length; i++) {
  109. var method = methods[i].toUpperCase()
  110. obj[method] = true
  111. }
  112. return obj
  113. }
  114. /**
  115. * Get the token secret from the request.
  116. *
  117. * @param {IncomingMessage} req
  118. * @param {Object} [cookie]
  119. * @api private
  120. */
  121. function getsecret(req, cookie) {
  122. var secret
  123. if (cookie) {
  124. // get secret from cookie
  125. var bag = cookie.signed
  126. ? 'signedCookies'
  127. : 'cookies'
  128. secret = req[bag][cookie.key]
  129. } else if (req.session) {
  130. // get secret from session
  131. secret = req.session.csrfSecret
  132. } else {
  133. throw new Error('misconfigured csrf')
  134. }
  135. return secret
  136. }
  137. /**
  138. * Set a cookie on the HTTP response.
  139. *
  140. * @param {OutgoingMessage} res
  141. * @param {string} name
  142. * @param {string} val
  143. * @param {Object} [options]
  144. * @api private
  145. */
  146. function setcookie(res, name, val, options) {
  147. var data = Cookie.serialize(name, val, options);
  148. var prev = res.getHeader('set-cookie') || [];
  149. var header = Array.isArray(prev) ? prev.concat(data)
  150. : Array.isArray(data) ? [prev].concat(data)
  151. : [prev, data];
  152. res.setHeader('set-cookie', header);
  153. }
  154. /**
  155. * Set the token secret on the request.
  156. *
  157. * @param {IncomingMessage} req
  158. * @param {OutgoingMessage} res
  159. * @param {string} val
  160. * @param {Object} [cookie]
  161. * @api private
  162. */
  163. function setsecret(req, res, val, cookie) {
  164. if (cookie) {
  165. // set secret on cookie
  166. if (cookie.signed) {
  167. var secret = req.secret
  168. if (!secret) {
  169. throw new Error('cookieParser("secret") required for signed cookies')
  170. }
  171. val = 's:' + sign(val, secret)
  172. }
  173. setcookie(res, cookie.key, val, cookie);
  174. } else if (req.session) {
  175. // set secret on session
  176. req.session.csrfSecret = val
  177. } else {
  178. /* istanbul ignore next: should never actually run */
  179. throw new Error('misconfigured csrf')
  180. }
  181. }
  182. /**
  183. * Verify the token.
  184. *
  185. * @param {IncomingMessage} req
  186. * @param {Object} tokens
  187. * @param {string} secret
  188. * @param {string} val
  189. * @api private
  190. */
  191. function verifytoken(req, tokens, secret, val) {
  192. // valid token
  193. if (!tokens.verify(secret, val)) {
  194. throw createError(403, 'invalid csrf token', {
  195. code: 'EBADCSRFTOKEN'
  196. });
  197. }
  198. }