123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- /*!
- * csurf
- * Copyright(c) 2011 Sencha Inc.
- * Copyright(c) 2014 Jonathan Ong
- * Copyright(c) 2014 Douglas Christopher Wilson
- * MIT Licensed
- */
- /**
- * Module dependencies.
- */
- var Cookie = require('cookie');
- var csrfTokens = require('csrf');
- var createError = require('http-errors');
- var sign = require('cookie-signature').sign;
- /**
- * CSRF protection middleware.
- *
- * This middleware adds a `req.csrfToken()` function to make a token
- * which should be added to requests which mutate
- * state, within a hidden form field, query-string etc. This
- * token is validated against the visitor's session.
- *
- * @param {Object} options
- * @return {Function} middleware
- * @api public
- */
- module.exports = function csurf(options) {
- options = options || {};
- // get cookie options
- var cookie = options.cookie !== true
- ? options.cookie || undefined
- : {}
- // get value getter
- var value = options.value || defaultValue
- // token repo
- var tokens = csrfTokens(options);
- // default cookie key
- if (cookie && !cookie.key) {
- cookie.key = '_csrf'
- }
- // ignored methods
- var ignoreMethods = options.ignoreMethods === undefined
- ? ['GET', 'HEAD', 'OPTIONS']
- : options.ignoreMethods
- if (!Array.isArray(ignoreMethods)) {
- throw new TypeError('option ignoreMethods must be an array')
- }
- // generate lookup
- var ignoreMethod = getIgnoredMethods(ignoreMethods)
- return function csrf(req, res, next) {
- var secret = getsecret(req, cookie)
- var token
- // lazy-load token getter
- req.csrfToken = function csrfToken() {
- var sec = !cookie
- ? getsecret(req, cookie)
- : secret
- // use cached token if secret has not changed
- if (token && sec === secret) {
- return token
- }
- // generate & set new secret
- if (sec === undefined) {
- sec = tokens.secretSync()
- setsecret(req, res, sec, cookie)
- }
- // update changed secret
- secret = sec
- // create new token
- token = tokens.create(secret)
- return token
- }
- // generate & set secret
- if (!secret) {
- secret = tokens.secretSync()
- setsecret(req, res, secret, cookie)
- }
- // verify the incoming token
- if (!ignoreMethod[req.method]) {
- verifytoken(req, tokens, secret, value(req))
- }
- next()
- }
- };
- /**
- * Default value function, checking the `req.body`
- * and `req.query` for the CSRF token.
- *
- * @param {IncomingMessage} req
- * @return {String}
- * @api private
- */
- function defaultValue(req) {
- return (req.body && req.body._csrf)
- || (req.query && req.query._csrf)
- || (req.headers['x-csrf-token'])
- || (req.headers['x-xsrf-token']);
- }
- /**
- * Get a lookup of ignored methods.
- *
- * @param {array} methods
- * @returns {object}
- * @api private
- */
- function getIgnoredMethods(methods) {
- var obj = Object.create(null)
- for (var i = 0; i < methods.length; i++) {
- var method = methods[i].toUpperCase()
- obj[method] = true
- }
- return obj
- }
- /**
- * Get the token secret from the request.
- *
- * @param {IncomingMessage} req
- * @param {Object} [cookie]
- * @api private
- */
- function getsecret(req, cookie) {
- var secret
- if (cookie) {
- // get secret from cookie
- var bag = cookie.signed
- ? 'signedCookies'
- : 'cookies'
- secret = req[bag][cookie.key]
- } else if (req.session) {
- // get secret from session
- secret = req.session.csrfSecret
- } else {
- throw new Error('misconfigured csrf')
- }
- return secret
- }
- /**
- * Set a cookie on the HTTP response.
- *
- * @param {OutgoingMessage} res
- * @param {string} name
- * @param {string} val
- * @param {Object} [options]
- * @api private
- */
- function setcookie(res, name, val, options) {
- var data = Cookie.serialize(name, val, options);
- var prev = res.getHeader('set-cookie') || [];
- var header = Array.isArray(prev) ? prev.concat(data)
- : Array.isArray(data) ? [prev].concat(data)
- : [prev, data];
- res.setHeader('set-cookie', header);
- }
- /**
- * Set the token secret on the request.
- *
- * @param {IncomingMessage} req
- * @param {OutgoingMessage} res
- * @param {string} val
- * @param {Object} [cookie]
- * @api private
- */
- function setsecret(req, res, val, cookie) {
- if (cookie) {
- // set secret on cookie
- if (cookie.signed) {
- var secret = req.secret
- if (!secret) {
- throw new Error('cookieParser("secret") required for signed cookies')
- }
- val = 's:' + sign(val, secret)
- }
- setcookie(res, cookie.key, val, cookie);
- } else if (req.session) {
- // set secret on session
- req.session.csrfSecret = val
- } else {
- /* istanbul ignore next: should never actually run */
- throw new Error('misconfigured csrf')
- }
- }
- /**
- * Verify the token.
- *
- * @param {IncomingMessage} req
- * @param {Object} tokens
- * @param {string} secret
- * @param {string} val
- * @api private
- */
- function verifytoken(req, tokens, secret, val) {
- // valid token
- if (!tokens.verify(secret, val)) {
- throw createError(403, 'invalid csrf token', {
- code: 'EBADCSRFTOKEN'
- });
- }
- }
|