Sep 16 - Optimize code, part 1

This commit is contained in:
2026-09-16 10:52:59 -04:00
parent 4212726611
commit 7626287344
22 changed files with 587 additions and 96 deletions
+49 -25
View File
@@ -37,7 +37,8 @@ class SecurityManager:
self.session_tokens = {}
# Security configuration
self.max_failed_attempts = 5
self.max_failed_attempts = 5 # per IP + username, 15 minutes
self.max_failed_attempts_per_username = 20 # per username from any IP, 15 minutes
self.lockout_duration = 900 # 15 minutes
self.session_timeout = 3600 # 1 hour
@@ -117,12 +118,13 @@ class SecurityManager:
def get_client_ip(self):
"""Get real client IP address"""
# Check for forwarded headers (in case behind proxy/CDN)
forwarded_ips = request.headers.getlist('X-Forwarded-For')
if forwarded_ips:
return forwarded_ips[0].split(',')[0].strip()
# request.remote_addr is the real client address: app.py wraps the app in
# ProxyFix(x_for=TRUSTED_PROXY_COUNT), which takes it from the entry our
# own proxy (Nginx) appended to X-Forwarded-For. The FIRST entry of that
# header — used here before — is whatever the client chose to send, so
# changing it on every request bypassed the login rate limit.
return request.headers.get('X-Real-IP') or request.remote_addr
return request.remote_addr or 'unknown'
def is_suspicious_request(self):
"""Detect suspicious request patterns"""
@@ -235,30 +237,52 @@ class SecurityManager:
return False
def is_auth_rate_limited(self):
"""Check if authentication endpoint is rate limited"""
def _login_attempt_keys(self, username=None):
"""Failed-login counter keys: this IP + username, and the username alone."""
client_ip = self.get_client_ip()
name = (username or '').strip().lower()
if not name:
return [f"ip:{client_ip}"]
return [f"ip:{client_ip}|user:{name}", f"user:{name}"]
def is_auth_rate_limited(self, username=None):
"""
Should this login attempt be blocked?
Two counters over lockout_duration (15 minutes):
- this IP + username: blocked after max_failed_attempts (5)
- this username from ANY IP: blocked after max_failed_attempts_per_username
(20), so rotating IP addresses cannot brute-force one account
Deliberately not a plain per-IP counter: users behind one shared address
(office NAT, a proxy that hides client IPs) must not lock each other out.
Counters are in memory per gunicorn worker.
"""
current_time = time.time()
# Clean old attempts
self.failed_attempts[client_ip] = deque([
attempt for attempt in self.failed_attempts[client_ip]
if current_time - attempt < 900 # Keep attempts from last 15 minutes
], maxlen=10)
return len(self.failed_attempts[client_ip]) >= self.max_failed_attempts
for key in self._login_attempt_keys(username):
self.failed_attempts[key] = deque([
attempt for attempt in self.failed_attempts.get(key, ())
if current_time - attempt < self.lockout_duration
], maxlen=50)
limit = (self.max_failed_attempts_per_username if key.startswith('user:')
else self.max_failed_attempts)
if len(self.failed_attempts[key]) >= limit:
return True
return False
def record_failed_attempt(self, identifier):
"""Record a failed authentication attempt"""
"""Record a failed authentication attempt (identifier = the submitted username)"""
client_ip = self.get_client_ip()
current_time = time.time()
self.failed_attempts[client_ip].append(current_time)
keys = self._login_attempt_keys(identifier)
for key in keys:
if key not in self.failed_attempts:
self.failed_attempts[key] = deque(maxlen=50)
self.failed_attempts[key].append(current_time)
self.log_security_event('authentication_failure', {
'ip': client_ip,
'identifier': identifier,
'attempts': len(self.failed_attempts[client_ip])
'attempts': len(self.failed_attempts[keys[0]])
})
def create_secure_session(self, user_id):
@@ -278,10 +302,10 @@ class SecurityManager:
session['security_token'] = session_token
session['login_time'] = datetime.utcnow().isoformat()
# Clear any failed attempts for this IP
# Clear the failed-login counters for this IP + username and the username
client_ip = self.get_client_ip()
if client_ip in self.failed_attempts:
del self.failed_attempts[client_ip]
for key in self._login_attempt_keys(session.get('username')) + [f"ip:{client_ip}"]:
self.failed_attempts.pop(key, None)
self.log_security_event('secure_session_created', {
'user_id': user_id,