Skip to content

Commit 8deab49

Browse files
committed
Implement IP blacklisting
1 parent 96d9d07 commit 8deab49

File tree

5 files changed

+30
-0
lines changed

5 files changed

+30
-0
lines changed

schemas/qwc-db-auth.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,14 @@
123123
"totp_issuer_name": {
124124
"description": "Issuer name for QR code URI. Default: 'QWC Services'",
125125
"type": "string"
126+
},
127+
"ip_blacklist_duration": {
128+
"description": "How many seconds an IP will remain in the blacklist. See also ip_blacklist_max_attempt_count. Default: 300",
129+
"type": "integer"
130+
},
131+
"ip_blacklist_max_attempt_count": {
132+
"description": "After how many failed login attempts an IP will be blacklisted. Should be less than max_login_attempts. See also ip_blacklist_duration. Default: 10",
133+
"type": "integer"
126134
}
127135
},
128136
"required": [

src/db_auth.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@
1616
from werkzeug.security import check_password_hash
1717

1818
from qwc_services_core.auth import get_username
19+
from qwc_services_core.cache import ExpiringDict
1920
from qwc_services_core.database import DatabaseEngine
2021
from qwc_services_core.config_models import ConfigModels
2122
from qwc_services_core.runtime_config import RuntimeConfig
2223

2324
from forms import LoginForm, NewPasswordForm, EditPasswordForm, VerifyForm
2425

26+
ip_blacklist = ExpiringDict()
2527

2628
class DBAuth:
2729
"""DBAuth class
@@ -92,6 +94,8 @@ def __init__(self, tenant, mail, app):
9294
self.totp_enabled = config.get('totp_enabled', False)
9395
self.totp_enabled_for_admin = config.get('totp_enabled_for_admin', False)
9496
self.totp_issuer_name = config.get('totp_issuer_name', 'QWC Services')
97+
self.ip_blacklist_duration = config.get('ip_blacklist_duration', 300)
98+
self.ip_blacklist_max_attempt_count = config.get('ip_blacklist_max_attempt_count', 10)
9599

96100
db_engine = DatabaseEngine()
97101
self.config_models = ConfigModels(
@@ -700,6 +704,14 @@ def __user_is_authorized(self, user, password):
700704
:param User user: User instance
701705
:param str password: Password
702706
"""
707+
# Check if IP blacklisted
708+
if self.ip_blacklist_duration > 0:
709+
entry = ip_blacklist.lookup(request.remote_addr)
710+
count = entry['value'] if entry else 0
711+
if count >= self.ip_blacklist_max_attempt_count:
712+
self.logger.info("IP %s is blacklisted with %s attempts" % (request.remote_addr, count))
713+
return False, i18n.t('auth.ip_blacklisted')
714+
703715
if user is None or user.password_hash is None:
704716
# invalid username or no password set
705717
return False, i18n.t('auth.auth_failed')
@@ -719,6 +731,13 @@ def __user_is_authorized(self, user, password):
719731
else:
720732
# invalid password
721733

734+
# add to ip blacklist
735+
if self.ip_blacklist_duration > 0:
736+
entry = ip_blacklist.lookup(request.remote_addr)
737+
count = entry['value'] if entry else 0
738+
ip_blacklist.set(request.remote_addr, count + 1, self.ip_blacklist_duration)
739+
self.logger.info("Attempt count for IP %s: %s" % (request.remote_addr, count + 1))
740+
722741
# increase failed login attempts counter
723742
user.failed_sign_in_count += 1
724743

src/translations/auth.de.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"auth_failed": "Ungültiger Benutzername oder ungültiges Passwort",
1010
"account_locked": "Konto gesperrt",
1111
"validation_error": "Überprüfung des Formulars fehlgeschlagen",
12+
"ip_blacklisted": "IP-Addresse ist vorübergehend blockiert",
1213

1314
"login_link": "Anmeldung",
1415

src/translations/auth.en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"auth_failed": "Invalid username or password",
1010
"account_locked": "Account locked",
1111
"validation_error": "Form validation error",
12+
"ip_blacklisted": "IP address is temporarily blacklisted",
1213

1314
"login_link": "Sign In",
1415

src/translations/auth.fr.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"auth_failed": "Identifiant ou mot de passe invalide",
1010
"account_locked": "Compte bloqué",
1111
"validation_error": "Erreur dans le formulaire",
12+
"ip_blacklisted": "Adresse IP temporairement bloquée",
1213

1314
"login_link": "Se connecter",
1415

0 commit comments

Comments
 (0)