Merge branch 'master' of github.com:nodemailer/wildduck

This commit is contained in:
Andris Reinman 2018-04-13 13:47:27 +03:00
commit e5376d8a4d
2 changed files with 93 additions and 15 deletions

View file

@ -38,9 +38,14 @@ module.exports = {
BCRYPT_ROUNDS: 12,
// how many authentication failures per user to allow before blocking until the end of the auth window
AUTH_FAILURES: 6,
USER_AUTH_FAILURES: 12,
// authentication window in seconds, starts counting from first invalid authentication
AUTH_WINDOW: 60,
USER_AUTH_WINDOW: 120,
// how many authentication failures per ip to allow before blocking until the end of the auth window
IP_AUTH_FAILURES: 10,
// authentication window in seconds, starts counting from first invalid authentication
IP_AUTH_WINDOW: 300,
// how many TOTP failures per user to allow before blocking until the end of the auth window
TOTP_FAILURES: 6,

View file

@ -242,6 +242,68 @@ class UserHandler {
});
}
/**
* rateLimitIP
* if ip is not available will always return success object
* @param {Object} meta
* @param {String} meta.ip request remote ip address
* @param {Integer} count
* @param {Function} callback
*/
rateLimitIP (meta, count, callback) {
if (meta.ip) {
this.counters.ttlcounter('auth_ip:' + meta.ip, count, consts.IP_AUTH_FAILURES, consts.IP_AUTH_WINDOW, callback);
}
return callback(null, {success: true});
}
/**
* rateLimitUser
* @param {String} tokenID user identifier
* @param {Integer} count
* @param {Function} callback
*/
rateLimitUser (tokenID, count, callback) {
this.counters.ttlcounter('auth_user:' + tokenID, count, consts.USER_AUTH_FAILURES, consts.USER_AUTH_WINDOW, callback);
}
/**
* rateLimitReleaseUser
* @param {String} tokenID user identifier
* @param {Integer} count
* @param {Function} callback
*/
rateLimitReleaseUser (tokenID, callback) {
this.redis.del('auth_user:' + tokenID, callback);
}
/**
* rateLimit
* @param {String} tokenID user identifier
* @param {Object} meta
* @param {String} meta.ip request remote ip address
* @param {Integer} count
* @param {Function} callback
*/
rateLimit (tokenID, meta, count, callback) {
this.rateLimitIP(meta, count, (err, ipRes) => {
if (err) {
return callback(err);
}
this.rateLimitUser(tokenID, count, (err, userRes) => {
if (err) {
return callback(err);
}
if (!ipRes.success) {
return callback(null, ipRes);
}
return callback(null, userRes);
});
});
}
/**
* Authenticate user
*
@ -261,6 +323,16 @@ class UserHandler {
return callback(null, false);
}
this.rateLimitIP(meta, 0, (err, res) => {
if (err) {
err.code = 'InternalDatabaseError';
return callback(err);
}
if (!res.success) {
return rateLimitResponse(res, callback);
}
this.checkAddress(username, (err, query) => {
if (err) {
return callback(err);
@ -301,8 +373,8 @@ class UserHandler {
return this.logAuthEvent(null, meta, () => {
// rate limit failed authentication attempts against non-existent users as well
let ustring = (query.unameview || query._id || '').toString();
let rlkey = 'auth:' + ustring + (meta.ip ? ':' + meta.ip : '');
this.counters.ttlcounter(rlkey, 1, consts.AUTH_FAILURES, consts.AUTH_WINDOW, (err, res) => {
this.rateLimit(ustring, meta, 1, (err, res) => {
if (err) {
err.code = 'InternalDatabaseError';
return callback(err);
@ -315,8 +387,7 @@ class UserHandler {
});
}
let rlkey = 'auth:' + userData._id.toString() + (meta.ip ? ':' + meta.ip : '');
this.counters.ttlcounter(rlkey, 0, consts.AUTH_FAILURES, consts.AUTH_WINDOW, (err, res) => {
this.rateLimitUser(userData._id, 0, (err, res) => {
if (err) {
err.code = 'InternalDatabaseError';
return callback(err);
@ -342,13 +413,13 @@ class UserHandler {
let authSuccess = (...args) => {
// clear rate limit counter on success
this.redis.del(rlkey, () => false);
this.rateLimitReleaseUser(userData._id, () => false);
callback(...args);
};
let authFail = (...args) => {
// increment rate limit counter on failure
this.counters.ttlcounter(rlkey, 1, consts.AUTH_FAILURES, consts.AUTH_WINDOW, () => {
this.rateLimit(userData._id, meta, 1, () => {
callback(...args);
});
};
@ -514,7 +585,7 @@ class UserHandler {
if (err) {
// don't really care
}
this.redis.del(rlkey, () => false);
this.rateLimitReleaseUser(userData._id, () => false);
this.users.collection('asps').findOneAndUpdate(
{
_id: asp._id
@ -546,6 +617,8 @@ class UserHandler {
}
);
});
});
}
/**
@ -595,7 +668,7 @@ class UserHandler {
// FIXME: use IP in rlkey
let rlkey = 'outh:' + userData._id.toString();
this.counters.ttlcounter(rlkey, 0, consts.AUTH_FAILURES, consts.AUTH_WINDOW, (err, res) => {
this.counters.ttlcounter(rlkey, 0, consts.USER_AUTH_FAILURES, consts.USER_AUTH_WINDOW, (err, res) => {
if (err) {
err.code = 'InternalDatabaseError';
return callback(err);
@ -612,7 +685,7 @@ class UserHandler {
let authFail = (...args) => {
// increment rate limit counter on failure
this.counters.ttlcounter(rlkey, 1, consts.AUTH_FAILURES, consts.AUTH_WINDOW, () => {
this.counters.ttlcounter(rlkey, 1, consts.USER_AUTH_FAILURES, consts.USER_AUTH_WINDOW, () => {
callback(...args);
});
};
@ -1575,8 +1648,8 @@ class UserHandler {
}
checkTotp(user, data, callback) {
let rlkey = 'totp:' + user.toString() + (data.ip ? ':' + data.ip : '');
this.counters.ttlcounter(rlkey, 0, consts.AUTH_FAILURES, consts.AUTH_WINDOW * 3, (err, res) => {
let userRlKey = 'totp:' + user;
this.rateLimit(userRlKey, data, 0, (err, res) => { // NOT Sure why this used "consts.USER_AUTH_WINDOW * 3"
if (err) {
err.code = 'InternalDatabaseError';
return callback(err);
@ -1587,13 +1660,13 @@ class UserHandler {
let authSuccess = (...args) => {
// clear rate limit counter on success
this.redis.del(rlkey, () => false);
this.rateLimitReleaseUser(userRlKey, () => false);
callback(...args);
};
let authFail = (...args) => {
// increment rate limit counter on failure
this.counters.ttlcounter(rlkey, 1, consts.TOTP_FAILURES, consts.TOTP_WINDOW, () => {
this.rateLimit(userRlKey, data, 1, () => {
callback(...args);
});
};