diff --git a/package.json b/package.json index edbb976a..61aeb26b 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 79baca8d..b8d19c80 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'; @@ -23,6 +24,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'; @@ -73,18 +75,27 @@ 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', 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); +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('/'); diff --git a/server/utils/requestsOfType.js b/server/utils/requestsOfType.js new file mode 100644 index 00000000..cac9bca4 --- /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');