diff --git a/api-server/src/server/middleware.json b/api-server/src/server/middleware.json index 69a47f4402..2788a0de62 100644 --- a/api-server/src/server/middleware.json +++ b/api-server/src/server/middleware.json @@ -35,7 +35,6 @@ "helmet#noSniff": {}, "helmet#frameguard": {}, "./middlewares/csurf": {}, - "./middlewares/csurf-set-cookie": {}, "./middlewares/constant-headers": {}, "./middlewares/csp": {}, "./middlewares/flash-cheaters": {}, @@ -44,7 +43,6 @@ "files": {}, "final:after": { "./middlewares/sentry-error-handler": {}, - "./middlewares/csurf-error-handler": {}, "./middlewares/error-handlers": {}, "strong-error-handler": { "params": { diff --git a/api-server/src/server/middlewares/csurf-error-handler.js b/api-server/src/server/middlewares/csurf-error-handler.js deleted file mode 100644 index 290dbb01f5..0000000000 --- a/api-server/src/server/middlewares/csurf-error-handler.js +++ /dev/null @@ -1,12 +0,0 @@ -import { csrfOptions } from './csurf.js'; - -export default function csrfErrorHandler() { - return function (err, req, res, next) { - if (err.code === 'EBADCSRFTOKEN') { - // use the middleware to generate a token. The client sends this back via - // a header - res.cookie('csrf_token', req.csrfToken(), csrfOptions); - } - next(err); - }; -} diff --git a/api-server/src/server/middlewares/csurf-set-cookie.js b/api-server/src/server/middlewares/csurf-set-cookie.js deleted file mode 100644 index 83da1361fd..0000000000 --- a/api-server/src/server/middlewares/csurf-set-cookie.js +++ /dev/null @@ -1,13 +0,0 @@ -import { csrfOptions } from './csurf.js'; - -export default function setCSRFCookie() { - return function (req, res, next) { - // not all paths require a CSRF token, so the function may not be available. - if (req.csrfToken) { - // use the middleware to generate a token. The client sends this back via - // a header - res.cookie('csrf_token', req.csrfToken(), csrfOptions); - } - next(); - }; -} diff --git a/api-server/src/server/middlewares/csurf.js b/api-server/src/server/middlewares/csurf.js index 126f725d57..9c790509c5 100644 --- a/api-server/src/server/middlewares/csurf.js +++ b/api-server/src/server/middlewares/csurf.js @@ -1,14 +1,12 @@ import csurf from 'csurf'; -export const csrfOptions = { - domain: process.env.COOKIE_DOMAIN || 'localhost', - sameSite: 'strict', - secure: process.env.FREECODECAMP_NODE_ENV === 'production' -}; - export default function getCsurf() { const protection = csurf({ - cookie: csrfOptions + cookie: { + domain: process.env.COOKIE_DOMAIN || 'localhost', + sameSite: 'strict', + secure: process.env.FREECODECAMP_NODE_ENV === 'production' + } }); return function csrf(req, res, next) { const { path } = req; @@ -16,10 +14,8 @@ export default function getCsurf() { // eslint-disable-next-line max-len /^\/hooks\/update-paypal$/.test(path) ) { - next(); - } else { - // add the middleware - protection(req, res, next); + return next(); } + return protection(req, res, next); }; } diff --git a/api-server/src/server/utils/getSetAccessToken.js b/api-server/src/server/utils/getSetAccessToken.js index a7ffe8bfa9..866a69710b 100644 --- a/api-server/src/server/utils/getSetAccessToken.js +++ b/api-server/src/server/utils/getSetAccessToken.js @@ -64,7 +64,6 @@ export function removeCookies(req, res) { res.clearCookie('access_token', config); res.clearCookie('userId', config); res.clearCookie('_csrf', config); - res.clearCookie('csrf_token', config); return; } diff --git a/client/gatsby-browser.js b/client/gatsby-browser.js index fe1862a515..97bb014d75 100644 --- a/client/gatsby-browser.js +++ b/client/gatsby-browser.js @@ -2,7 +2,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Provider } from 'react-redux'; import { I18nextProvider } from 'react-i18next'; -import cookies from 'browser-cookies'; import i18n from './i18n/config'; import { createStore } from './src/redux/createStore'; @@ -28,9 +27,3 @@ wrapRootElement.propTypes = { export const wrapPageElement = layoutSelector; export const disableCorePrefetching = () => true; - -export const onClientEntry = () => { - // purge the _csrf cookie, rather than relying what the browser decides a - // Session duration is - cookies.erase('_csrf'); -}; diff --git a/client/package-lock.json b/client/package-lock.json index ecff36892e..bc2c2c733b 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -7131,6 +7131,16 @@ "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==" }, + "csrf": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csrf/-/csrf-3.1.0.tgz", + "integrity": "sha512-uTqEnCvWRk042asU6JtapDTcJeeailFy4ydOQS28bj1hcLnYRiqi8SsD2jS412AY1I/4qdOwWZun774iqywf9w==", + "requires": { + "rndm": "1.2.0", + "tsscmp": "1.0.6", + "uid-safe": "2.1.5" + } + }, "css": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz", @@ -20946,6 +20956,11 @@ "ret": "~0.1.10" } }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -22361,6 +22376,11 @@ "inherits": "^2.0.1" } }, + "rndm": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz", + "integrity": "sha1-8z/pz7Urv9UgqhgyO8ZdsRCht2w=" + }, "rst-selector-parser": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", @@ -24423,6 +24443,11 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, + "tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==" + }, "tsutils": { "version": "3.19.1", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.19.1.tgz", @@ -24522,6 +24547,14 @@ "typescript-compare": "^0.0.2" } }, + "uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "requires": { + "random-bytes": "~1.0.0" + } + }, "unbox-primitive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.0.tgz", diff --git a/client/package.json b/client/package.json index bd672162d9..f44ff1a80f 100644 --- a/client/package.json +++ b/client/package.json @@ -62,6 +62,7 @@ "buffer": "6.0.3", "chai": "4.3.4", "crypto-browserify": "3.12.0", + "csrf": "3.1.0", "date-fns": "2.21.3", "enzyme": "3.11.0", "enzyme-adapter-react-16": "1.15.6", diff --git a/client/src/utils/ajax.js b/client/src/utils/ajax.js index 386fa38074..d9fb422c02 100644 --- a/client/src/utils/ajax.js +++ b/client/src/utils/ajax.js @@ -1,20 +1,22 @@ import envData from '../../../config/env.json'; import axios from 'axios'; +import Tokens from 'csrf'; import cookies from 'browser-cookies'; const { apiLocation } = envData; const base = apiLocation; +const tokens = new Tokens(); axios.defaults.withCredentials = true; -// csrf_token is passed to the client as a cookie. The client must send -// this back as a header. +// _csrf is passed to the client as a cookie. Tokens are sent back to the server +// via headers: function setCSRFTokens() { - const csrfToken = typeof window !== 'undefined' && cookies.get('csrf_token'); - if (!csrfToken) return; - axios.defaults.headers.post['CSRF-Token'] = csrfToken; - axios.defaults.headers.put['CSRF-Token'] = csrfToken; + const _csrf = typeof window !== 'undefined' && cookies.get('_csrf'); + if (!_csrf) return; + axios.defaults.headers.post['CSRF-Token'] = tokens.create(_csrf); + axios.defaults.headers.put['CSRF-Token'] = tokens.create(_csrf); } function get(path) {