From 2ca82fa00f145c52fa4dcabe9bcf5954900a2b82 Mon Sep 17 00:00:00 2001 From: lucasbutzke Date: Wed, 30 Jul 2025 19:21:58 -0400 Subject: [PATCH] [RTOP-26] Change .env variables generation to python and verifing if are valid at inicialization --- background_installer.sh | 6 +---- webserver/config.py | 50 ++++++++++++++++++++++++++++++++++++++--- webserver/restapi.py | 2 +- 3 files changed, 49 insertions(+), 9 deletions(-) diff --git a/background_installer.sh b/background_installer.sh index ca6c84e..16cb7a2 100755 --- a/background_installer.sh +++ b/background_installer.sh @@ -215,15 +215,11 @@ function generate_env() { fi cat < .env -FLASK_ENV=development -SQLALCHEMY_DATABASE_URI=sqlite:///restapi.db -JWT_SECRET_KEY=$(openssl rand -hex 32) -PEPPER=$(openssl rand -hex 32) EOF # Set restrictive permissions (readable only by owner) chmod 600 .env - echo ".env file created with random JWT_SECRET_KEY and PEPPER." + echo ".env file created." } diff --git a/webserver/config.py b/webserver/config.py index 1405447..2b7cc0f 100644 --- a/webserver/config.py +++ b/webserver/config.py @@ -1,23 +1,67 @@ from dotenv import load_dotenv import os +import re +import secrets +import logging from pathlib import Path from dotenv import load_dotenv # Always resolve .env relative to the repo root to guarantee it is found ENV_PATH = Path(__file__).resolve().parent.parent / ".env" -load_dotenv(dotenv_path=ENV_PATH, override=False) BASE_DIR = os.path.abspath(os.path.dirname(__file__)) +logger = logging.getLogger(__name__) -class Config: - # Mandatory settings – raise immediately if not provided +# Function to validate environment variable values +def is_valid_env(var_name, value): + if var_name == "SQLALCHEMY_DATABASE_URI": + return value.startswith("sqlite:///") or value.startswith("postgresql://") or value.startswith("mysql://") + elif var_name in ("JWT_SECRET_KEY", "PEPPER"): + return bool(re.fullmatch(r"[a-fA-F0-9]{64}", value)) + return False + +# Function to generate a new .env file with valid defaults +def generate_env_file(): + jwt = secrets.token_hex(32) + pepper = secrets.token_hex(32) + uri = "sqlite:///restapi.db" + + with open(ENV_PATH, "w") as f: + f.write("FLASK_ENV=development\n") + f.write(f"SQLALCHEMY_DATABASE_URI={uri}\n") + f.write(f"JWT_SECRET_KEY={jwt}\n") + f.write(f"PEPPER={pepper}\n") + + os.chmod(ENV_PATH, 0o600) + logger.info(f"✅ .env file created at {ENV_PATH}") + +# Load .env file +if not os.path.isfile(ENV_PATH): + logger.warning("⚠️ .env file not found, creating one...") + generate_env_file() + +load_dotenv(dotenv_path=ENV_PATH, override=False) + +# Mandatory settings – raise immediately if not provided +try: for _var in ("SQLALCHEMY_DATABASE_URI", "JWT_SECRET_KEY", "PEPPER"): if not os.getenv(_var): raise RuntimeError(f"Environment variable '{_var}' is required but not set") + for var in ("SQLALCHEMY_DATABASE_URI", "JWT_SECRET_KEY", "PEPPER"): + val = os.getenv(var) + if not val or not is_valid_env(var, val): # <<<<<<<< CALL HAPPENS HERE + raise RuntimeError(f"Environment variable '{var}' is invalid or missing") +except RuntimeError as e: + logger.error(f"❌ {e}") + logger.info("🔁 Regenerating .env with new valid values...") + generate_env_file() + load_dotenv(ENV_PATH) +class Config: SQLALCHEMY_DATABASE_URI = os.environ["SQLALCHEMY_DATABASE_URI"] JWT_SECRET_KEY = os.environ["JWT_SECRET_KEY"] PEPPER = os.environ["PEPPER"] + class DevConfig(Config): SQLALCHEMY_TRACK_MODIFICATIONS = False # keep performance parity with prod DEBUG = True diff --git a/webserver/restapi.py b/webserver/restapi.py index a01da58..94eec86 100644 --- a/webserver/restapi.py +++ b/webserver/restapi.py @@ -6,8 +6,8 @@ from werkzeug.security import generate_password_hash, check_password_hash import logging from typing import Callable, Optional -import config +import config import os env = os.getenv("FLASK_ENV", "development")