CSRF/XSS protection (#374)
* /api endpoints only allows requests with application/json Content-Type Otherwise sends 406 Unacceptable * 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.
This commit is contained in:
parent
4476405021
commit
6cbc376d6e
3 changed files with 34 additions and 7 deletions
|
@ -71,6 +71,7 @@
|
||||||
"cookie-parser": "^1.4.1",
|
"cookie-parser": "^1.4.1",
|
||||||
"cors": "^2.8.1",
|
"cors": "^2.8.1",
|
||||||
"csslint": "^0.10.0",
|
"csslint": "^0.10.0",
|
||||||
|
"csurf": "^1.9.0",
|
||||||
"decomment": "^0.8.7",
|
"decomment": "^0.8.7",
|
||||||
"dotenv": "^2.0.0",
|
"dotenv": "^2.0.0",
|
||||||
"dropzone": "^4.3.0",
|
"dropzone": "^4.3.0",
|
||||||
|
|
|
@ -7,6 +7,7 @@ import session from 'express-session';
|
||||||
import connectMongo from 'connect-mongo';
|
import connectMongo from 'connect-mongo';
|
||||||
import passport from 'passport';
|
import passport from 'passport';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import csurf from 'csurf';
|
||||||
|
|
||||||
// Webpack Requirements
|
// Webpack Requirements
|
||||||
import webpack from 'webpack';
|
import webpack from 'webpack';
|
||||||
|
@ -23,6 +24,7 @@ import files from './routes/file.routes';
|
||||||
import aws from './routes/aws.routes';
|
import aws from './routes/aws.routes';
|
||||||
import serverRoutes from './routes/server.routes';
|
import serverRoutes from './routes/server.routes';
|
||||||
import embedRoutes from './routes/embed.routes';
|
import embedRoutes from './routes/embed.routes';
|
||||||
|
import { requestsOfTypeJSON } from './utils/requestsOfType';
|
||||||
|
|
||||||
import { renderIndex } from './views/index';
|
import { renderIndex } from './views/index';
|
||||||
import { get404Sketch } from './views/404Page';
|
import { get404Sketch } from './views/404Page';
|
||||||
|
@ -73,18 +75,27 @@ app.use(session({
|
||||||
autoReconnect: true
|
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.initialize());
|
||||||
app.use(passport.session());
|
app.use(passport.session());
|
||||||
app.use('/api', users);
|
app.use('/api', requestsOfTypeJSON(), users);
|
||||||
app.use('/api', sessions);
|
app.use('/api', requestsOfTypeJSON(), sessions);
|
||||||
app.use('/api', projects);
|
app.use('/api', requestsOfTypeJSON(), projects);
|
||||||
app.use('/api', files);
|
app.use('/api', requestsOfTypeJSON(), files);
|
||||||
app.use('/api', aws);
|
app.use('/api', requestsOfTypeJSON(), aws);
|
||||||
// this is supposed to be TEMPORARY -- until i figure out
|
// this is supposed to be TEMPORARY -- until i figure out
|
||||||
// isomorphic rendering
|
// 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', passport.authenticate('github'));
|
||||||
app.get('/auth/github/callback', passport.authenticate('github', { failureRedirect: '/login' }), (req, res) => {
|
app.get('/auth/github/callback', passport.authenticate('github', { failureRedirect: '/login' }), (req, res) => {
|
||||||
res.redirect('/');
|
res.redirect('/');
|
||||||
|
|
15
server/utils/requestsOfType.js
Normal file
15
server/utils/requestsOfType.js
Normal file
|
@ -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');
|
Loading…
Reference in a new issue