2015-05-26 22:04:27 +03:00
|
|
|
// # SpamPrevention Middleware
|
|
|
|
// Usage: spamPrevention
|
|
|
|
// After:
|
|
|
|
// Before:
|
|
|
|
// App: Admin|Blog|API
|
|
|
|
//
|
|
|
|
// Helpers to handle spam detection on signin, forgot password, and protected pages.
|
|
|
|
|
|
|
|
var _ = require('lodash'),
|
2016-10-11 11:36:00 +03:00
|
|
|
errors = require('../../errors'),
|
|
|
|
config = require('../../config'),
|
|
|
|
i18n = require('../../i18n'),
|
2015-05-26 22:04:27 +03:00
|
|
|
loginSecurity = [],
|
|
|
|
forgottenSecurity = [],
|
|
|
|
spamPrevention;
|
|
|
|
|
|
|
|
spamPrevention = {
|
|
|
|
/*jslint unparam:true*/
|
|
|
|
// limit signin requests to ten failed requests per IP per hour
|
2015-05-30 23:18:26 +03:00
|
|
|
signin: function signin(req, res, next) {
|
2015-05-26 22:04:27 +03:00
|
|
|
var currentTime = process.hrtime()[0],
|
|
|
|
remoteAddress = req.connection.remoteAddress,
|
|
|
|
deniedRateLimit = '',
|
|
|
|
ipCount = '',
|
|
|
|
rateSigninPeriod = config.rateSigninPeriod || 3600,
|
|
|
|
rateSigninAttempts = config.rateSigninAttempts || 10;
|
|
|
|
|
|
|
|
if (req.body.username && req.body.grant_type === 'password') {
|
|
|
|
loginSecurity.push({ip: remoteAddress, time: currentTime, email: req.body.username});
|
2016-10-17 13:45:50 +03:00
|
|
|
} else if (req.body.grant_type === 'refresh_token' || req.body.grant_type === 'authorization_code') {
|
2015-05-26 22:04:27 +03:00
|
|
|
return next();
|
|
|
|
} else {
|
2016-10-06 15:27:35 +03:00
|
|
|
return next(new errors.BadRequestError({message: i18n.t('errors.middleware.spamprevention.noUsername')}));
|
2015-05-26 22:04:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// filter entries that are older than rateSigninPeriod
|
2015-05-30 23:18:26 +03:00
|
|
|
loginSecurity = _.filter(loginSecurity, function filter(logTime) {
|
2015-05-26 22:04:27 +03:00
|
|
|
return (logTime.time + rateSigninPeriod > currentTime);
|
|
|
|
});
|
|
|
|
|
|
|
|
// check number of tries per IP address
|
|
|
|
ipCount = _.chain(loginSecurity).countBy('ip').value();
|
|
|
|
deniedRateLimit = (ipCount[remoteAddress] > rateSigninAttempts);
|
|
|
|
|
|
|
|
if (deniedRateLimit) {
|
2016-10-06 15:27:35 +03:00
|
|
|
return next(new errors.TooManyRequestsError({
|
|
|
|
message: i18n.t('errors.middleware.spamprevention.tooManyAttempts') + rateSigninPeriod === 3600 ? i18n.t('errors.middleware.spamprevention.waitOneHour') : i18n.t('errors.middleware.spamprevention.tryAgainLater'),
|
|
|
|
context: i18n.t('errors.middleware.spamprevention.tooManySigninAttempts.error', {rateSigninAttempts: rateSigninAttempts, rateSigninPeriod: rateSigninPeriod}),
|
|
|
|
help: i18n.t('errors.middleware.spamprevention.tooManySigninAttempts.context')
|
|
|
|
}));
|
2015-05-26 22:04:27 +03:00
|
|
|
}
|
|
|
|
next();
|
|
|
|
},
|
|
|
|
|
|
|
|
// limit forgotten password requests to five requests per IP per hour for different email addresses
|
|
|
|
// limit forgotten password requests to five requests per email address
|
2016-11-07 14:18:50 +03:00
|
|
|
// @TODO: add validation check to validation middleware
|
2015-05-30 23:18:26 +03:00
|
|
|
forgotten: function forgotten(req, res, next) {
|
2016-11-07 14:18:50 +03:00
|
|
|
if (!req.body.passwordreset) {
|
|
|
|
return next(new errors.BadRequestError({
|
|
|
|
message: i18n.t('errors.api.utils.noRootKeyProvided', {docName: 'passwordreset'})
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2015-05-26 22:04:27 +03:00
|
|
|
var currentTime = process.hrtime()[0],
|
|
|
|
remoteAddress = req.connection.remoteAddress,
|
|
|
|
rateForgottenPeriod = config.rateForgottenPeriod || 3600,
|
|
|
|
rateForgottenAttempts = config.rateForgottenAttempts || 5,
|
|
|
|
email = req.body.passwordreset[0].email,
|
|
|
|
ipCount = '',
|
|
|
|
deniedRateLimit = '',
|
|
|
|
deniedEmailRateLimit = '',
|
2015-05-30 23:18:26 +03:00
|
|
|
index = _.findIndex(forgottenSecurity, function findIndex(logTime) {
|
2015-05-26 22:04:27 +03:00
|
|
|
return (logTime.ip === remoteAddress && logTime.email === email);
|
|
|
|
});
|
|
|
|
|
|
|
|
if (email) {
|
|
|
|
if (index !== -1) {
|
|
|
|
forgottenSecurity[index].count = forgottenSecurity[index].count + 1;
|
|
|
|
} else {
|
|
|
|
forgottenSecurity.push({ip: remoteAddress, time: currentTime, email: email, count: 0});
|
|
|
|
}
|
|
|
|
} else {
|
2016-10-06 15:27:35 +03:00
|
|
|
return next(new errors.BadRequestError({message: i18n.t('errors.middleware.spamprevention.noEmail')}));
|
2015-05-26 22:04:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// filter entries that are older than rateForgottenPeriod
|
2015-05-30 23:18:26 +03:00
|
|
|
forgottenSecurity = _.filter(forgottenSecurity, function filter(logTime) {
|
2015-05-26 22:04:27 +03:00
|
|
|
return (logTime.time + rateForgottenPeriod > currentTime);
|
|
|
|
});
|
|
|
|
|
|
|
|
// check number of tries with different email addresses per IP
|
|
|
|
ipCount = _.chain(forgottenSecurity).countBy('ip').value();
|
|
|
|
deniedRateLimit = (ipCount[remoteAddress] > rateForgottenAttempts);
|
|
|
|
|
|
|
|
if (index !== -1) {
|
|
|
|
deniedEmailRateLimit = (forgottenSecurity[index].count > rateForgottenAttempts);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (deniedEmailRateLimit) {
|
2016-10-06 15:27:35 +03:00
|
|
|
return next(new errors.TooManyRequestsError({
|
|
|
|
message: i18n.t('errors.middleware.spamprevention.tooManyAttempts') + rateForgottenPeriod === 3600 ? i18n.t('errors.middleware.spamprevention.waitOneHour') : i18n.t('errors.middleware.spamprevention.tryAgainLater'),
|
|
|
|
context: i18n.t('errors.middleware.spamprevention.forgottenPasswordEmail.error', {
|
|
|
|
rfa: rateForgottenAttempts,
|
|
|
|
rfp: rateForgottenPeriod
|
|
|
|
}),
|
|
|
|
help: i18n.t('errors.middleware.spamprevention.forgottenPasswordEmail.context')
|
|
|
|
}));
|
2015-05-26 22:04:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (deniedRateLimit) {
|
2016-10-06 15:27:35 +03:00
|
|
|
return next(new errors.TooManyRequestsError({
|
|
|
|
message: i18n.t('errors.middleware.spamprevention.tooManyAttempts') + rateForgottenPeriod === 3600 ? i18n.t('errors.middleware.spamprevention.waitOneHour') : i18n.t('errors.middleware.spamprevention.tryAgainLater'),
|
|
|
|
context: i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.error', {rfa: rateForgottenAttempts, rfp: rateForgottenPeriod}),
|
|
|
|
help: i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.context')
|
|
|
|
}));
|
2015-05-26 22:04:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
next();
|
|
|
|
},
|
|
|
|
|
2015-05-30 23:18:26 +03:00
|
|
|
resetCounter: function resetCounter(email) {
|
|
|
|
loginSecurity = _.filter(loginSecurity, function filter(logTime) {
|
2015-05-26 22:04:27 +03:00
|
|
|
return (logTime.email !== email);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
module.exports = spamPrevention;
|