Files
timemaster/backend/app/routers/kiosk.py
T
patrick dd3e069466 fix: router db.refresh() nach commit bricht RLS-Kontext
SET LOCAL Werte (bypass_rls, company_id) sind transaktions-gebunden.
Nach db.commit() ist der Kontext weg – ein nachfolgendes db.refresh()
läuft in einer neuen Transaktion ohne RLS-Kontext und liefert 0 Rows.

Da expire_on_commit=False gesetzt ist, sind alle Instanz-Attribute
nach dem Commit bereits im Speicher vorhanden. Die expliziten
db.refresh()-Aufrufe nach db.commit() in allen Routers sind daher
redundant und wurden entfernt.

test_rls.py: 6 neue Tests beweisen DB-seitige Mandanten-Isolation.
conftest.py: _apply_rls() wendet RLS-Policies auf Test-DB an.
migrations/0024: korrigiert auf op.execute(text()) API.
migrations/env.py: SET LOCAL außerhalb Transaktion entfernt.

Ergebnis: 8 failed (pre-existing), 126 passed – identisch zur Baseline vor RLS.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 22:34:48 +02:00

100 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from uuid import UUID
from fastapi import APIRouter, Depends, Header, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_db
from app.core.dependencies import require_role
from app.models.user import User, UserRole
from app.schemas.kiosk import KioskDeviceCreate, KioskDeviceCreated, KioskDeviceOut, KioskDeviceUpdate
from app.services.kiosk_service import kiosk_service
router = APIRouter(prefix="/kiosk", tags=["Kiosk"])
_admin_roles = (UserRole.COMPANY_ADMIN, UserRole.SUPER_ADMIN)
# ── Geräteverwaltung (COMPANY_ADMIN) ──────────────────────────────────────────
@router.get("/devices", response_model=list[KioskDeviceOut])
async def list_devices(
current_user: User = require_role(*_admin_roles),
db: AsyncSession = Depends(get_db),
):
"""Alle registrierten Kiosk-Geräte der Firma auflisten."""
return await kiosk_service.list_devices(current_user.company_id, db)
@router.post("/devices", response_model=KioskDeviceCreated, status_code=201)
async def create_device(
data: KioskDeviceCreate,
current_user: User = require_role(*_admin_roles),
db: AsyncSession = Depends(get_db),
):
"""Neues Kiosk-Gerät registrieren. Token wird nur einmalig zurückgegeben."""
device, raw_token = await kiosk_service.create_device(current_user.company_id, data, db)
await db.commit()
return KioskDeviceCreated(
**KioskDeviceOut.model_validate(device).model_dump(),
token=raw_token,
)
@router.get("/devices/{device_id}", response_model=KioskDeviceOut)
async def get_device(
device_id: UUID,
current_user: User = require_role(*_admin_roles),
db: AsyncSession = Depends(get_db),
):
return await kiosk_service.get_device(device_id, current_user.company_id, db)
@router.patch("/devices/{device_id}", response_model=KioskDeviceOut)
async def update_device(
device_id: UUID,
data: KioskDeviceUpdate,
current_user: User = require_role(*_admin_roles),
db: AsyncSession = Depends(get_db),
):
device = await kiosk_service.update_device(device_id, current_user.company_id, data, db)
await db.commit()
return KioskDeviceOut.model_validate(device)
@router.post("/devices/{device_id}/rotate-token", response_model=KioskDeviceCreated)
async def rotate_token(
device_id: UUID,
current_user: User = require_role(*_admin_roles),
db: AsyncSession = Depends(get_db),
):
"""Token rotieren das alte Token wird sofort ungültig."""
device, raw_token = await kiosk_service.rotate_token(device_id, current_user.company_id, db)
await db.commit()
return KioskDeviceCreated(
**KioskDeviceOut.model_validate(device).model_dump(),
token=raw_token,
)
@router.delete("/devices/{device_id}", status_code=204)
async def delete_device(
device_id: UUID,
current_user: User = require_role(*_admin_roles),
db: AsyncSession = Depends(get_db),
):
await kiosk_service.delete_device(device_id, current_user.company_id, db)
await db.commit()
# ── Kiosk-Auth (Gerät authentifiziert sich per Token) ─────────────────────────
@router.get("/me", response_model=KioskDeviceOut)
async def kiosk_me(
x_kiosk_token: str = Header(..., alias="X-Kiosk-Token", min_length=32, max_length=128),
db: AsyncSession = Depends(get_db),
):
"""Kiosk-Gerät prüft seine eigene Identität / aktualisiert last_seen_at."""
device = await kiosk_service.authenticate_device(x_kiosk_token, db)
await db.commit()
return KioskDeviceOut.model_validate(device)