feat: Eine Geraetemarke kann jetzt auch als Anmeldung fuer eine App gelten
Die API kannte zwei Zustaende: angemeldet per Sitzungs-Cookie, oder eine enge Kurzbefehl-Marke, die nur erfassen darf. Eine native App passt in keinen davon - sie kann kein Cookie-Formular ausfuellen und soll das Passwort nicht auf dem Geraet halten. Neu ist die Reichweite "app": Sie gilt im Header X-Certain-Token wie eine Anmeldung, laeuft nicht nach 30 Tagen ab und laesst sich einzeln widerrufen. Die enge Marke bleibt eng - das ist der Punkt, an dem die Tests am genauesten hinsehen. Und eine App-Marke darf keine weitere ausstellen: Sonst stellte sich ein Dieb Ersatzschluessel aus und das Widerrufen liefe ins Leere. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
15b863ff5e
commit
8a7545bf5b
+57
-1
@@ -1,9 +1,15 @@
|
||||
"""Gemeinsame FastAPI-Abhängigkeiten: aktueller Benutzer (Cookie), Admin-Pflicht.
|
||||
"""Gemeinsame FastAPI-Abhängigkeiten: aktueller Benutzer, Admin-Pflicht.
|
||||
|
||||
Die Anmeldung läuft über eine server-seitige Sitzung im Cookie (siehe
|
||||
``services/sessions``). ``get_current_user`` liest das Cookie, prüft die Sitzung
|
||||
und liefert den Benutzer – oder 401.
|
||||
|
||||
Zweiter Weg für die native App: eine Gerätemarke der Reichweite ``app`` im
|
||||
Header ``X-Certain-Token``. Eine App kann kein Cookie-Formular ausfüllen und
|
||||
soll nicht das Passwort auf dem Gerät halten. Die enge ``quick_add``-Marke
|
||||
bleibt davon unberührt – sie gilt weiterhin nur dort, wo
|
||||
``require_quick_token`` sie ausdrücklich verlangt.
|
||||
|
||||
``enforce_account_state`` hängt am zentralen ``/api``-Router und sperrt Benutzer
|
||||
aus, die erst noch etwas erledigen müssen (Passwort wechseln, zweiten Faktor
|
||||
einrichten). Bisher wurde das nur im Frontend umgeleitet – wer die API direkt
|
||||
@@ -54,7 +60,57 @@ def _session_user(request: Request, db: Session) -> User | None:
|
||||
return request.state.session_user
|
||||
token = request.cookies.get(get_settings().cookie_name, "")
|
||||
user = sessions.validate(db, token)
|
||||
herkunft = "cookie"
|
||||
if user is None:
|
||||
# Die native App meldet sich mit ihrer Marke statt mit einem Cookie.
|
||||
# Hier und nicht erst in ``get_current_user``, damit auch
|
||||
# ``enforce_account_state`` sie sieht – sonst käme eine App an der
|
||||
# Pflicht zum Passwortwechsel vorbei.
|
||||
user = _app_token_user(request, db)
|
||||
herkunft = "marke"
|
||||
request.state.session_user = user
|
||||
request.state.auth_via = herkunft if user is not None else None
|
||||
return user
|
||||
|
||||
|
||||
def _app_token_user(request: Request, db: Session) -> User | None:
|
||||
"""Benutzer hinter einer App-Marke im Header ``X-Certain-Token``.
|
||||
|
||||
Nur die Reichweite ``app`` zählt hier. Eine ``quick_add``-Marke darf auf
|
||||
diesem Weg NICHT zur Anmeldung werden – sonst wäre die bewusst enge Marke
|
||||
still zu einem Vollzugang geworden.
|
||||
"""
|
||||
roh = (request.headers.get("X-Certain-Token") or "").strip()
|
||||
if not roh:
|
||||
return None
|
||||
marke = (
|
||||
db.query(ApiToken)
|
||||
.filter(ApiToken.token_hash == hash_session_token(roh),
|
||||
ApiToken.scope == TokenScope.app)
|
||||
.first()
|
||||
)
|
||||
if marke is None:
|
||||
return None
|
||||
besitzer = db.get(User, marke.owner_id)
|
||||
if besitzer is None or not besitzer.is_active:
|
||||
return None
|
||||
marke.last_used_at = datetime.now(timezone.utc)
|
||||
db.commit()
|
||||
return besitzer
|
||||
|
||||
|
||||
def require_cookie_session(request: Request, db: Session = Depends(get_db)) -> User:
|
||||
"""Wie ``get_current_user``, aber NUR mit echter Anmeldung.
|
||||
|
||||
Für Schritte, die eine Marke nicht auslösen darf – allen voran das
|
||||
Ausstellen weiterer Marken.
|
||||
"""
|
||||
user = get_current_user(request, db)
|
||||
if getattr(request.state, "auth_via", None) != "cookie":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Dafür ist eine richtige Anmeldung nötig, keine Gerätemarke",
|
||||
)
|
||||
return user
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user