""" Account-Lifecycle (CAPABILITY_CATALOG.v1.md §3, M3 C0). Zustände: unverified → verified_pending_club → active_member; platform_admin separat. """ from __future__ import annotations import os from typing import TYPE_CHECKING, Optional from fastapi import HTTPException from club_tenancy import is_platform_admin if TYPE_CHECKING: from tenant_context import TenantContext _ACCOUNT_STATE_RANK = { "unverified": 1, "verified_pending_club": 2, "active_member": 3, "platform_admin": 4, } def resolve_account_state( *, email_verified: bool, global_role: str, has_active_membership: bool, ) -> str: """Ermittelt account_state für ein Profil.""" if is_platform_admin(global_role): return "platform_admin" if not email_verified: return "unverified" if not has_active_membership: return "verified_pending_club" return "active_member" def account_state_satisfies(current: str, required: str) -> bool: """True wenn current mindestens required ist.""" cur = _ACCOUNT_STATE_RANK.get(current, 0) req = _ACCOUNT_STATE_RANK.get(required, 99) if current == "platform_admin": return True return cur >= req def account_gate_enforcement_enabled() -> bool: """Account-Gates aktiv (Default an — nur wenige Endpoints in M3).""" return os.getenv("ACCOUNT_GATE_ENFORCE", "1").strip() == "1" def assert_min_account_state( tenant: "TenantContext", min_state: str, *, endpoint: Optional[str] = None, ) -> None: """ Prüft Mindest-Account-Status. Wirft 403 wenn ACCOUNT_GATE_ENFORCE=1 (Default). """ current = getattr(tenant, "account_state", "active_member") ok = account_state_satisfies(current, min_state) if ok: return if not account_gate_enforcement_enabled(): return detail = ( f"Account-Status „{current}“ reicht nicht für diese Aktion " f"(erforderlich: {min_state})." ) if endpoint: detail = f"{detail} ({endpoint})" raise HTTPException(status_code=403, detail=detail)