Some checks failed
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Failing after 0s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Failing after 4m0s
Test Suite / playwright-tests (push) Failing after 3m41s
- Introduced `email_verified` and `account_state` attributes in the `TenantContext` to improve user state management. - Updated the `resolve_tenant_context` function to dynamically fetch `email_verified` status from the database and determine `account_state` based on user roles and memberships. - Implemented `assert_min_account_state` checks across various endpoints to enforce access control based on user account status. - Incremented version to 1.1.0 in version.py to reflect these enhancements in tenant context management and access control.
78 lines
2.0 KiB
Python
78 lines
2.0 KiB
Python
"""
|
|
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)
|