shinkan-jinkendo/backend/account_lifecycle.py
Lars 30dc30c7aa
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
Enhance Tenant Context and Access Control Features
- 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.
2026-06-06 21:10:52 +02:00

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)