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