"""Kurzlebige Redis-Sessions für das öffentliche QR-Stempeln. Anders als der Kiosk (15 min, vertrauenswürdiges Wandterminal) läuft das öffentliche Stempeln auf einem privaten Handy über einen ungesicherten öffentlichen Endpunkt. Deshalb sehr kurze TTL (120s): nach Anmeldung genügend Zeit zum Ein-/Ausstempeln, danach automatischer Verfall. Redis ist Pflicht. Bei Redis-Ausfall → 503. """ from __future__ import annotations import json import uuid from datetime import datetime, timezone from typing import Optional from fastapi import HTTPException PUBLIC_STAMP_SESSION_TTL = 120 # 2 Minuten SESSION_KEY_PREFIX = "public_stamp_session:" class PublicStampSessionService: def _get_redis(self): from app.core.redis import get_redis_client client = get_redis_client() if client is None: raise HTTPException( status_code=503, detail="Stempel-Service nicht verfügbar (Redis nicht erreichbar).", ) return client async def create_session(self, user_id: uuid.UUID, company_id: uuid.UUID) -> str: redis = self._get_redis() session_token = str(uuid.uuid4()) key = SESSION_KEY_PREFIX + session_token payload = { "user_id": str(user_id), "company_id": str(company_id), "created_at": datetime.now(timezone.utc).isoformat(), } try: redis.setex(key, PUBLIC_STAMP_SESSION_TTL, json.dumps(payload)) except Exception as exc: raise HTTPException(status_code=503, detail=f"Session konnte nicht erstellt werden: {exc}") return session_token async def get_session(self, session_token: str) -> Optional[dict]: redis = self._get_redis() try: data = redis.get(SESSION_KEY_PREFIX + session_token) except Exception as exc: raise HTTPException(status_code=503, detail=f"Session-Lookup fehlgeschlagen: {exc}") if data is None: return None return json.loads(data) async def require_session(self, session_token: str) -> dict: session = await self.get_session(session_token) if session is None: raise HTTPException( status_code=401, detail="Stempel-Session abgelaufen. Bitte Personalnummer und PIN erneut eingeben.", ) return session async def invalidate_session(self, session_token: str) -> None: redis = self._get_redis() try: redis.delete(SESSION_KEY_PREFIX + session_token) except Exception: pass # Best-effort public_stamp_session_service = PublicStampSessionService()