diff --git a/package.json b/package.json index ad097e0..7186625 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "cookie-parser": "^1.4.3", "core-js": "^2.4.1", "express": "^4.14.0", + "express-bouncer": "^0.2.0", "express-graphql": "^0.6.1", "express-jwt": "^5.1.0", "fastclick": "^1.0.6", diff --git a/src/components/RsvpButton/RsvpButton.js b/src/components/RsvpButton/RsvpButton.js index c353b46..cc018e0 100644 --- a/src/components/RsvpButton/RsvpButton.js +++ b/src/components/RsvpButton/RsvpButton.js @@ -29,7 +29,6 @@ class RsvpButton extends React.Component { } handleRsvpBlur = () => { - this.setState({ inputInvalidMsg: null }); if (this.props.focusCallback) { this.props.focusCallback(false); } @@ -54,6 +53,18 @@ class RsvpButton extends React.Component { }).then(data => { if (data.status === 200) { 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 { this.setState({ inputInvalid: true, @@ -111,6 +122,7 @@ class RsvpButton extends React.Component { onFocus={this.handleRsvpFocus} onBlur={this.handleRsvpBlur} onChange={this.handleRsvpChange} + disabled={this.state.inputDisabled} id="code" type="text" name="code" diff --git a/src/server.js b/src/server.js index 2a02cc8..a9a2937 100644 --- a/src/server.js +++ b/src/server.js @@ -10,6 +10,7 @@ import React from 'react'; import ReactDOM from 'react-dom/server'; import UniversalRouter from 'universal-router'; import PrettyError from 'pretty-error'; +import Bouncer from 'express-bouncer'; import App from './components/App'; import Html from './components/Html'; import { ErrorPageWithoutStyle } from './routes/error/ErrorPage'; @@ -28,6 +29,17 @@ import { sendSlackMsgWithDebounce } from './core/slack'; const app = express(); 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 // user agent is not known. @@ -84,15 +96,15 @@ app.use('/graphql', expressGraphQL(req => ({ // ----------------------------------------------------------------------------- // -app.get('/:password', (req, res, next) => { - if (req.params.password.length !== 4) { +app.get('/:code', (req, res, next) => { + if (req.params.code.length !== 4) { // Not a password next(); } else { - Person.findAll({ where: { password: req.params.password } }) + Person.findAll({ where: { password: req.params.code } }) .then(data => { if (data.length > 0) { - res.redirect(`/rsvp/${req.params.password}`); + res.redirect(`/rsvp/${req.params.code}`); } else { next(); } @@ -107,6 +119,19 @@ app.post('/rsvp', (req, res) => { 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) => { try { const update = {}; diff --git a/yarn.lock b/yarn.lock index c0f0094..73e87c5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2513,6 +2513,10 @@ expand-range@^1.8.1: dependencies: 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: version "0.6.1" 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" 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" 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" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"