From ed3b5bf9539a2099e9bc60d69c1490b87d8a34b2 Mon Sep 17 00:00:00 2001 From: Andrew Nicolaou Date: Mon, 26 Jun 2017 16:37:50 +0200 Subject: [PATCH 1/2] /api endpoints only allows requests with application/json Content-Type Otherwise sends 406 Unacceptable --- server/server.js | 11 ++++++----- server/utils/requestsOfType.js | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 server/utils/requestsOfType.js diff --git a/server/server.js b/server/server.js index 79baca8d67..a9d3c704ac 100644 --- a/server/server.js +++ b/server/server.js @@ -23,6 +23,7 @@ import files from './routes/file.routes'; import aws from './routes/aws.routes'; import serverRoutes from './routes/server.routes'; import embedRoutes from './routes/embed.routes'; +import { requestsOfTypeJSON } from './utils/requestsOfType'; import { renderIndex } from './views/index'; import { get404Sketch } from './views/404Page'; @@ -75,11 +76,11 @@ app.use(session({ })); app.use(passport.initialize()); app.use(passport.session()); -app.use('/api', users); -app.use('/api', sessions); -app.use('/api', projects); -app.use('/api', files); -app.use('/api', aws); +app.use('/api', requestsOfTypeJSON(), users); +app.use('/api', requestsOfTypeJSON(), sessions); +app.use('/api', requestsOfTypeJSON(), projects); +app.use('/api', requestsOfTypeJSON(), files); +app.use('/api', requestsOfTypeJSON(), aws); // this is supposed to be TEMPORARY -- until i figure out // isomorphic rendering app.use('/', serverRoutes); diff --git a/server/utils/requestsOfType.js b/server/utils/requestsOfType.js new file mode 100644 index 0000000000..cac9bca4ff --- /dev/null +++ b/server/utils/requestsOfType.js @@ -0,0 +1,15 @@ +/* + express middleware that sends a 406 Unacceptable + response if an incoming request's Content-Type + header does not match `type` +*/ +const requestsOfType = type => (req, res, next) => { + if (req.get('content-type') != null && !req.is(type)) { + return next({ statusCode: 406 }); // 406 UNACCEPTABLE + } + + return next(); +}; + +export default requestsOfType; +export const requestsOfTypeJSON = () => requestsOfType('application/json'); From 0cae81792837f62dfbe018f0557e688b63ed5849 Mon Sep 17 00:00:00 2001 From: Andrew Nicolaou Date: Mon, 26 Jun 2017 18:01:55 +0200 Subject: [PATCH 2/2] Uses CSRF token The CSRF token is sent as the cookie 'XSRF-TOKEN' on all HTML page requests. This token is picked up automatically by axios and sent to the API with all requests as an 'X-XSRF-TOKEN' header. The middleware runs on all routes and verifies that the token matches what's stored in the session. --- package.json | 1 + server/server.js | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 16e0f4144e..569fc489e5 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "cookie-parser": "^1.4.1", "cors": "^2.8.1", "csslint": "^0.10.0", + "csurf": "^1.9.0", "decomment": "^0.8.7", "dotenv": "^2.0.0", "dropzone": "^4.3.0", diff --git a/server/server.js b/server/server.js index a9d3c704ac..b8d19c8018 100644 --- a/server/server.js +++ b/server/server.js @@ -7,6 +7,7 @@ import session from 'express-session'; import connectMongo from 'connect-mongo'; import passport from 'passport'; import path from 'path'; +import csurf from 'csurf'; // Webpack Requirements import webpack from 'webpack'; @@ -74,6 +75,15 @@ app.use(session({ autoReconnect: true }) })); + +// Enables CSRF protection and stores secret in session +app.use(csurf()); +// Middleware to add CSRF token as cookie to some requests +const csrfToken = (req, res, next) => { + res.cookie('XSRF-TOKEN', req.csrfToken()); + next(); +}; + app.use(passport.initialize()); app.use(passport.session()); app.use('/api', requestsOfTypeJSON(), users); @@ -83,9 +93,9 @@ app.use('/api', requestsOfTypeJSON(), files); app.use('/api', requestsOfTypeJSON(), aws); // this is supposed to be TEMPORARY -- until i figure out // isomorphic rendering -app.use('/', serverRoutes); +app.use('/', csrfToken, serverRoutes); -app.use('/', embedRoutes); +app.use('/', csrfToken, embedRoutes); app.get('/auth/github', passport.authenticate('github')); app.get('/auth/github/callback', passport.authenticate('github', { failureRedirect: '/login' }), (req, res) => { res.redirect('/');