mirror of
https://github.com/nodemailer/wildduck.git
synced 2024-12-28 19:24:32 +08:00
Merge branch 'master' of github.com:nodemailer/wildduck
This commit is contained in:
commit
e5376d8a4d
2 changed files with 93 additions and 15 deletions
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue