dd3e069466
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>
100 lines
3.6 KiB
Python
100 lines
3.6 KiB
Python
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)
|