feat: Cloud-Fundament - Auth, Persistenz und Mandanten-Trennung
- SQLite-Store (app/store.py) hinter Store-Interface: Nutzer + Sessions persistent - Bearer-Token-Auth (app/auth.py); Nutzerverwaltung via Admin-Key (POST /api/admin/users) - GET /api/me, PUT /api/me/prefs (dauerhafte Nutzer-Praeferenzen) - chat/speak/transcribe/sessions auth-geschuetzt; Mandanten-Trennung (fremde Session -> 403) - Route-Aufloesung: Defaults < Profil < ENV < Nutzer-Prefs < Session < Request - SessionManager (in-memory) durch Store ersetzt - AUTH_ENABLED-Schalter (prod an, dev/Tests aus); DB_PATH/ADMIN_API_KEY - Doku aktualisiert (README, BEDIENUNGSANLEITUNG, Architektur, deploy-env); data/ gitignored - Tests: 29 gruen (Auth, Mandanten, Persistenz, Routing) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
293ed257db
commit
e0e69fdf15
22 changed files with 625 additions and 57 deletions
|
|
@ -23,9 +23,20 @@ from app.pipeline.input_cleaner import InputCleaner
|
|||
from app.pipeline.spoken_response_adapter import SpokenResponseAdapter
|
||||
from app.pipeline.tts_normalizer import TTSNormalizer
|
||||
from app.core.orchestrator import Orchestrator
|
||||
from app.core.session_manager import SessionManager
|
||||
from app.store import SQLiteStore, Store, User
|
||||
|
||||
session_manager = SessionManager()
|
||||
# ---------------------------------------------------------------------------
|
||||
# Persistenz-Store: Modul-Singleton (SQLite). Spaetere Backends implementieren
|
||||
# dasselbe Store-Interface, ohne die App zu aendern.
|
||||
# ---------------------------------------------------------------------------
|
||||
_store: Store | None = None
|
||||
|
||||
|
||||
def get_store() -> Store:
|
||||
global _store
|
||||
if _store is None:
|
||||
_store = SQLiteStore(settings.db_path)
|
||||
return _store
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Provider-Registries: Modul austauschbar via Name, ohne Kern-Code zu aendern.
|
||||
|
|
@ -136,17 +147,35 @@ class ResolvedRoute:
|
|||
}
|
||||
|
||||
|
||||
def get_session_route(session_id: str | None) -> dict:
|
||||
"""Liefert die gespeicherte Route einer Session (leeres dict ohne session_id)."""
|
||||
return session_manager.get(session_id) if session_id else {}
|
||||
def get_session_route(session_id: str | None, user: User | None = None) -> dict:
|
||||
"""Liefert die gespeicherte Route einer Session des Nutzers (leeres dict sonst).
|
||||
|
||||
Gehoert die Session einem anderen Nutzer, wird SessionOwnershipError ausgeloest.
|
||||
"""
|
||||
if not session_id:
|
||||
return {}
|
||||
session = get_store().get_session(session_id)
|
||||
if session is None:
|
||||
return {}
|
||||
if user is not None and session.user_id != user.id:
|
||||
from app.store import SessionOwnershipError
|
||||
|
||||
raise SessionOwnershipError(
|
||||
f"Session {session_id!r} gehoert einem anderen Nutzer"
|
||||
)
|
||||
return session.data
|
||||
|
||||
|
||||
def resolve_route(
|
||||
user: User | None = None,
|
||||
session_id: str | None = None,
|
||||
overrides: dict | None = None,
|
||||
cfg: Settings = settings,
|
||||
) -> ResolvedRoute:
|
||||
"""Loest die effektive Route aus Defaults, Session und Request-Overrides auf."""
|
||||
"""Loest die effektive Route auf.
|
||||
|
||||
Praezedenz (hoeher gewinnt): Defaults < Nutzer-Prefs < Session-Route < Request.
|
||||
"""
|
||||
resolved = {
|
||||
"input_endpoint": cfg.default_input_endpoint,
|
||||
"output_endpoint": cfg.default_output_endpoint,
|
||||
|
|
@ -156,10 +185,11 @@ def resolve_route(
|
|||
"language": cfg.default_language,
|
||||
}
|
||||
|
||||
session_route = get_session_route(session_id)
|
||||
user_prefs = user.prefs if user is not None else {}
|
||||
session_route = get_session_route(session_id, user)
|
||||
request_overrides = overrides or {}
|
||||
|
||||
for layer in (session_route, request_overrides):
|
||||
for layer in (user_prefs, session_route, request_overrides):
|
||||
for key in ROUTE_KEYS:
|
||||
value = layer.get(key)
|
||||
if value is not None:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue