From 04f003c9f035036a19169fe1db0ec88fda7e22e2 Mon Sep 17 00:00:00 2001 From: Jon Ander Oribe Date: Mon, 19 Jan 2026 07:20:29 +0100 Subject: [PATCH] Add rate limiting to login endpoint Introduced Flask-Limiter to restrict login attempts to 5 per minute per IP, enhancing security against brute-force attacks. Updated error handling to display a user-friendly message when the rate limit is exceeded. Minor improvements to the login page, including clearer error messages and display of the user's IP address. --- app.py | 18 ++++++++++ pyproject.toml | 1 + templates/login.html | 79 ++++++++++++++++++++++---------------------- 3 files changed, 58 insertions(+), 40 deletions(-) diff --git a/app.py b/app.py index 58fc070..e30646f 100644 --- a/app.py +++ b/app.py @@ -39,6 +39,8 @@ from utils.constants import ( QUEUE_MAX_SIZE, ) import logging +from flask_limiter import Limiter +from flask_limiter.util import get_remote_address # Track application start time for uptime calculation import time as _time _app_start_time = _time.time() @@ -48,9 +50,24 @@ logger = logging.getLogger('intercept.database') app = Flask(__name__) app.secret_key = "signals_intelligence_secret" # Required for flash messages +# Set up rate limiting +limiter = Limiter( + key_func=get_remote_address, # Identifies the user by their IP + app=app, + storage_uri="memory://", # Use RAM memory (change to redis:// etc. for distributed setups) +) + # Disable Werkzeug debugger PIN (not needed for local development tool) os.environ['WERKZEUG_DEBUG_PIN'] = 'off' +# ============================================ +# ERROR HANDLERS +# ============================================ +@app.errorhandler(429) +def ratelimit_handler(e): + logger.warning(f"Rate limit exceeded for IP: {request.remote_addr}") + flash("Too many login attempts. Please wait one minute before trying again.", "error") + return render_template('login.html', version=VERSION), 429 # ============================================ # SECURITY HEADERS @@ -174,6 +191,7 @@ def logout(): return redirect(url_for('login')) @app.route('/login', methods=['GET', 'POST']) +@limiter.limit("5 per minute") # Limit to 5 login attempts per minute per IP def login(): if request.method == 'POST': username = request.form.get('username') diff --git a/pyproject.toml b/pyproject.toml index b6faae6..e42cf18 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,7 @@ dependencies = [ "skyfield>=1.45", "pyserial>=3.5", "Werkzeug>=3.1.5", + "flask-limiter>=2.5.4", ] [project.urls] diff --git a/templates/login.html b/templates/login.html index 2a24e6d..6662aa9 100644 --- a/templates/login.html +++ b/templates/login.html @@ -4,62 +4,61 @@ iNTERCEPT // Restricted Access - - + + -
-
+
+
-
+

SECURE LOGIN

// Restricted Terminal Access

SYSTEM AUTH v{{ version }}

+

+ Unauthorized access is logged. IP: {{ request.remote_addr }} +

+
-
- + \ No newline at end of file