You've already forked Mailu
mirror of
https://github.com/Mailu/Mailu.git
synced 2025-12-03 22:49:11 +02:00
Merge #2732
2732: Only account for distinct attempts in rate limits r=mergify[bot] a=nextgens ## What type of PR? enhancement ## What does this PR do? Only account for distinct attempts in rate limits. This is solving the problem related to users changing their passwords and having their client hammer the old credentials. Reduce the default to 50 distinct passwords per day ### Related issue(s) ## Prerequisites Before we can consider review and merge, please make sure the following list is done and checked. If an entry in not applicable, you can check it or remove it from the list. - [x] In case of feature or enhancement: documentation updated accordingly - [x] Unless it's docs or a minor change: add [changelog](https://mailu.io/master/contributors/workflow.html#changelog) entry file. Co-authored-by: Florent Daigniere <nextgens@freenetproject.org> Co-authored-by: Florent Daigniere <nextgens@users.noreply.github.com>
This commit is contained in:
@@ -44,7 +44,7 @@ DEFAULT_CONFIG = {
|
||||
'AUTH_RATELIMIT_IP': '5/hour',
|
||||
'AUTH_RATELIMIT_IP_V4_MASK': 24,
|
||||
'AUTH_RATELIMIT_IP_V6_MASK': 48,
|
||||
'AUTH_RATELIMIT_USER': '100/day',
|
||||
'AUTH_RATELIMIT_USER': '50/day',
|
||||
'AUTH_RATELIMIT_EXEMPTION': '',
|
||||
'AUTH_RATELIMIT_EXEMPTION_LENGTH': 86400,
|
||||
'DISABLE_STATISTICS': False,
|
||||
|
||||
@@ -85,6 +85,7 @@ def handle_authentication(headers):
|
||||
raw_user_email = urllib.parse.unquote(headers["Auth-User"])
|
||||
raw_password = urllib.parse.unquote(headers["Auth-Pass"])
|
||||
user_email = 'invalid'
|
||||
password = 'invalid'
|
||||
try:
|
||||
user_email = raw_user_email.encode("iso8859-1").decode("utf8")
|
||||
password = raw_password.encode("iso8859-1").decode("utf8")
|
||||
@@ -107,6 +108,7 @@ def handle_authentication(headers):
|
||||
"Auth-Server": server,
|
||||
"Auth-User": user_email,
|
||||
"Auth-User-Exists": is_valid_user,
|
||||
"Auth-Password": password,
|
||||
"Auth-Port": port
|
||||
}
|
||||
status, code = get_status(protocol, "authentication")
|
||||
@@ -115,6 +117,7 @@ def handle_authentication(headers):
|
||||
"Auth-Error-Code": code,
|
||||
"Auth-User": user_email,
|
||||
"Auth-User-Exists": is_valid_user,
|
||||
"Auth-Password": password,
|
||||
"Auth-Wait": 0
|
||||
}
|
||||
# Unexpected
|
||||
|
||||
@@ -48,7 +48,7 @@ def nginx_authentication():
|
||||
if headers.get("Auth-Status") == "OK":
|
||||
utils.limiter.exempt_ip_from_ratelimits(client_ip)
|
||||
elif is_valid_user:
|
||||
utils.limiter.rate_limit_user(username, client_ip)
|
||||
utils.limiter.rate_limit_user(username, client_ip, password=response.headers.get('Auth-Password', None))
|
||||
elif not is_from_webmail:
|
||||
utils.limiter.rate_limit_ip(client_ip, username)
|
||||
return response
|
||||
|
||||
@@ -68,9 +68,13 @@ class LimitWraperFactory(object):
|
||||
app.logger.warn(f'Authentication attempt from {ip} for {username} has been rate-limited.')
|
||||
return is_rate_limited
|
||||
|
||||
def rate_limit_user(self, username, ip, device_cookie=None, device_cookie_name=None):
|
||||
def rate_limit_user(self, username, ip, device_cookie=None, device_cookie_name=None, password=''):
|
||||
limiter = self.get_limiter(app.config["AUTH_RATELIMIT_USER"], 'auth-user')
|
||||
if self.is_subject_to_rate_limits(ip):
|
||||
truncated_password = hmac.new(bytearray(username, 'utf-8'), bytearray(password, 'utf-8'), 'sha256').hexdigest()[-6:]
|
||||
if password and (self.storage.get(f'dedup2-{username}-{truncated_password}') > 0):
|
||||
return
|
||||
self.storage.incr(f'dedup2-{username}-{truncated_password}', limits.parse(app.config['AUTH_RATELIMIT_USER']).GRANULARITY.seconds, True)
|
||||
limiter.hit(device_cookie if device_cookie_name == username else username)
|
||||
|
||||
""" Device cookies as described on:
|
||||
|
||||
@@ -59,7 +59,7 @@ def login():
|
||||
flask.flash(msg, "error")
|
||||
return response
|
||||
else:
|
||||
utils.limiter.rate_limit_user(username, client_ip, device_cookie, device_cookie_username) if models.User.get(username) else utils.limiter.rate_limit_ip(client_ip, username)
|
||||
utils.limiter.rate_limit_user(username, client_ip, device_cookie, device_cookie_username, form.pw.data) if models.User.get(username) else utils.limiter.rate_limit_ip(client_ip, username)
|
||||
flask.current_app.logger.warn(f'Login failed for {username} from {client_ip}.')
|
||||
flask.flash('Wrong e-mail or password', 'error')
|
||||
return flask.render_template('login.html', form=form, fields=fields)
|
||||
|
||||
Reference in New Issue
Block a user