.
This commit is contained in:
parent
76103ac0f2
commit
d57355a9c9
@ -13,7 +13,6 @@ dependencies = [
|
|||||||
"python-dotenv>=1.0",
|
"python-dotenv>=1.0",
|
||||||
"fastapi>=0.110",
|
"fastapi>=0.110",
|
||||||
"uvicorn[standard]>=0.27",
|
"uvicorn[standard]>=0.27",
|
||||||
"pydantic-settings>=2.2",
|
|
||||||
|
|
||||||
"pyhanko>=0.25",
|
"pyhanko>=0.25",
|
||||||
"cryptography>=42",
|
"cryptography>=42",
|
||||||
|
|||||||
@ -1,54 +1,114 @@
|
|||||||
from pydantic_settings import BaseSettings
|
import os
|
||||||
from pydantic import Field, model_validator
|
|
||||||
|
|
||||||
|
|
||||||
class AppSettings(BaseSettings):
|
class AppSettings:
|
||||||
# --------------------
|
def __init__(self) -> None:
|
||||||
# Application
|
# --------------------
|
||||||
# --------------------
|
# Application
|
||||||
app_env: str = Field(default="local")
|
# --------------------
|
||||||
log_level: str = Field(default="INFO")
|
self.app_env = self._get_env(
|
||||||
|
"APP_ENV",
|
||||||
|
default="local",
|
||||||
|
normalize=self._normalize_lower,
|
||||||
|
)
|
||||||
|
|
||||||
# --------------------
|
self.log_level = self._get_env(
|
||||||
# Secret manager
|
"LOG_LEVEL",
|
||||||
# --------------------
|
default="INFO",
|
||||||
secret_provider: str = Field(default="fake")
|
normalize=self._normalize_upper,
|
||||||
gcp_project_id: str | None = None
|
)
|
||||||
|
|
||||||
# --------------------
|
# --------------------
|
||||||
# PDF signing
|
# Secret manager
|
||||||
# --------------------
|
# --------------------
|
||||||
pdf_cert_secret_name: str
|
self.secret_provider = self._get_env(
|
||||||
pdf_cert_password_secret_name: str
|
"SECRET_PROVIDER",
|
||||||
|
default="fake",
|
||||||
|
normalize=self._normalize_lower,
|
||||||
|
)
|
||||||
|
|
||||||
@model_validator(mode="after")
|
self.gcp_project_id = self._get_env(
|
||||||
def validate_required_settings(self) -> "AppSettings":
|
"GCP_PROJECT_ID",
|
||||||
|
default=None,
|
||||||
|
required=self.secret_provider == "google",
|
||||||
|
)
|
||||||
|
|
||||||
|
# --------------------
|
||||||
|
# PDF signing
|
||||||
|
# --------------------
|
||||||
|
self.pdf_cert_secret_name = self._get_env(
|
||||||
|
"PDF_CERT_SECRET_NAME",
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.pdf_cert_password_secret_name = self._get_env(
|
||||||
|
"PDF_CERT_PASSWORD_SECRET_NAME",
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# --------------------
|
||||||
|
# Cross-field validation
|
||||||
|
# --------------------
|
||||||
|
self._validate()
|
||||||
|
|
||||||
|
# ======================================================
|
||||||
|
# Helpers
|
||||||
|
# ======================================================
|
||||||
|
|
||||||
|
def _get_env(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
*,
|
||||||
|
default: str | None = None,
|
||||||
|
required: bool = False,
|
||||||
|
normalize=None,
|
||||||
|
) -> str | None:
|
||||||
"""
|
"""
|
||||||
Fail fast if required configuration is missing.
|
Read and validate an environment variable.
|
||||||
"""
|
"""
|
||||||
|
raw = os.environ.get(name, default)
|
||||||
|
|
||||||
if not self.pdf_cert_secret_name:
|
if raw is None:
|
||||||
raise ValueError("PDF_CERT_SECRET_NAME is required")
|
if required:
|
||||||
|
raise ValueError(f"Environment variable {name} is required")
|
||||||
|
return None
|
||||||
|
|
||||||
if not self.pdf_cert_password_secret_name:
|
if not isinstance(raw, str):
|
||||||
raise ValueError("PDF_CERT_PASSWORD_SECRET_NAME is required")
|
raise ValueError(f"Environment variable {name} must be a string")
|
||||||
|
|
||||||
# ---- Secret provider validation ----
|
value = raw.strip()
|
||||||
if self.secret_provider == "google":
|
|
||||||
if not self.gcp_project_id:
|
|
||||||
raise ValueError(
|
|
||||||
"GCP_PROJECT_ID is required when SECRET_PROVIDER=google"
|
|
||||||
)
|
|
||||||
|
|
||||||
# ---- PDF signing validation ----
|
if required and value == "":
|
||||||
if self.app_env == "prod":
|
raise ValueError(f"Environment variable {name} cannot be empty")
|
||||||
if not self.pdf_pfx_password:
|
|
||||||
raise ValueError(
|
|
||||||
"PDF_PFX_PASSWORD is required in production"
|
|
||||||
)
|
|
||||||
|
|
||||||
return self
|
if normalize:
|
||||||
|
value = normalize(value)
|
||||||
|
|
||||||
class Config:
|
return value
|
||||||
env_file = ".env"
|
|
||||||
case_sensitive = False
|
@staticmethod
|
||||||
|
def _normalize_lower(value: str) -> str:
|
||||||
|
return value.strip().lower()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _normalize_upper(value: str) -> str:
|
||||||
|
return value.strip().upper()
|
||||||
|
|
||||||
|
# ======================================================
|
||||||
|
# Validations
|
||||||
|
# ======================================================
|
||||||
|
|
||||||
|
def _validate(self) -> None:
|
||||||
|
allowed_providers = {"fake", "infisical", "google"}
|
||||||
|
|
||||||
|
if self.secret_provider not in allowed_providers:
|
||||||
|
raise ValueError(
|
||||||
|
f"Invalid SECRET_PROVIDER: {self.secret_provider}. "
|
||||||
|
f"Allowed values: {allowed_providers}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.app_env not in {"local", "prod"}:
|
||||||
|
raise ValueError(
|
||||||
|
f"Invalid APP_ENV: {self.app_env}. "
|
||||||
|
"Allowed values: local | prod"
|
||||||
|
)
|
||||||
|
|||||||
@ -10,7 +10,7 @@ load_dotenv()
|
|||||||
# 👇 FAIL FAST: load settings at startup
|
# 👇 FAIL FAST: load settings at startup
|
||||||
get_settings()
|
get_settings()
|
||||||
|
|
||||||
app = FastAPI(title="Signing Service")
|
app = FastAPI(title="FactuGES Document Signing Service")
|
||||||
app.include_router(sign_router)
|
app.include_router(sign_router)
|
||||||
|
|
||||||
@app.get("/health")
|
@app.get("/health")
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user