diff --git a/src/auth/common.py b/src/auth/common.py index 1492d5c..0fc7c14 100644 --- a/src/auth/common.py +++ b/src/auth/common.py @@ -37,8 +37,21 @@ # JWT settings -JWT_SECRET_KEY = config["auth"]["secret_jwt_key"] JWT_ALGORITHM = config["auth"]["jwt_algorithm"] +JWT_SECRET_KEY = config["auth"].get("secret_jwt_key") +JWT_SECRET_KEY_FILE = config["auth"].get("secret_jwt_key_file") +if JWT_SECRET_KEY_FILE: + if not os.path.exists(JWT_SECRET_KEY_FILE): + raise FileNotFoundError(f"secret_jwt_key file not found: {JWT_SECRET_KEY_FILE}") + try: + with open(JWT_SECRET_KEY_FILE, 'r') as f: + JWT_SECRET_KEY = f.read().strip() + except PermissionError: + raise PermissionError(f"Cannot read secret_jwt_key file: {JWT_SECRET_KEY_FILE}") + except Exception as e: + raise RuntimeError(f"Error reading secret_jwt_key file {JWT_SECRET_KEY_FILE}: {e}") +if JWT_SECRET_KEY is None: + raise RuntimeError("No secret_jwt_key provided (c.f. setting auth.secret_jwt_key)") # Server settings API_SERVER_URL = config["server"].get("api_server_url") diff --git a/src/datastores/sql/alembic/env.py b/src/datastores/sql/alembic/env.py index b7f5e90..39f2120 100644 --- a/src/datastores/sql/alembic/env.py +++ b/src/datastores/sql/alembic/env.py @@ -6,6 +6,7 @@ # Import OpenRelik models so Alembic finds them. from datastores.sql.database import BaseModel +from datastores.sql.database import get_db_url from datastores.sql.models.file import ( File, FileAttribute, @@ -70,7 +71,7 @@ def run_migrations_online() -> None: and associate a connection with the context. """ - url = os.environ.get("SQLALCHEMY_DATABASE_URL") + url = get_db_url() connectable = create_engine(url) # connectable = engine_from_config( diff --git a/src/datastores/sql/database.py b/src/datastores/sql/database.py index e88b90f..9914fbf 100644 --- a/src/datastores/sql/database.py +++ b/src/datastores/sql/database.py @@ -43,22 +43,50 @@ from config import config -SQLALCHEMY_DATABASE_URL = config["datastores"]["sqlalchemy"]["database_url"] -SQLALCHEMY_DATABASE_URL_ENV = os.getenv("SQLALCHEMY_DATABASE_URL") - # Set SQLAlchemy connection pool settings SQLALCHEMY_POOL_SIZE = 20 SQLALCHEMY_MAX_OVERFLOW = 30 SQLALCHEMY_POOL_TIMEOUT = 60 -if SQLALCHEMY_DATABASE_URL_ENV: - SQLALCHEMY_DATABASE_URL = SQLALCHEMY_DATABASE_URL_ENV - # For SQLite you need to set check_same_thread # engine = create_engine( # SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} # ) +def get_db_url() -> str: + # config file + sqlalchemy_database_url = config["datastores"]["sqlalchemy"].get("database_url") + sqlalchemy_database_url_file = config["datastores"]["sqlalchemy"].get("database_url_file") + + # env vars + sqlalchemy_database_url_env = os.getenv("SQLALCHEMY_DATABASE_URL") + sqlalchemy_database_url_env_file = os.getenv("SQLALCHEMY_DATABASE_URL_FILE") + + # environment variable takes precedence + if sqlalchemy_database_url_env_file: + sqlalchemy_database_url_file = sqlalchemy_database_url_env_file + + # file takes precedence over direct configuration + if sqlalchemy_database_url_file: + if not os.path.exists(sqlalchemy_database_url_file): + raise FileNotFoundError(f"Database URL file not found: {sqlalchemy_database_url_file}") + try: + with open(sqlalchemy_database_url_file, 'r') as f: + sqlalchemy_database_url = f.read().strip() + except PermissionError: + raise PermissionError(f"Cannot read database URL file: {sqlalchemy_database_url_file}") + except Exception as e: + raise RuntimeError(f"Error reading database URL file {sqlalchemy_database_url_file}: {e}") + elif sqlalchemy_database_url_env: + sqlalchemy_database_url = sqlalchemy_database_url_env + + if sqlalchemy_database_url is None: + raise RuntimeError("No database URL provided (c.f. setting datastores.sqlalchemy.database_url)") + + return sqlalchemy_database_url + +SQLALCHEMY_DATABASE_URL = get_db_url() + engine = create_engine( SQLALCHEMY_DATABASE_URL, pool_pre_ping=True, diff --git a/src/main.py b/src/main.py index 7fe7bfc..21c9d81 100644 --- a/src/main.py +++ b/src/main.py @@ -96,8 +96,28 @@ async def lifespan(app: FastAPI): # Create the main app + +secret_key = config["auth"].get("secret_session_key") +secret_key_file = config["auth"].get("secret_session_key_file") +if secret_key_file: + if not os.path.exists(secret_key_file): + raise FileNotFoundError(f"secret_session_key file not found: {secret_key_file}") + try: + with open(secret_key_file, 'r') as f: + secret_key = f.read().strip() + except PermissionError: + raise PermissionError(f"Cannot read secret_session_key file: {secret_key_file}") + except Exception as e: + raise RuntimeError(f"Error reading secret_session_key file {secret_key_file}: {e}") + +if secret_key is None: + raise RuntimeError("No secret_session_key provided (c.f. setting auth.secret_session_key)") + app = FastAPI(lifespan=lifespan) -app.add_middleware(SessionMiddleware, secret_key=config["auth"]["secret_session_key"]) +app.add_middleware(SessionMiddleware, secret_key=secret_key) + +# avoid further usage (of course the secret is still stored in the middleware component) +del secret_key # Create app for API version 1 api_v1 = FastAPI()