Rollenbasiertes, langlebiges Token neben dem normalen JWT-Login-Flow - für automatisierte Zugriffe/Skripte. Admin wählt bei Erzeugung eine oder mehrere Rollen, Klartext wird einmalig gezeigt (wie ein Passwort), Token ist jederzeit deaktivierbar/reaktivierbar. Architektur: jedes Token bekommt einen Begleit-"Dienstkonto"-Benutzer (Benutzer.ist_dienstkonto=True, kein echter Login), der die Rollen über die bestehende benutzer_rolle-Tabelle trägt - dadurch funktionieren require_roles()/require_permission() und alle Audit-FKs unverändert, keine bestehende Endpunkt-Dependency musste angepasst werden. get_current_user() erkennt ein Token am Präfix mabea_pat_ und löst es über den Hash (SHA-256, kein bcrypt - Token ist bereits hochentropisch) auf den Begleit-Benutzer auf. Dienstkonten sind aus GET /benutzer ausgeblendet, damit sie keine Auswahl-Dropdowns verunreinigen. Neuer Admin-Tab "API-Tokens" (Gruppe System, nur Administration). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VDMvneg6wKfXdXvTYNd3dN
30 lines
1.5 KiB
Python
30 lines
1.5 KiB
Python
import uuid
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import Boolean, ForeignKey, String
|
|
from sqlalchemy.dialects.postgresql import TIMESTAMP, UUID
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from app.db.base import Base
|
|
|
|
|
|
class ApiToken(Base):
|
|
"""FOUND-016: langlebiges Token für Entwicklungs-/Tooling-Zugriff auf die
|
|
API (kein Nutzer-Login-Flow) - `benutzer_id` zeigt auf einen begleitenden
|
|
Dienstkonto-Benutzer (`Benutzer.ist_dienstkonto=True`), der die dem Token
|
|
zugewiesenen Rollen trägt. Nur `token_hash` wird gespeichert (SHA-256,
|
|
kein bcrypt nötig - hochentropisches Zufallstoken statt Passwort mit
|
|
Wörterbuch-/Brute-Force-Risiko, siehe app/core/security.py), der
|
|
Klartext wird ausschließlich einmalig bei der Erzeugung zurückgegeben."""
|
|
|
|
__tablename__ = "api_token"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
name: Mapped[str] = mapped_column(String, nullable=False)
|
|
token_hash: Mapped[str] = mapped_column(String, unique=True, nullable=False)
|
|
benutzer_id: Mapped[int] = mapped_column(ForeignKey("benutzer.id"), nullable=False, unique=True)
|
|
aktiv: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
|
erstellt_am: Mapped[datetime] = mapped_column(TIMESTAMP(timezone=True), nullable=False)
|
|
erstellt_von_benutzer_id: Mapped[int] = mapped_column(ForeignKey("benutzer.id"), nullable=False)
|
|
letzte_verwendung_am: Mapped[datetime | None] = mapped_column(TIMESTAMP(timezone=True))
|