feat: login IP whitelist, blocks UI and fail2ban sync (0.4.1)

Admin settings for web login whitelist, blocked IP list with unblock,
and optional fail2ban ignoreip sync for SSH jail.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-25 10:06:10 +10:00
parent bda02f67eb
commit 719d5c5719
12 changed files with 863 additions and 2 deletions
+10
View File
@@ -12,6 +12,10 @@ from sqlalchemy.orm import Session
from app.config import get_settings
from app.models.login_attempt import LoginAttempt
from app.services.login_security_settings import (
get_effective_login_security_config,
is_ip_login_whitelisted,
)
from app.services.telegram_notify import send_telegram_text
logger = logging.getLogger(__name__)
@@ -91,6 +95,9 @@ def ensure_login_allowed(db: Session, request: Request) -> str:
"""Raise 429 if IP exceeded failed login threshold. Returns client IP."""
cfg = get_login_rate_limit_config()
ip_address = client_ip_from_request(request)
sec = get_effective_login_security_config(db)
if is_ip_login_whitelisted(ip_address, sec.ip_whitelist):
return ip_address
since = datetime.now(timezone.utc) - timedelta(minutes=cfg.window_minutes)
failures = _failure_count(db, ip_address, since=since)
if failures >= cfg.max_failures:
@@ -119,6 +126,9 @@ def record_login_failure(
request: Request | None = None,
) -> None:
del request # reserved
sec = get_effective_login_security_config(db)
if is_ip_login_whitelisted(ip_address, sec.ip_whitelist):
return
cfg = get_login_rate_limit_config()
since = datetime.now(timezone.utc) - timedelta(minutes=cfg.window_minutes)
prior_failures = _failure_count(db, ip_address, since=since)