feat(kiosk): QR-Login-Token-Endpunkt für Employee-App
POST /kiosk/my-qr-token (normale JWT-Auth) – Mitarbeiter fordert einen 5min gültigen, einmalig einlösbaren QR-Token an, den die Employee-App als QR-Code zeigt. Kiosk scannt ihn (POST /kiosk/auth/qr, bereits vorhanden) und meldet den Mitarbeiter an. Backend-Funktion (generate_qr_token) existierte schon, war aber nie über einen Endpunkt erreichbar. Test für den kompletten Ablauf inkl. Einmaligkeit (zweiter Versuch mit selbem Token → 401). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dzdm3EWeNdYFUuXFRJryA1
This commit is contained in:
1 parent
62a19d17e8
commit
71db3bdd57
3 files changed
+47
-2
No files matched your search
@@ -6,7 +6,7 @@ from fastapi import APIRouter, Body, Depends, HTTPException, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.dependencies import require_role
|
||||
from app.core.dependencies import CurrentUser, require_role
|
||||
from app.core.kiosk_security import verify_kiosk_request
|
||||
from app.models.company import Company
|
||||
from app.models.kiosk_device import KioskDevice, KioskDeviceStatus
|
||||
@@ -25,6 +25,7 @@ from app.schemas.kiosk_auth import (
|
||||
KioskNfcLoginRequest,
|
||||
KioskPinLoginRequest,
|
||||
KioskQrLoginRequest,
|
||||
KioskQrTokenResponse,
|
||||
KioskSessionResponse,
|
||||
KioskStampRequest,
|
||||
KioskStampResponse,
|
||||
@@ -35,7 +36,7 @@ from app.schemas.kiosk_auth import (
|
||||
)
|
||||
from app.schemas.time_entry import StampInRequest
|
||||
from app.models.time_entry import EntrySource
|
||||
from app.services.kiosk_auth_service import _display_name, kiosk_auth_service
|
||||
from app.services.kiosk_auth_service import QR_TOKEN_TTL, _display_name, kiosk_auth_service
|
||||
from app.services.kiosk_service import kiosk_service
|
||||
from app.services.kiosk_session_service import SESSION_TTL_SECONDS, kiosk_session_service
|
||||
from app.services.time_service import time_service
|
||||
@@ -45,6 +46,17 @@ router = APIRouter(prefix="/kiosk", tags=["Kiosk"])
|
||||
_admin_roles = (UserRole.COMPANY_ADMIN, UserRole.SUPER_ADMIN)
|
||||
|
||||
|
||||
# ── Mitarbeiter-QR-Login (normale JWT-Auth, für die Employee-App) ────────────
|
||||
|
||||
@router.post("/my-qr-token", response_model=KioskQrTokenResponse)
|
||||
async def get_my_qr_token(current_user: CurrentUser):
|
||||
"""Erzeugt einen 5 min gültigen, einmalig einlösbaren QR-Login-Token für
|
||||
den eingeloggten Mitarbeiter – die Employee-App zeigt ihn als QR-Code,
|
||||
der Kiosk scannt ihn (POST /kiosk/auth/qr) und meldet den Mitarbeiter an."""
|
||||
token = await kiosk_auth_service.generate_qr_token(current_user.id, current_user.company_id)
|
||||
return KioskQrTokenResponse(qr_token=token, expires_in_seconds=QR_TOKEN_TTL)
|
||||
|
||||
|
||||
# ── Geräteverwaltung (COMPANY_ADMIN) ──────────────────────────────────────────
|
||||
|
||||
@router.get("/devices", response_model=list[KioskDeviceOut])
|
||||
|
||||
@@ -61,3 +61,10 @@ class KioskStampStatusResponse(BaseModel):
|
||||
stamped_in: bool
|
||||
on_break: bool
|
||||
start_time: str | None = None
|
||||
|
||||
|
||||
class KioskQrTokenResponse(BaseModel):
|
||||
"""Für die Mitarbeiter-App: eigenen QR-Login-Token anfordern, der am
|
||||
Kiosk gescannt werden kann (5 min gültig, einmalig einlösbar)."""
|
||||
qr_token: str
|
||||
expires_in_seconds: int
|
||||
@@ -517,3 +517,29 @@ async def test_create_bearer_device_via_admin_api(client: AsyncClient, kiosk_adm
|
||||
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
|
||||
)
|
||||
assert hb.status_code == 200, hb.text
|
||||
|
||||
|
||||
async def test_my_qr_token_and_kiosk_login(client: AsyncClient, kiosk_admin_headers, approved_bearer_device):
|
||||
"""Mitarbeiter fordert eigenen QR-Token an, Kiosk scannt ihn → Session."""
|
||||
_, bearer_token = approved_bearer_device
|
||||
|
||||
r = await client.post("/api/v1/kiosk/my-qr-token", headers=kiosk_admin_headers)
|
||||
assert r.status_code == 200, r.text
|
||||
qr_token = r.json()["qr_token"]
|
||||
assert r.json()["expires_in_seconds"] > 0
|
||||
|
||||
login = await client.post(
|
||||
"/api/v1/kiosk/auth/qr",
|
||||
json={"qr_token": qr_token},
|
||||
headers={"Authorization": f"Bearer {bearer_token}", "Content-Type": "application/json"},
|
||||
)
|
||||
assert login.status_code == 200, login.text
|
||||
assert login.json()["auth_method"] == "qr"
|
||||
|
||||
# Einmalig: zweiter Versuch mit demselben Token scheitert.
|
||||
login2 = await client.post(
|
||||
"/api/v1/kiosk/auth/qr",
|
||||
json={"qr_token": qr_token},
|
||||
headers={"Authorization": f"Bearer {bearer_token}", "Content-Type": "application/json"},
|
||||
)
|
||||
assert login2.status_code == 401, login2.text
|
||||
Reference in new issue
Block a user