From 22968b5a6ea1f3fc36e097ae7a9c0f704b90a8ed Mon Sep 17 00:00:00 2001 From: Jon Ander Oribe Date: Sat, 24 Jan 2026 15:26:22 +0100 Subject: [PATCH 1/7] Add PEPPER to password handling for improved security Introduces a PEPPER value from config and appends it to passwords during both admin user creation and login authentication. This enhances password security by making hashes more resilient to attacks. --- app.py | 4 ++-- config.py | 1 + utils/database.py | 9 ++------- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/app.py b/app.py index 8bdef28e..6a7fcc9c 100644 --- a/app.py +++ b/app.py @@ -27,7 +27,7 @@ from flask import Flask, render_template, jsonify, send_file, Response, request,redirect, url_for, flash, session from werkzeug.security import check_password_hash -from config import VERSION, CHANGELOG +from config import PEPPER, VERSION, CHANGELOG from utils.dependencies import check_tool, check_all_dependencies, TOOL_DEPENDENCIES from utils.process import cleanup_stale_processes from utils.sdr import SDRFactory @@ -200,7 +200,7 @@ def logout(): def login(): if request.method == 'POST': username = request.form.get('username') - password = request.form.get('password') + password = f"{request.form.get('password') or ''}{PEPPER}" # Connect to DB and find user with get_db() as conn: diff --git a/config.py b/config.py index fc5514e6..cf359dc5 100644 --- a/config.py +++ b/config.py @@ -128,6 +128,7 @@ def _get_env_bool(key: str, default: bool) -> bool: # Admin credentials ADMIN_USERNAME = _get_env('ADMIN_USERNAME', 'admin') ADMIN_PASSWORD = _get_env('ADMIN_PASSWORD', 'admin') +PEPPER = _get_env('PEPPER', '7aJBLRSimPO0l6Wkfic8YhO3PPqwPwD7oikTfK4TYjzIFxS4Tk') def configure_logging() -> None: """Configure application logging.""" diff --git a/utils/database.py b/utils/database.py index cdfe369b..35e14f0c 100644 --- a/utils/database.py +++ b/utils/database.py @@ -13,7 +13,7 @@ from pathlib import Path from typing import Any from werkzeug.security import generate_password_hash -from config import ADMIN_USERNAME, ADMIN_PASSWORD +from config import ADMIN_USERNAME, ADMIN_PASSWORD, PEPPER logger = logging.getLogger('intercept.database') @@ -115,12 +115,7 @@ def init_db() -> None: cursor = conn.execute('SELECT COUNT(*) FROM users') if cursor.fetchone()[0] == 0: - from config import ADMIN_USERNAME, ADMIN_PASSWORD - - logger.info(f"Creating default admin user: {ADMIN_USERNAME}") - - # Password hashing - hashed_pw = generate_password_hash(ADMIN_PASSWORD) + hashed_pw = generate_password_hash(f"{ADMIN_PASSWORD}{PEPPER}") conn.execute(''' INSERT INTO users (username, password_hash, role) From 1ea2e812fa3a2c0dec8ef81080a794ed24b4334e Mon Sep 17 00:00:00 2001 From: Jon Ander Oribe Date: Mon, 26 Jan 2026 15:44:44 +0100 Subject: [PATCH 2/7] Add PEPPER-based password hashing and migration Introduces secure PEPPER-based password hashing for user authentication. Updates documentation and setup to require a unique INTERCEPT_PEPPER environment variable, modifies login logic to use the new verification method, and adds automatic migration for legacy password hashes. Updates dependencies and Docker configuration to support these changes. --- README.md | 14 ++++++++++++- app.py | 29 +++++++++----------------- config.py | 26 +++++++++++------------ docker-compose.yml | 1 + pyproject.toml | 3 +++ setup.sh | 33 ++++++++++++++++++++++++++++++ utils/database.py | 51 +++++++++++++++++++++++++++++++++++++++++++++- 7 files changed, 123 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index de362a7c..124cdb0a 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,17 @@ Support the developer of this open-source project ``` **1. Clone and run:** +Generate a secure PEPPER with "openssl rand -hex 32" and save it in your computer. + +macOS: + +echo 'export INTERCEPT_PEPPER="your_generated_secret_here"' >> ~/.zshrc +source ~/.zshrc + +Linux: +echo 'export INTERCEPT_PEPPER="your_generated_secret_here"' >> ~/.bashrc +source ~/.bashrc + ```bash git clone https://github.com/smittix/intercept.git cd intercept @@ -53,7 +64,8 @@ sudo -E venv/bin/python intercept.py ``` ### Docker (Alternative) - +Generate a secure PEPPER with "openssl rand -hex 32". +Modify on the docker-compose the INTERCEPT_PEPPER=your_generated_secret_here ```bash git clone https://github.com/smittix/intercept.git cd intercept diff --git a/app.py b/app.py index 5799c876..2914205e 100644 --- a/app.py +++ b/app.py @@ -9,7 +9,7 @@ import sys import site -from utils.database import get_db +from utils.database import verify_user # Ensure user site-packages is available (may be disabled when running as root/sudo) if not site.ENABLE_USER_SITE: @@ -203,9 +203,9 @@ def add_security_headers(response): # ============================================ @app.before_request -def require_login(): - # Routes that don't require login (to avoid infinite redirect loop) - allowed_routes = ['login', 'static', 'favicon', 'health', 'health_check'] +def require_login(): + # Routes that don't require login (to avoid infinite redirect loop) + allowed_routes = ['login', 'static', 'favicon', 'health', 'health_check'] # If user is not logged in and the current route is not allowed... if 'logged_in' not in session and request.endpoint not in allowed_routes: @@ -217,26 +217,17 @@ 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 +@limiter.limit("5 per minute") def login(): if request.method == 'POST': username = request.form.get('username') - password = f"{request.form.get('password') or ''}{PEPPER}" - - # Connect to DB and find user - with get_db() as conn: - cursor = conn.execute( - 'SELECT password_hash, role FROM users WHERE username = ?', - (username,) - ) - user = cursor.fetchone() + password = request.form.get('password') or '' + user_data = verify_user(username, password) - # Verify user exists and password is correct - if user and check_password_hash(user['password_hash'], password): - # Store data in session + if user_data: session['logged_in'] = True session['username'] = username - session['role'] = user['role'] + session['role'] = user_data['role'] logger.info(f"User '{username}' logged in successfully.") return redirect(url_for('index')) @@ -710,4 +701,4 @@ def main() -> None: debug=args.debug, threaded=True, load_dotenv=False, - ) + ) diff --git a/config.py b/config.py index ed212fb5..27115362 100644 --- a/config.py +++ b/config.py @@ -126,18 +126,18 @@ def _get_env_bool(key: str, default: bool) -> bool: BT_SCAN_TIMEOUT = _get_env_int('BT_SCAN_TIMEOUT', 10) BT_UPDATE_INTERVAL = _get_env_float('BT_UPDATE_INTERVAL', 2.0) -# ADS-B settings -ADSB_SBS_PORT = _get_env_int('ADSB_SBS_PORT', 30003) -ADSB_UPDATE_INTERVAL = _get_env_float('ADSB_UPDATE_INTERVAL', 1.0) -ADSB_HISTORY_ENABLED = _get_env_bool('ADSB_HISTORY_ENABLED', False) -ADSB_DB_HOST = _get_env('ADSB_DB_HOST', 'localhost') -ADSB_DB_PORT = _get_env_int('ADSB_DB_PORT', 5432) -ADSB_DB_NAME = _get_env('ADSB_DB_NAME', 'intercept_adsb') -ADSB_DB_USER = _get_env('ADSB_DB_USER', 'intercept') -ADSB_DB_PASSWORD = _get_env('ADSB_DB_PASSWORD', 'intercept') -ADSB_HISTORY_BATCH_SIZE = _get_env_int('ADSB_HISTORY_BATCH_SIZE', 500) -ADSB_HISTORY_FLUSH_INTERVAL = _get_env_float('ADSB_HISTORY_FLUSH_INTERVAL', 1.0) -ADSB_HISTORY_QUEUE_SIZE = _get_env_int('ADSB_HISTORY_QUEUE_SIZE', 50000) +# ADS-B settings +ADSB_SBS_PORT = _get_env_int('ADSB_SBS_PORT', 30003) +ADSB_UPDATE_INTERVAL = _get_env_float('ADSB_UPDATE_INTERVAL', 1.0) +ADSB_HISTORY_ENABLED = _get_env_bool('ADSB_HISTORY_ENABLED', False) +ADSB_DB_HOST = _get_env('ADSB_DB_HOST', 'localhost') +ADSB_DB_PORT = _get_env_int('ADSB_DB_PORT', 5432) +ADSB_DB_NAME = _get_env('ADSB_DB_NAME', 'intercept_adsb') +ADSB_DB_USER = _get_env('ADSB_DB_USER', 'intercept') +ADSB_DB_PASSWORD = _get_env('ADSB_DB_PASSWORD', 'intercept') +ADSB_HISTORY_BATCH_SIZE = _get_env_int('ADSB_HISTORY_BATCH_SIZE', 500) +ADSB_HISTORY_FLUSH_INTERVAL = _get_env_float('ADSB_HISTORY_FLUSH_INTERVAL', 1.0) +ADSB_HISTORY_QUEUE_SIZE = _get_env_int('ADSB_HISTORY_QUEUE_SIZE', 50000) # Satellite settings SATELLITE_UPDATE_INTERVAL = _get_env_int('SATELLITE_UPDATE_INTERVAL', 30) @@ -147,7 +147,7 @@ def _get_env_bool(key: str, default: bool) -> bool: # Admin credentials ADMIN_USERNAME = _get_env('ADMIN_USERNAME', 'admin') ADMIN_PASSWORD = _get_env('ADMIN_PASSWORD', 'admin') -PEPPER = _get_env('PEPPER', '7aJBLRSimPO0l6Wkfic8YhO3PPqwPwD7oikTfK4TYjzIFxS4Tk') +PEPPER = os.environ.get('INTERCEPT_PEPPER') def configure_logging() -> None: """Configure application logging.""" diff --git a/docker-compose.yml b/docker-compose.yml index 303a29b7..fc4f8aee 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,6 +28,7 @@ services: - INTERCEPT_HOST=0.0.0.0 - INTERCEPT_PORT=5050 - INTERCEPT_LOG_LEVEL=INFO + - INTERCEPT_PEPPER=your_generated_secret_here # ADS-B history is disabled by default # To enable, use: docker compose --profile history up -d # - INTERCEPT_ADSB_HISTORY_ENABLED=true diff --git a/pyproject.toml b/pyproject.toml index 4c21a50a..08f75e79 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,9 @@ dependencies = [ "bleak>=0.21.0", "flask-sock", "requests>=2.28.0", + 'psycopg2-binary>=2.9.9', + 'numpy>=1.24.0', + 'scipy>=1.10.0' ] [project.urls] diff --git a/setup.sh b/setup.sh index 62b910ae..de1938e5 100755 --- a/setup.sh +++ b/setup.sh @@ -877,11 +877,44 @@ install_debian_packages() { fi } +# ---------------------------- +# Security / Pepper Check +# ---------------------------- +check_pepper() { + echo + info "Checking Security Configuration..." + + if [[ -z "${INTERCEPT_PEPPER:-}" ]]; then + warn "INTERCEPT_PEPPER is not set in your environment." + echo -e "For security, you must generate a unique Pepper for password hashing." + + if ask_yes_no "Would you like me to generate one and show you how to save it?"; then + local NEW_PEPPER + NEW_PEPPER=$(openssl rand -hex 32) + ok "Generated Pepper: ${NEW_PEPPER}" + echo + echo "To make this permanent, run the following command:" + if [[ "$OS" == "macos" ]]; then + echo -e "${YELLOW}echo 'export INTERCEPT_PEPPER=\"$NEW_PEPPER\"' >> ~/.zshrc && source ~/.zshrc${NC}" + else + echo -e "${YELLOW}echo 'export INTERCEPT_PEPPER=\"$NEW_PEPPER\"' >> ~/.bashrc && source ~/.bashrc${NC}" + fi + echo + warn "IMPORTANT: Save this secret! If you lose it, you will be locked out of your database." + else + fail "Warning: INTERCEPT will fail to start without INTERCEPT_PEPPER." + fi + else + ok "INTERCEPT_PEPPER is already set." + fi +} + # ---------------------------- # Final summary / hard fail # ---------------------------- final_summary_and_hard_fail() { check_tools + check_pepper echo "============================================" echo diff --git a/utils/database.py b/utils/database.py index 1cc9baf9..e993d33a 100644 --- a/utils/database.py +++ b/utils/database.py @@ -12,7 +12,7 @@ from datetime import datetime from pathlib import Path from typing import Any -from werkzeug.security import generate_password_hash +from werkzeug.security import generate_password_hash, check_password_hash from config import ADMIN_USERNAME, ADMIN_PASSWORD, PEPPER logger = logging.getLogger('intercept.database') @@ -24,6 +24,55 @@ # Thread-local storage for connections _local = threading.local() +from werkzeug.security import check_password_hash, generate_password_hash + +def verify_user(username: str, password: str) -> dict | None: + """ + Verifies user credentials. If a legacy hash is found, it migrates + the user to the new PEPPER-based hashing automatically. + Returns user data (role) if successful, None otherwise. + """ + with get_db() as conn: + print(f"Verifying user: {username} at {datetime.now()}") + cursor = conn.execute( + 'SELECT id, password_hash, role FROM users WHERE username = ?', + (username,) + ) + user = cursor.fetchone() + + if not user: + return None + + user_id = user['id'] + current_hash = user['password_hash'] + user_role = user['role'] + + # 1. New verification (Password + Pepper) + if check_password_hash(current_hash, f"{password}{PEPPER}"): + print(f"User '{username}' new verified at {datetime.now()}") + return {"role": user_role} + + # 2. Legacy fallback (Password only) + if check_password_hash(current_hash, password): + print(f"User '{username}' legacy verified at {datetime.now()}") + logger.info(f"Legacy hash detected for user '{username}'. Migrating...") + + # Upgrade the hash to include the Pepper + new_hash = generate_password_hash(f"{password}{PEPPER}") + + try: + with get_db() as conn: + conn.execute( + 'UPDATE users SET password_hash = ? WHERE id = ?', + (new_hash, user_id) + ) + return {"role": user_role} + except Exception as e: + logger.error(f"Migration failed for {username}, but granting access: {e}") + return {"role": user_role} + + return None + def get_db_path() -> Path: """Get the database file path, creating directory if needed.""" From 3a23aa396a5e99508a0267534e6e5d0d818a30f5 Mon Sep 17 00:00:00 2001 From: Jon Ander Oribe Date: Mon, 26 Jan 2026 15:51:17 +0100 Subject: [PATCH 3/7] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 124cdb0a..02da0ca9 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Support the developer of this open-source project ``` **1. Clone and run:** -Generate a secure PEPPER with "openssl rand -hex 32" and save it in your computer. +Generate a secure PEPPER with "openssl rand -hex 32" and save it in your computer. This is necessary if you are deploying with requirements or UV. In case of ./setup.sh the PEPPER will be ask you to generate during the installation. macOS: From 8ccc9cd412e489dc18622be092ef44ca8601a402 Mon Sep 17 00:00:00 2001 From: Jon Ander Oribe Date: Mon, 26 Jan 2026 16:14:56 +0100 Subject: [PATCH 4/7] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 02da0ca9..58acb726 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,8 @@ Support the developer of this open-source project ``` **1. Clone and run:** +The new version of INTERCEPT includes PEPPER as a security enhancement, so all users, both new and existing, must follow the steps below to use the platform. + Generate a secure PEPPER with "openssl rand -hex 32" and save it in your computer. This is necessary if you are deploying with requirements or UV. In case of ./setup.sh the PEPPER will be ask you to generate during the installation. macOS: From 760dd4a978bce0b590c06f9bfe1b84e8bd3d58dd Mon Sep 17 00:00:00 2001 From: Jon Ander Oribe Date: Mon, 26 Jan 2026 16:22:40 +0100 Subject: [PATCH 5/7] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 58acb726..c7c784a8 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Support the developer of this open-source project ``` **1. Clone and run:** -The new version of INTERCEPT includes PEPPER as a security enhancement, so all users, both new and existing, must follow the steps below to use the platform. +The new version of INTERCEPT includes PEPPER as a security enhancement, so all users, both new and existing, must follow the steps below to use the platform. SAVE your PEPPER for future updates or you will not be able to log in with your usual user account. Generate a secure PEPPER with "openssl rand -hex 32" and save it in your computer. This is necessary if you are deploying with requirements or UV. In case of ./setup.sh the PEPPER will be ask you to generate during the installation. From 230bc3fcbcebb3b422c05760c075ddbd9180de75 Mon Sep 17 00:00:00 2001 From: Jon Ander Oribe Date: Wed, 28 Jan 2026 07:43:09 +0100 Subject: [PATCH 6/7] Enforce PEPPER env var and add user auth migration tests Require the INTERCEPT_PEPPER environment variable at startup and raise an error if unset. Add comprehensive tests for user verification, including legacy password hash migration to peppered hashes, successful and failed logins, and user-not-found scenarios. Also, remove unused PEPPER import from app.py. --- app.py | 2 +- config.py | 2 + tests/test_database.py | 98 ++++++++++++++++++++++++++++++++++++++++++ utils/database.py | 2 - 4 files changed, 101 insertions(+), 3 deletions(-) diff --git a/app.py b/app.py index 2914205e..0a492f00 100644 --- a/app.py +++ b/app.py @@ -27,7 +27,7 @@ from flask import Flask, render_template, jsonify, send_file, Response, request,redirect, url_for, flash, session from werkzeug.security import check_password_hash -from config import PEPPER, VERSION, CHANGELOG +from config import VERSION, CHANGELOG from utils.dependencies import check_tool, check_all_dependencies, TOOL_DEPENDENCIES from utils.process import cleanup_stale_processes from utils.sdr import SDRFactory diff --git a/config.py b/config.py index 27115362..1738fc6a 100644 --- a/config.py +++ b/config.py @@ -148,6 +148,8 @@ def _get_env_bool(key: str, default: bool) -> bool: ADMIN_USERNAME = _get_env('ADMIN_USERNAME', 'admin') ADMIN_PASSWORD = _get_env('ADMIN_PASSWORD', 'admin') PEPPER = os.environ.get('INTERCEPT_PEPPER') +if PEPPER is None: + raise RuntimeError("INTERCEPT_PEPPER environment variable must be set. See README for setup instructions.") def configure_logging() -> None: """Configure application logging.""" diff --git a/tests/test_database.py b/tests/test_database.py index 93309edd..8ab01ae5 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -5,6 +5,11 @@ import pytest from pathlib import Path from unittest.mock import patch +from unittest.mock import patch, MagicMock +from utils.database import verify_user, generate_password_hash, check_password_hash + +# Mock configuration to ensure the PEPPER is consistent during tests +MOCK_PEPPER = "secret_pepper_123" # Need to patch DB_PATH before importing database module @pytest.fixture(autouse=True) @@ -254,3 +259,96 @@ def test_correlation_upsert(self, temp_db): if c['wifi_mac'] == 'AA:AA:AA:AA:AA:AA'] assert len(matching) == 1 assert matching[0]['confidence'] == 0.9 + +###### +# Tests for user verification and password hash migration +###### + +@pytest.fixture +def mock_db_user(): + """Simulates a database response for a user.""" + def _create_user(id_val, pw_hash, role="admin"): + return {"id": id_val, "password_hash": pw_hash, "role": role} + return _create_user + +### 1. Test: Successful Login with New Hash (Peppered) +@patch('utils.database.PEPPER', MOCK_PEPPER) +@patch('utils.database.get_db') +def test_verify_user_success_new_hash(mock_get_db, mock_db_user): + # Generate a hash that ALREADY includes the pepper + password = "my_secure_password" + peppered_hash = generate_password_hash(f"{password}{MOCK_PEPPER}") + + # Configure the DB mock + mock_conn = MagicMock() + mock_conn.execute.return_value.fetchone.return_value = mock_db_user(1, peppered_hash) + mock_get_db.return_value.__enter__.return_value = mock_conn + + result = verify_user("test_user", password) + + assert result is not None + assert result["role"] == "admin" + +### 2. Test: Legacy Hash Detection and Automatic Migration +@patch('utils.database.PEPPER', MOCK_PEPPER) +@patch('utils.database.get_db') +def test_verify_user_legacy_migration(mock_get_db, mock_db_user): + password = "old_password" + # Create a hash WITHOUT the pepper (simulating old data) + legacy_hash = generate_password_hash(password) + + mock_conn = MagicMock() + mock_conn.execute.return_value.fetchone.return_value = mock_db_user(2, legacy_hash) + mock_get_db.return_value.__enter__.return_value = mock_conn + + # Act: Verify the user + result = verify_user("legacy_user", password) + + # ASSERTIONS + # 1. Access must be granted (Fallback worked) + assert result is not None + assert result["role"] == "admin" + + # 2. Verify the UPDATE logic was triggered + update_calls = [ + call for call in mock_conn.execute.call_args_list + if 'UPDATE users SET password_hash' in call[0][0] + ] + assert len(update_calls) == 1, "The database was not updated with the new hash" + + # 3. CRITICAL: Verify the updated hash now includes the PEPPER + # We extract the 'new_hash' argument from the execute(query, params) call + new_hash_in_db = update_calls[0][0][1][0] + + # It must fail WITHOUT the pepper now + assert check_password_hash(new_hash_in_db, password) is False + # It must succeed WITH the pepper + assert check_password_hash(new_hash_in_db, f"{password}{MOCK_PEPPER}") is True + + print("✓ Migration successful: User granted access and hash upgraded with Pepper.") + +### 3. Test: Login Failure (Incorrect Password) +@patch('utils.database.PEPPER', MOCK_PEPPER) +@patch('utils.database.get_db') +def test_verify_user_wrong_password(mock_get_db, mock_db_user): + correct_password = "real_password" + peppered_hash = generate_password_hash(f"{correct_password}{MOCK_PEPPER}") + + mock_conn = MagicMock() + mock_conn.execute.return_value.fetchone.return_value = mock_db_user(3, peppered_hash) + mock_get_db.return_value.__enter__.return_value = mock_conn + + # Attempt login with a typo/wrong password + result = verify_user("test_user", "wrong_password") + + assert result is None + +### 4. Test: User Does Not Exist +@patch('utils.database.get_db') +def test_verify_user_not_found(mock_get_db): + mock_conn = MagicMock() + mock_conn.execute.return_value.fetchone.return_value = None + mock_get_db.return_value.__enter__.return_value = mock_conn + + result = verify_user("ghost_user", "1234") + assert result is None \ No newline at end of file diff --git a/utils/database.py b/utils/database.py index e993d33a..c2e47681 100644 --- a/utils/database.py +++ b/utils/database.py @@ -24,8 +24,6 @@ # Thread-local storage for connections _local = threading.local() -from werkzeug.security import check_password_hash, generate_password_hash - def verify_user(username: str, password: str) -> dict | None: """ Verifies user credentials. If a legacy hash is found, it migrates From 5918c662f6d606849b9a5bc0c397f1a900d2c299 Mon Sep 17 00:00:00 2001 From: Jon Ander Oribe Date: Wed, 28 Jan 2026 07:53:30 +0100 Subject: [PATCH 7/7] Update database.py Deleting prints --- utils/database.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/utils/database.py b/utils/database.py index 0eae42cc..da89ab00 100644 --- a/utils/database.py +++ b/utils/database.py @@ -31,7 +31,6 @@ def verify_user(username: str, password: str) -> dict | None: Returns user data (role) if successful, None otherwise. """ with get_db() as conn: - print(f"Verifying user: {username} at {datetime.now()}") cursor = conn.execute( 'SELECT id, password_hash, role FROM users WHERE username = ?', (username,) @@ -47,12 +46,10 @@ def verify_user(username: str, password: str) -> dict | None: # 1. New verification (Password + Pepper) if check_password_hash(current_hash, f"{password}{PEPPER}"): - print(f"User '{username}' new verified at {datetime.now()}") return {"role": user_role} # 2. Legacy fallback (Password only) if check_password_hash(current_hash, password): - print(f"User '{username}' legacy verified at {datetime.now()}") logger.info(f"Legacy hash detected for user '{username}'. Migrating...") # Upgrade the hash to include the Pepper @@ -1997,3 +1994,4 @@ def cleanup_old_payloads(max_age_hours: int = 24) -> int: WHERE received_at < datetime('now', ?) ''', (f'-{max_age_hours} hours',)) return cursor.rowcount +