feat: implement admin bootstrap settings and ensure default roles and admin account
This commit is contained in:
@@ -10,6 +10,17 @@ from typing import Optional
|
||||
from services.security import JWTSettings
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class AdminBootstrapSettings:
|
||||
"""Default administrator bootstrap configuration."""
|
||||
|
||||
email: str
|
||||
username: str
|
||||
password: str
|
||||
roles: tuple[str, ...]
|
||||
force_reset: bool
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class SessionSettings:
|
||||
"""Cookie and header configuration for session token transport."""
|
||||
@@ -40,6 +51,11 @@ class Settings:
|
||||
session_header_name: str = "Authorization"
|
||||
session_header_prefix: str = "Bearer"
|
||||
session_allow_header_fallback: bool = True
|
||||
admin_email: str = "admin@calminer.local"
|
||||
admin_username: str = "admin"
|
||||
admin_password: str = "ChangeMe123!"
|
||||
admin_roles: tuple[str, ...] = ("admin",)
|
||||
admin_force_reset: bool = False
|
||||
|
||||
@classmethod
|
||||
def from_environment(cls) -> "Settings":
|
||||
@@ -74,6 +90,21 @@ class Settings:
|
||||
session_allow_header_fallback=cls._bool_from_env(
|
||||
"CALMINER_SESSION_ALLOW_HEADER_FALLBACK", True
|
||||
),
|
||||
admin_email=os.getenv(
|
||||
"CALMINER_SEED_ADMIN_EMAIL", "admin@calminer.local"
|
||||
),
|
||||
admin_username=os.getenv(
|
||||
"CALMINER_SEED_ADMIN_USERNAME", "admin"
|
||||
),
|
||||
admin_password=os.getenv(
|
||||
"CALMINER_SEED_ADMIN_PASSWORD", "ChangeMe123!"
|
||||
),
|
||||
admin_roles=cls._parse_admin_roles(
|
||||
os.getenv("CALMINER_SEED_ADMIN_ROLES")
|
||||
),
|
||||
admin_force_reset=cls._bool_from_env(
|
||||
"CALMINER_SEED_FORCE", False
|
||||
),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@@ -98,6 +129,22 @@ class Settings:
|
||||
return False
|
||||
return default
|
||||
|
||||
@staticmethod
|
||||
def _parse_admin_roles(raw_value: str | None) -> tuple[str, ...]:
|
||||
if not raw_value:
|
||||
return ("admin",)
|
||||
parts = [segment.strip()
|
||||
for segment in raw_value.split(",") if segment.strip()]
|
||||
if "admin" not in parts:
|
||||
parts.insert(0, "admin")
|
||||
seen: set[str] = set()
|
||||
ordered: list[str] = []
|
||||
for role_name in parts:
|
||||
if role_name not in seen:
|
||||
ordered.append(role_name)
|
||||
seen.add(role_name)
|
||||
return tuple(ordered)
|
||||
|
||||
def jwt_settings(self) -> JWTSettings:
|
||||
"""Build runtime JWT settings compatible with token helpers."""
|
||||
|
||||
@@ -122,6 +169,17 @@ class Settings:
|
||||
allow_header_fallback=self.session_allow_header_fallback,
|
||||
)
|
||||
|
||||
def admin_bootstrap_settings(self) -> AdminBootstrapSettings:
|
||||
"""Return configured admin bootstrap settings."""
|
||||
|
||||
return AdminBootstrapSettings(
|
||||
email=self.admin_email,
|
||||
username=self.admin_username,
|
||||
password=self.admin_password,
|
||||
roles=self.admin_roles,
|
||||
force_reset=self.admin_force_reset,
|
||||
)
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def get_settings() -> Settings:
|
||||
|
||||
Reference in New Issue
Block a user