2017-03-13 23:07:12 +03:00
|
|
|
var moment = require('moment'),
|
2016-11-08 14:33:19 +03:00
|
|
|
errors = require('../../errors'),
|
|
|
|
config = require('../../config'),
|
|
|
|
spam = config.get('spam') || {},
|
|
|
|
_ = require('lodash'),
|
|
|
|
spamPrivateBlog = spam.private_blog || {},
|
|
|
|
spamGlobalBlock = spam.global_block || {},
|
|
|
|
spamGlobalReset = spam.global_reset || {},
|
|
|
|
spamUserReset = spam.user_reset || {},
|
|
|
|
spamUserLogin = spam.user_login || {},
|
|
|
|
|
|
|
|
i18n = require('../../i18n'),
|
2017-03-13 23:07:12 +03:00
|
|
|
store,
|
2016-11-08 14:33:19 +03:00
|
|
|
handleStoreError,
|
|
|
|
globalBlock,
|
|
|
|
globalReset,
|
2017-03-13 23:07:12 +03:00
|
|
|
privateBlogInstance,
|
|
|
|
globalResetInstance,
|
|
|
|
globalBlockInstance,
|
|
|
|
userLoginInstance,
|
|
|
|
userResetInstance,
|
2016-11-08 14:33:19 +03:00
|
|
|
privateBlog,
|
|
|
|
userLogin,
|
|
|
|
userReset,
|
|
|
|
logging = require('../../logging'),
|
|
|
|
spamConfigKeys = ['freeRetries', 'minWait', 'maxWait', 'lifetime'];
|
|
|
|
|
|
|
|
handleStoreError = function handleStoreError(err) {
|
2017-10-04 12:02:22 +03:00
|
|
|
var customError = new errors.InternalServerError({
|
2016-11-09 22:56:42 +03:00
|
|
|
message: 'Unknown error',
|
|
|
|
err: err.parent ? err.parent : err
|
2016-11-10 14:17:11 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
// see https://github.com/AdamPflug/express-brute/issues/45
|
|
|
|
// express-brute does not always forward a callback
|
|
|
|
// we are using reset as synchronous call, so we have to log the error if it occurs
|
|
|
|
// there is no way to try/catch, because the reset operation happens asynchronous
|
|
|
|
if (!err.next) {
|
|
|
|
logging.error(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
err.next(customError);
|
2016-11-08 14:33:19 +03:00
|
|
|
};
|
2015-05-26 22:04:27 +03:00
|
|
|
|
2016-11-08 14:33:19 +03:00
|
|
|
// This is a global endpoint protection mechanism that will lock an endpoint if there are so many
|
|
|
|
// requests from a single IP
|
|
|
|
// We allow for a generous number of requests here to prevent communites on the same IP bing barred on account of a single suer
|
|
|
|
// Defaults to 50 attempts per hour and locks the endpoint for an hour
|
2017-03-13 23:07:12 +03:00
|
|
|
globalBlock = function globalBlock() {
|
|
|
|
var ExpressBrute = require('express-brute'),
|
|
|
|
BruteKnex = require('brute-knex'),
|
|
|
|
db = require('../../data/db');
|
|
|
|
|
|
|
|
store = store || new BruteKnex({
|
2017-11-01 16:44:54 +03:00
|
|
|
tablename: 'brute',
|
|
|
|
createTable: false,
|
|
|
|
knex: db.knex
|
|
|
|
});
|
2017-03-13 23:07:12 +03:00
|
|
|
|
|
|
|
globalBlockInstance = globalBlockInstance || new ExpressBrute(store,
|
2017-11-01 16:44:54 +03:00
|
|
|
_.extend({
|
|
|
|
attachResetToRequest: false,
|
|
|
|
failCallback: function (req, res, next, nextValidRequestDate) {
|
|
|
|
return next(new errors.TooManyRequestsError({
|
|
|
|
message: 'Too many attempts try again in ' + moment(nextValidRequestDate).fromNow(true),
|
|
|
|
context: i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.error',
|
|
|
|
{rfa: spamGlobalBlock.freeRetries + 1 || 5, rfp: spamGlobalBlock.lifetime || 60 * 60}),
|
|
|
|
help: i18n.t('errors.middleware.spamprevention.tooManyAttempts')
|
|
|
|
}));
|
|
|
|
},
|
|
|
|
handleStoreError: handleStoreError
|
|
|
|
}, _.pick(spamGlobalBlock, spamConfigKeys))
|
|
|
|
);
|
2017-03-13 23:07:12 +03:00
|
|
|
|
|
|
|
return globalBlockInstance;
|
|
|
|
};
|
|
|
|
|
|
|
|
globalReset = function globalReset() {
|
|
|
|
var ExpressBrute = require('express-brute'),
|
|
|
|
BruteKnex = require('brute-knex'),
|
|
|
|
db = require('../../data/db');
|
|
|
|
|
|
|
|
store = store || new BruteKnex({
|
2017-11-01 16:44:54 +03:00
|
|
|
tablename: 'brute',
|
|
|
|
createTable: false,
|
|
|
|
knex: db.knex
|
|
|
|
});
|
2017-03-13 23:07:12 +03:00
|
|
|
|
|
|
|
globalResetInstance = globalResetInstance || new ExpressBrute(store,
|
2017-11-01 16:44:54 +03:00
|
|
|
_.extend({
|
|
|
|
attachResetToRequest: false,
|
|
|
|
failCallback: function (req, res, next, nextValidRequestDate) {
|
|
|
|
// TODO use i18n again
|
|
|
|
return next(new errors.TooManyRequestsError({
|
|
|
|
message: 'Too many attempts try again in ' + moment(nextValidRequestDate).fromNow(true),
|
|
|
|
context: i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.error',
|
|
|
|
{rfa: spamGlobalReset.freeRetries + 1 || 5, rfp: spamGlobalReset.lifetime || 60 * 60}),
|
|
|
|
help: i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.context')
|
|
|
|
}));
|
|
|
|
},
|
|
|
|
handleStoreError: handleStoreError
|
|
|
|
}, _.pick(spamGlobalReset, spamConfigKeys))
|
|
|
|
);
|
2017-03-13 23:07:12 +03:00
|
|
|
|
|
|
|
return globalResetInstance;
|
|
|
|
};
|
2016-11-08 14:33:19 +03:00
|
|
|
|
|
|
|
// Stops login attempts for a user+IP pair with an increasing time period starting from 10 minutes
|
|
|
|
// and rising to a week in a fibonnaci sequence
|
|
|
|
// The user+IP count is reset when on successful login
|
|
|
|
// Default value of 5 attempts per user+IP pair
|
2017-03-13 23:07:12 +03:00
|
|
|
userLogin = function userLogin() {
|
|
|
|
var ExpressBrute = require('express-brute'),
|
|
|
|
BruteKnex = require('brute-knex'),
|
|
|
|
db = require('../../data/db');
|
|
|
|
|
|
|
|
store = store || new BruteKnex({
|
2017-11-01 16:44:54 +03:00
|
|
|
tablename: 'brute',
|
|
|
|
createTable: false,
|
|
|
|
knex: db.knex
|
|
|
|
});
|
2017-03-13 23:07:12 +03:00
|
|
|
|
|
|
|
userLoginInstance = userLoginInstance || new ExpressBrute(store,
|
2017-11-01 16:44:54 +03:00
|
|
|
_.extend({
|
|
|
|
attachResetToRequest: true,
|
|
|
|
failCallback: function (req, res, next, nextValidRequestDate) {
|
|
|
|
return next(new errors.TooManyRequestsError({
|
|
|
|
message: 'Too many sign-in attempts try again in ' + moment(nextValidRequestDate).fromNow(true),
|
|
|
|
// TODO add more options to i18n
|
|
|
|
context: i18n.t('errors.middleware.spamprevention.tooManySigninAttempts.context'),
|
|
|
|
help: i18n.t('errors.middleware.spamprevention.tooManySigninAttempts.context')
|
|
|
|
}));
|
|
|
|
},
|
|
|
|
handleStoreError: handleStoreError
|
|
|
|
}, _.pick(spamUserLogin, spamConfigKeys))
|
|
|
|
);
|
2017-03-13 23:07:12 +03:00
|
|
|
|
|
|
|
return userLoginInstance;
|
|
|
|
};
|
2016-11-08 14:33:19 +03:00
|
|
|
|
|
|
|
// Stop password reset requests when there are (freeRetries + 1) requests per lifetime per email
|
|
|
|
// Defaults here are 5 attempts per hour for a user+IP pair
|
|
|
|
// The endpoint is then locked for an hour
|
2017-03-13 23:07:12 +03:00
|
|
|
userReset = function userReset() {
|
|
|
|
var ExpressBrute = require('express-brute'),
|
|
|
|
BruteKnex = require('brute-knex'),
|
|
|
|
db = require('../../data/db');
|
|
|
|
|
|
|
|
store = store || new BruteKnex({
|
2017-11-01 16:44:54 +03:00
|
|
|
tablename: 'brute',
|
|
|
|
createTable: false,
|
|
|
|
knex: db.knex
|
|
|
|
});
|
2017-03-13 23:07:12 +03:00
|
|
|
|
|
|
|
userResetInstance = userResetInstance || new ExpressBrute(store,
|
2017-11-01 16:44:54 +03:00
|
|
|
_.extend({
|
|
|
|
attachResetToRequest: true,
|
|
|
|
failCallback: function (req, res, next, nextValidRequestDate) {
|
|
|
|
return next(new errors.TooManyRequestsError({
|
|
|
|
message: 'Too many password reset attempts try again in ' + moment(nextValidRequestDate).fromNow(true),
|
|
|
|
context: i18n.t('errors.middleware.spamprevention.forgottenPasswordEmail.error',
|
|
|
|
{rfa: spamUserReset.freeRetries + 1 || 5, rfp: spamUserReset.lifetime || 60 * 60}),
|
|
|
|
help: i18n.t('errors.middleware.spamprevention.forgottenPasswordEmail.context')
|
|
|
|
}));
|
|
|
|
},
|
|
|
|
handleStoreError: handleStoreError
|
|
|
|
}, _.pick(spamUserReset, spamConfigKeys))
|
|
|
|
);
|
2017-03-13 23:07:12 +03:00
|
|
|
|
|
|
|
return userResetInstance;
|
|
|
|
};
|
2016-11-08 14:33:19 +03:00
|
|
|
|
|
|
|
// This protects a private blog from spam attacks. The defaults here allow 10 attempts per IP per hour
|
|
|
|
// The endpoint is then locked for an hour
|
2017-03-13 23:07:12 +03:00
|
|
|
privateBlog = function privateBlog() {
|
|
|
|
var ExpressBrute = require('express-brute'),
|
|
|
|
BruteKnex = require('brute-knex'),
|
|
|
|
db = require('../../data/db');
|
|
|
|
|
|
|
|
store = store || new BruteKnex({
|
2017-11-01 16:44:54 +03:00
|
|
|
tablename: 'brute',
|
|
|
|
createTable: false,
|
|
|
|
knex: db.knex
|
|
|
|
});
|
2017-03-13 23:07:12 +03:00
|
|
|
|
|
|
|
privateBlogInstance = privateBlogInstance || new ExpressBrute(store,
|
2017-11-01 16:44:54 +03:00
|
|
|
_.extend({
|
|
|
|
attachResetToRequest: false,
|
|
|
|
failCallback: function (req, res, next, nextValidRequestDate) {
|
|
|
|
logging.error(new errors.GhostError({
|
|
|
|
message: i18n.t('errors.middleware.spamprevention.tooManySigninAttempts.error',
|
|
|
|
{rfa: spamPrivateBlog.freeRetries + 1 || 5, rfp: spamPrivateBlog.lifetime || 60 * 60}),
|
|
|
|
context: i18n.t('errors.middleware.spamprevention.tooManySigninAttempts.context')
|
|
|
|
}));
|
|
|
|
|
|
|
|
return next(new errors.GhostError({
|
|
|
|
message: 'Too many private sign-in attempts try again in ' + moment(nextValidRequestDate).fromNow(true)
|
|
|
|
}));
|
|
|
|
},
|
|
|
|
handleStoreError: handleStoreError
|
|
|
|
}, _.pick(spamPrivateBlog, spamConfigKeys))
|
|
|
|
);
|
2017-03-13 23:07:12 +03:00
|
|
|
|
|
|
|
return privateBlogInstance;
|
|
|
|
};
|
2016-11-08 14:33:19 +03:00
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
globalBlock: globalBlock,
|
|
|
|
globalReset: globalReset,
|
|
|
|
userLogin: userLogin,
|
|
|
|
userReset: userReset,
|
|
|
|
privateBlog: privateBlog
|
2015-05-26 22:04:27 +03:00
|
|
|
};
|