AstraOS Ver. 1.46
All checks were successful
Deploy AstraOS Panel / deploy (push) Successful in 12s

Login Rate Limit
Reactivation Limit
Persistent Sessions
This commit is contained in:
elias 2026-08-18 19:15:56 +02:00
parent dc180a1e8a
commit bcaf8ae706

View file

@ -20,7 +20,26 @@ import asyncio
import discord import discord
from discord.ui import Button, View from discord.ui import Button, View
from dotenv import load_dotenv from dotenv import load_dotenv
import time as _time
load_dotenv() load_dotenv()
# ---- Simple login rate limiter (per IP, 0.5s min interval) ----
_login_last_attempt: dict = {} # {ip: timestamp}
_LOGIN_RATE_LIMIT_SECONDS = 0.5
def _check_login_rate_limit(ip: str) -> bool:
"""Returns True if the request is allowed, False if rate-limited."""
now = _time.time()
last = _login_last_attempt.get(ip, 0)
if now - last < _LOGIN_RATE_LIMIT_SECONDS:
return False
_login_last_attempt[ip] = now
# Cleanup old entries to prevent unbounded growth
if len(_login_last_attempt) > 10000:
cutoff = now - 60
for k in [k for k, v in list(_login_last_attempt.items()) if v < cutoff]:
_login_last_attempt.pop(k, None)
return True
async_mode = "eventlet" async_mode = "eventlet"
app = Flask(__name__, app = Flask(__name__,
template_folder='Frontend/HTML', template_folder='Frontend/HTML',
@ -53,7 +72,7 @@ class SuppressServerLoadFilter(logging.Filter):
log = logging.getLogger('werkzeug') log = logging.getLogger('werkzeug')
log.addFilter(SuppressServerLoadFilter()) log.addFilter(SuppressServerLoadFilter())
app.secret_key = os.environ.get('SECRET_KEY', os.urandom(32)) app.secret_key = os.environ.get('SECRET_KEY', os.urandom(32))
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=8) app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=30)
app.config['SESSION_COOKIE_SECURE'] = True app.config['SESSION_COOKIE_SECURE'] = True
app.config['SESSION_COOKIE_HTTPONLY'] = True app.config['SESSION_COOKIE_HTTPONLY'] = True
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
@ -658,6 +677,11 @@ def login():
if maintenance_status and maintenance_status.get('enabled', False): if maintenance_status and maintenance_status.get('enabled', False):
return redirect(url_for('maintenance')) return redirect(url_for('maintenance'))
if request.method == 'POST': if request.method == 'POST':
# Rate limit: max 1 attempt per 0.5s per IP
_client_ip = request.headers.get('X-Forwarded-For', request.remote_addr or '').split(',')[0].strip()
if not _check_login_rate_limit(_client_ip):
flash("Zu viele Anmeldeversuche. Bitte warte einen Moment.", "warning")
return render_template('login.html')
username = request.form.get('username') username = request.form.get('username')
password = request.form.get('password') password = request.form.get('password')
if not username or not password: if not username or not password: