Add bouncer to /rsvp/* routes to hinder brute force attacks
This commit is contained in:
@@ -15,6 +15,7 @@
|
|||||||
"cookie-parser": "^1.4.3",
|
"cookie-parser": "^1.4.3",
|
||||||
"core-js": "^2.4.1",
|
"core-js": "^2.4.1",
|
||||||
"express": "^4.14.0",
|
"express": "^4.14.0",
|
||||||
|
"express-bouncer": "^0.2.0",
|
||||||
"express-graphql": "^0.6.1",
|
"express-graphql": "^0.6.1",
|
||||||
"express-jwt": "^5.1.0",
|
"express-jwt": "^5.1.0",
|
||||||
"fastclick": "^1.0.6",
|
"fastclick": "^1.0.6",
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ class RsvpButton extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleRsvpBlur = () => {
|
handleRsvpBlur = () => {
|
||||||
this.setState({ inputInvalidMsg: null });
|
|
||||||
if (this.props.focusCallback) {
|
if (this.props.focusCallback) {
|
||||||
this.props.focusCallback(false);
|
this.props.focusCallback(false);
|
||||||
}
|
}
|
||||||
@@ -54,6 +53,18 @@ class RsvpButton extends React.Component {
|
|||||||
}).then(data => {
|
}).then(data => {
|
||||||
if (data.status === 200) {
|
if (data.status === 200) {
|
||||||
location.href = data.url;
|
location.href = data.url;
|
||||||
|
} else if (data.status === 429) {
|
||||||
|
this.setState({
|
||||||
|
inputInvalid: true,
|
||||||
|
inputInvalidMsg: 'You have made too many incorrect attempts. Please try again later.',
|
||||||
|
inputDisabled: true,
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
this.setState({
|
||||||
|
inputInvalid: false,
|
||||||
|
inputDisabled: false,
|
||||||
|
});
|
||||||
|
}, 10 * 60 * 1000);
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
inputInvalid: true,
|
inputInvalid: true,
|
||||||
@@ -111,6 +122,7 @@ class RsvpButton extends React.Component {
|
|||||||
onFocus={this.handleRsvpFocus}
|
onFocus={this.handleRsvpFocus}
|
||||||
onBlur={this.handleRsvpBlur}
|
onBlur={this.handleRsvpBlur}
|
||||||
onChange={this.handleRsvpChange}
|
onChange={this.handleRsvpChange}
|
||||||
|
disabled={this.state.inputDisabled}
|
||||||
id="code"
|
id="code"
|
||||||
type="text"
|
type="text"
|
||||||
name="code"
|
name="code"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import React from 'react';
|
|||||||
import ReactDOM from 'react-dom/server';
|
import ReactDOM from 'react-dom/server';
|
||||||
import UniversalRouter from 'universal-router';
|
import UniversalRouter from 'universal-router';
|
||||||
import PrettyError from 'pretty-error';
|
import PrettyError from 'pretty-error';
|
||||||
|
import Bouncer from 'express-bouncer';
|
||||||
import App from './components/App';
|
import App from './components/App';
|
||||||
import Html from './components/Html';
|
import Html from './components/Html';
|
||||||
import { ErrorPageWithoutStyle } from './routes/error/ErrorPage';
|
import { ErrorPageWithoutStyle } from './routes/error/ErrorPage';
|
||||||
@@ -28,6 +29,17 @@ import { sendSlackMsgWithDebounce } from './core/slack';
|
|||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
Rollbar.init();
|
Rollbar.init();
|
||||||
|
const bouncer = Bouncer(10 * 60 * 1000, 60 * 60 * 1000, 5);
|
||||||
|
bouncer.blocked = (req, res, next, remaining) => {
|
||||||
|
let time = Math.ceil(remaining / 1000);
|
||||||
|
let suffix = ' seconds';
|
||||||
|
if (time > 120) {
|
||||||
|
time = Math.ceil(time / 60);
|
||||||
|
suffix = ' minutes';
|
||||||
|
}
|
||||||
|
const body = `You have made too many incorrect attempts. Please wait ${time} ${suffix}.`;
|
||||||
|
res.status(429).send(body);
|
||||||
|
};
|
||||||
|
|
||||||
// Tell any CSS tooling (such as Material UI) to use all vendor prefixes if the
|
// Tell any CSS tooling (such as Material UI) to use all vendor prefixes if the
|
||||||
// user agent is not known.
|
// user agent is not known.
|
||||||
@@ -84,15 +96,15 @@ app.use('/graphql', expressGraphQL(req => ({
|
|||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
//
|
//
|
||||||
|
|
||||||
app.get('/:password', (req, res, next) => {
|
app.get('/:code', (req, res, next) => {
|
||||||
if (req.params.password.length !== 4) {
|
if (req.params.code.length !== 4) {
|
||||||
// Not a password
|
// Not a password
|
||||||
next();
|
next();
|
||||||
} else {
|
} else {
|
||||||
Person.findAll({ where: { password: req.params.password } })
|
Person.findAll({ where: { password: req.params.code } })
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.length > 0) {
|
if (data.length > 0) {
|
||||||
res.redirect(`/rsvp/${req.params.password}`);
|
res.redirect(`/rsvp/${req.params.code}`);
|
||||||
} else {
|
} else {
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
@@ -107,6 +119,19 @@ app.post('/rsvp', (req, res) => {
|
|||||||
res.redirect(`/rsvp/${req.body.code}`);
|
res.redirect(`/rsvp/${req.body.code}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.get('/rsvp/:code', bouncer.block, (req, res, next) => {
|
||||||
|
Person.findAll({ where: { password: req.params.code } })
|
||||||
|
.then(data => {
|
||||||
|
if (data.length > 0) {
|
||||||
|
bouncer.reset(req);
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
}).catch((err) => {
|
||||||
|
Rollbar.handleError(err);
|
||||||
|
next(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
app.post('/rsvp/save', (req, res) => {
|
app.post('/rsvp/save', (req, res) => {
|
||||||
try {
|
try {
|
||||||
const update = {};
|
const update = {};
|
||||||
|
|||||||
@@ -2513,6 +2513,10 @@ expand-range@^1.8.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
fill-range "^2.1.0"
|
fill-range "^2.1.0"
|
||||||
|
|
||||||
|
express-bouncer@^0.2.0:
|
||||||
|
version "0.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/express-bouncer/-/express-bouncer-0.2.0.tgz#392058966f6dfb991e7b651a1a3f0a363193cb04"
|
||||||
|
|
||||||
express-graphql@^0.6.1:
|
express-graphql@^0.6.1:
|
||||||
version "0.6.1"
|
version "0.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/express-graphql/-/express-graphql-0.6.1.tgz#cd9d144ac4d191b34a4261ac3a56fa407d5e19e8"
|
resolved "https://registry.yarnpkg.com/express-graphql/-/express-graphql-0.6.1.tgz#cd9d144ac4d191b34a4261ac3a56fa407d5e19e8"
|
||||||
@@ -4042,11 +4046,11 @@ lodash@^3.10.1:
|
|||||||
version "3.10.1"
|
version "3.10.1"
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
|
||||||
|
|
||||||
lodash@^4.0.0, lodash@^4.16.4, lodash@^4.17.2, lodash@^4.6.1:
|
lodash@^4.0.0, lodash@^4.15.0, lodash@^4.16.4, lodash@^4.17.2, lodash@^4.6.1:
|
||||||
version "4.17.2"
|
version "4.17.2"
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.2.tgz#34a3055babe04ce42467b607d700072c7ff6bf42"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.2.tgz#34a3055babe04ce42467b607d700072c7ff6bf42"
|
||||||
|
|
||||||
lodash@^4.1.0, lodash@^4.15.0, lodash@^4.17.4, lodash@^4.3.0:
|
lodash@^4.1.0, lodash@^4.17.4, lodash@^4.3.0:
|
||||||
version "4.17.4"
|
version "4.17.4"
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user