Harden redemption flow and improve operational safety

This commit is contained in:
youbin
2026-03-31 08:13:38 +08:00
parent e5bab51f98
commit de130f1052
13 changed files with 1138 additions and 106 deletions

View File

@@ -2,6 +2,7 @@ from __future__ import annotations
import os
from dataclasses import dataclass, field
from pathlib import Path
from dotenv import load_dotenv
@@ -22,6 +23,24 @@ def _env_int(name: str, default: int) -> int:
return default
def _normalize_database_url(database_url: str, warnings: list[str]) -> str:
normalized = database_url.strip()
if not normalized:
return "sqlite:///redemption.db"
container_prefix = "sqlite:////app/"
if normalized.startswith(container_prefix) and not Path("/.dockerenv").exists():
local_relative = normalized.removeprefix(container_prefix)
project_root = Path(__file__).resolve().parent.parent
local_path = (project_root / local_relative).resolve()
warnings.append(
f"DATABASE_URL 使用容器路径时,已自动映射到本地路径 {local_path}"
)
return f"sqlite:///{local_path}"
return normalized
@dataclass
class Settings:
app_name: str
@@ -39,6 +58,7 @@ class Settings:
default_domain: str
default_usage_location: str
default_license_sku: str
license_assignment_required: bool
force_change_password: bool
graph_base_url: str
token_endpoint: str
@@ -68,6 +88,7 @@ class Settings:
"defaultDomain": self.default_domain,
"defaultUsageLocation": self.default_usage_location,
"defaultLicenseSku": self.default_license_sku,
"licenseAssignmentRequired": self.license_assignment_required,
"forceChangePassword": self.force_change_password,
"pageSize": self.default_page_size,
"maxPageSize": self.max_page_size,
@@ -84,6 +105,7 @@ def load_settings() -> Settings:
validation_errors: list[str] = []
warnings: list[str] = []
database_url = _normalize_database_url(os.getenv("DATABASE_URL", "sqlite:///redemption.db"), warnings)
required_fields = {
"CLIENT_ID": os.getenv("CLIENT_ID", "").strip(),
@@ -121,13 +143,14 @@ def load_settings() -> Settings:
default_domain=os.getenv("DEFAULT_DOMAIN", "").strip(),
default_usage_location=os.getenv("DEFAULT_USAGE_LOCATION", "US").strip() or "US",
default_license_sku=os.getenv("DEFAULT_LICENSE_SKU", "").strip(),
license_assignment_required=_env_bool("LICENSE_ASSIGNMENT_REQUIRED", False),
force_change_password=_env_bool("FORCE_CHANGE_PASSWORD", True),
graph_base_url=graph_base_url,
token_endpoint=token_endpoint,
scope=scope,
database_url=os.getenv("DATABASE_URL", "sqlite:///redemption.db").strip(),
database_url=database_url,
default_page_size=min(max(_env_int("DEFAULT_PAGE_SIZE", 25), 1), 100),
max_page_size=min(max(_env_int("MAX_PAGE_SIZE", 100), 10), 500),
validation_errors=tuple(validation_errors),
warnings=tuple(warnings),
)
)