Added rate limiter on customer forgotten password

master
Mark Moffat 2019-12-16 17:03:51 +10:30
parent c086c942bc
commit c992673bc6
6 changed files with 59 additions and 24 deletions

17
package-lock.json generated
View File

@ -3598,6 +3598,11 @@
"promise": "^8.0.2"
}
},
"express-rate-limit": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-5.0.0.tgz",
"integrity": "sha512-dhT57wqxfqmkOi4HM7NuT4Gd7gbUgSK2ocG27Y6lwm8lbOAw9XQfeANawGq8wLDtlGPO1ZgDj0HmKsykTxfFAg=="
},
"express-session": {
"version": "1.17.0",
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.0.tgz",
@ -5058,9 +5063,9 @@
}
},
"handlebars": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.4.5.tgz",
"integrity": "sha512-0Ce31oWVB7YidkaTq33ZxEbN+UDxMMgThvCe8ptgQViymL5DPis9uLdTA13MiRPhgvqyxIegugrP97iK3JeBHg==",
"version": "4.5.3",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.3.tgz",
"integrity": "sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==",
"requires": {
"neo-async": "^2.6.0",
"optimist": "^0.6.1",
@ -9223,9 +9228,9 @@
"integrity": "sha512-kMBmblijHJXyOpKzgDhKx9INYU4u4E1RPMB0HqmKSgWG8vEcf3exEfLh4FFfzd3xdQOw9EuIy/cP0akY6rHopQ=="
},
"uglify-js": {
"version": "3.6.4",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.4.tgz",
"integrity": "sha512-9Yc2i881pF4BPGhjteCXQNaXx1DCwm3dtOyBaG2hitHjLWOczw/ki8vD1bqyT3u6K0Ms/FpCShkmfg+FtlOfYA==",
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.7.2.tgz",
"integrity": "sha512-uhRwZcANNWVLrxLfNFEdltoPNhECUR3lc+UdJoG9CBpMcSnKyWA94tc3eAujB1GcMY5Uwq8ZMp4qWpxWYDQmaA==",
"optional": true,
"requires": {
"commander": "~2.20.3",

View File

@ -38,6 +38,7 @@
"cookie-parser": "^1.4.4",
"express": "^4.17.1",
"express-handlebars": "^3.1.0",
"express-rate-limit": "^5.0.0",
"express-session": "^1.17.0",
"glob": "^7.1.5",
"helmet": "^3.21.2",

View File

@ -143,6 +143,29 @@ $(document).ready(function (){
});
});
$('#customerForgotten').validator().on('submit', function(e){
if(!e.isDefaultPrevented()){
e.preventDefault();
$.ajax({
method: 'POST',
url: '/customer/forgotten_action',
data: {
email: $('#email').val()
}
})
.done(function(msg){
showNotification(msg.message, 'success');
})
.fail(function(msg){
if(msg.message){
showNotification(msg.responseJSON.message, 'danger');
return;
}
showNotification(msg.responseText, 'danger');
});
}
});
$('#createCustomerAccount').validator().on('click', function(e){
e.preventDefault();
if($('#shipping-form').validator('validate').has('.has-error').length === 0){

File diff suppressed because one or more lines are too long

View File

@ -4,10 +4,16 @@ const colors = require('colors');
const randtoken = require('rand-token');
const bcrypt = require('bcryptjs');
const common = require('../lib/common');
const rateLimit = require('express-rate-limit');
const { indexCustomers } = require('../lib/indexing');
const { validateJson } = require('../lib/schema');
const { restrict } = require('../lib/auth');
const apiLimiter = rateLimit({
windowMs: 300000, // 5 minutes
max: 5
});
// insert a customer
router.post('/customer/create', async (req, res) => {
const db = req.app.db;
@ -281,21 +287,21 @@ router.get('/customer/forgotten', (req, res) => {
});
// forgotten password
router.post('/customer/forgotten_action', async (req, res) => {
router.post('/customer/forgotten_action', apiLimiter, async (req, res) => {
const db = req.app.db;
const config = req.app.config;
const passwordToken = randtoken.generate(30);
// find the user
const customer = await db.customers.findOne({ email: req.body.email });
// if we have a customer, set a token, expiry and email it
if(!customer){
req.session.message = 'Account does not exist';
req.session.message_type = 'danger';
res.redirect('/customer/forgotten');
return;
}
try{
if(!customer){
// if don't have an email on file, silently fail
res.status(200).json({
message: 'If your account exists, a password reset has been sent to your email'
});
return;
}
const tokenExpiry = Date.now() + 3600000;
await db.customers.updateOne({ email: req.body.email }, { $set: { resetToken: passwordToken, resetTokenExpiry: tokenExpiry } }, { multi: false });
// send forgotten password email
@ -311,13 +317,13 @@ router.post('/customer/forgotten_action', async (req, res) => {
// send the email with token to the user
// TODO: Should fix this to properly handle result
common.sendEmail(mailOpts.to, mailOpts.subject, mailOpts.body);
req.session.message = 'An email has been sent to ' + req.body.email + ' with further instructions';
req.session.message_type = 'success';
res.redirect('/customer/forgotten');
res.status(200).json({
message: 'If your account exists, a password reset has been sent to your email'
});
}catch(ex){
req.session.message = 'Account does not exist';
req.session.message_type = 'danger';
res.redirect('/customer/forgotten');
res.status(400).json({
message: 'Password reset failed.'
});
}
});

View File

@ -1,9 +1,9 @@
<div class="col-md-offset-4 col-md-4 col-lg-offset-4 col-lg-4" style="padding-top: 100px" >
<form class="form-signin" action="/{{route}}/forgotten_action" method="post" role="form" data-toggle="validator">
<form class="form-signin" id="customerForgotten" data-toggle="validator">
<h2 class="form-signin-heading">{{ @root.__ "Please enter your email address" }}</h2>
<div class="form-group">
<input type="email" name="email" class="form-control" placeholder="email address" required autofocus>
<input type="email" id="email" class="form-control" placeholder="email address" required autofocus>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit">{{ @root.__ "Reset" }}</button>
<button class="btn btn-lg btn-primary btn-block">{{ @root.__ "Reset" }}</button>
</form>
</div>