diff --git a/dashboard/backend/auth_service.py b/dashboard/backend/auth_service.py index a321589..cc9d485 100644 --- a/dashboard/backend/auth_service.py +++ b/dashboard/backend/auth_service.py @@ -224,14 +224,26 @@ def verify_totp(token: str, secret: str = None): if not secret: return True # 2FA not enabled totp = pyotp.TOTP(secret) - if not totp.verify(token): + if not totp.verify(token, valid_window=1): # Allow 1 time step before/after (±30s) return False def mutator(data): - current_ts = int(time.time()) // 30 - if data.get("last_totp_ts") == current_ts: + # Track recently used codes to prevent replay attacks + used_codes = data.get("used_totp_codes", []) + current_time = int(time.time()) + + # Check if this code was already used + if token in [code for code, _ in used_codes]: return False - data["last_totp_ts"] = current_ts + + # Add current code with timestamp + used_codes.append((token, current_time)) + + # Clean up codes older than 90 seconds (3 time windows) + used_codes = [(code, ts) for code, ts in used_codes if current_time - ts < 90] + + # Keep only last 5 codes to limit memory + data["used_totp_codes"] = used_codes[-5:] return True return _update_auth_data(mutator)