fix(auth): Sitzungen ueber die Benutzer-ID statt ueber den Namen aufloesen
Das Sitzungs-Token trug den Benutzernamen als Subjekt, und get_current_user suchte danach. Eine Umbenennung machte damit jedes Token auf jedem anderen Geraet stillschweigend ungueltig — der Nutzer flog ohne Hinweis raus. Tokens tragen jetzt zusaetzlich uid, die unveraenderliche Zeilen-ID, und die Aufloesung geht darueber. sub bleibt als Anzeigename erhalten. Tokens ohne uid werden weiter ueber den Namen aufgeloest, damit vor der Aenderung ausgestellte Sitzungen gueltig bleiben — mit "Angemeldet bleiben" laufen die 180 Tage. Fuer SSO war die Zuordnung schon vorher richtig: OIDCIdentity haengt an (provider_key, sub) des Providers, und bei bekanntem sub wird der lokale Benutzername nicht angefasst. Ein umbenannter Nutzer in Authentik landet also unveraendert auf demselben Konto. Vier Tests halten beide Richtungen fest: Umbenennung beim Provider, lokale Umbenennung mit weiterlaufender Sitzung, lokale Umbenennung mit erhaltener SSO-Verknuepfung, und die Kompatibilitaet alter Tokens. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
cc4daf4a84
commit
725b61c8cb
+21
-2
@@ -43,6 +43,18 @@ def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -
|
||||
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||
|
||||
|
||||
def create_user_token(user: "models.User", expires_delta: Optional[timedelta] = None) -> str:
|
||||
"""Session token for a user.
|
||||
|
||||
``uid`` is the authoritative subject: the immutable row id. ``sub`` keeps
|
||||
the username for readability and for tokens issued before ``uid`` existed,
|
||||
but it is not what identifies the account — otherwise renaming a user would
|
||||
silently invalidate every session on their other devices, and an identity
|
||||
provider that changes a name would break the mapping.
|
||||
"""
|
||||
return create_access_token({"sub": user.username, "uid": user.id}, expires_delta)
|
||||
|
||||
|
||||
def get_current_user(
|
||||
token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)
|
||||
) -> models.User:
|
||||
@@ -57,12 +69,19 @@ def get_current_user(
|
||||
# flow token); only a real access token may authenticate a request.
|
||||
if payload.get("typ") not in (None, "access"):
|
||||
raise exc
|
||||
uid = payload.get("uid")
|
||||
username: str = payload.get("sub")
|
||||
if not username:
|
||||
if uid is None and not username:
|
||||
raise exc
|
||||
except JWTError:
|
||||
raise exc
|
||||
user = db.query(models.User).filter(models.User.username == username).first()
|
||||
|
||||
if uid is not None:
|
||||
user = db.query(models.User).filter(models.User.id == uid).first()
|
||||
else:
|
||||
# Legacy token issued before uid existed. "Remember me" runs for 180
|
||||
# days, so these stay in circulation for a while.
|
||||
user = db.query(models.User).filter(models.User.username == username).first()
|
||||
if not user:
|
||||
raise exc
|
||||
return user
|
||||
|
||||
Reference in New Issue
Block a user