"""Sitzungs-Token: der Benutzer haengt an seiner ID, nicht an seinem Namen. Frueher stand der Benutzername im Token. Damit war der Name die Kennung der Sitzung – ein Umbenennen haette jede laufende Anmeldung ungueltig gemacht und war deshalb gar nicht erst vorgesehen. """ from datetime import datetime, timedelta, timezone import pytest from fastapi import HTTPException from jose import jwt from app.config import get_settings from app.deps import get_current_user from app.models import Role, User from app.routers.users import update_user from app.schemas import UserUpdate from app.security import create_access_token, hash_password @pytest.fixture def eva(db) -> User: nutzer = User(username="eva", password_hash=hash_password("geheim123"), role=Role.admin) db.add(nutzer) db.commit() db.refresh(nutzer) return nutzer def test_token_benennt_die_id_nicht_den_namen(eva): einstellungen = get_settings() inhalt = jwt.decode( create_access_token(user_id=eva.id, role=eva.role.value), einstellungen.jwt_secret, algorithms=[einstellungen.jwt_algorithm], ) assert inhalt["uid"] == eva.id assert inhalt["sub"] == str(eva.id) assert "eva" not in inhalt.values() def test_umbenennen_beendet_die_sitzung_nicht(db, eva): """Der eigentliche Gewinn: das Token gilt nach dem Umbenennen weiter.""" token = create_access_token(user_id=eva.id, role=eva.role.value) update_user(user_id=eva.id, payload=UserUpdate(username="eva.neu"), db=db) weiterhin = get_current_user(token=token, db=db) assert weiterhin.id == eva.id assert weiterhin.username == "eva.neu" def test_umbenennen_auf_vergebenen_namen_wird_abgelehnt(db, eva): db.add(User(username="otto", password_hash=hash_password("geheim123"), role=Role.user)) db.commit() with pytest.raises(HTTPException) as fehler: update_user(user_id=eva.id, payload=UserUpdate(username="otto"), db=db) assert fehler.value.status_code == 409 db.refresh(eva) assert eva.username == "eva" def test_gleicher_name_ist_kein_konflikt(db, eva): """Ein Speichern ohne Namensaenderung darf nicht an sich selbst scheitern.""" update_user(user_id=eva.id, payload=UserUpdate(username="eva"), db=db) db.refresh(eva) assert eva.username == "eva" def test_geloeschter_benutzer_hat_keine_gueltige_sitzung(db, eva): token = create_access_token(user_id=eva.id, role=eva.role.value) db.delete(eva) db.commit() with pytest.raises(HTTPException) as fehler: get_current_user(token=token, db=db) assert fehler.value.status_code == 401 def test_altes_token_mit_namen_gilt_weiter(db, eva): """Beim Update darf niemand aus der laufenden Sitzung fliegen. Token aus der Zeit vor der Umstellung tragen den Namen in ``sub`` und kein ``uid``; sie muessen bis zu ihrem Ablauf weiter funktionieren. """ einstellungen = get_settings() alt = jwt.encode( { "sub": "eva", "role": "admin", "exp": datetime.now(timezone.utc) + timedelta(hours=1), }, einstellungen.jwt_secret, algorithm=einstellungen.jwt_algorithm, ) assert get_current_user(token=alt, db=db).id == eva.id def test_benutzer_namens_42_verwirrt_die_aufloesung_nicht(db): """Ein rein numerischer Name darf nicht als ID missdeutet werden.""" einstellungen = get_settings() zahlname = User(username="42", password_hash=hash_password("geheim123"), role=Role.user) anderer = User(username="otto", password_hash=hash_password("geheim123"), role=Role.user) db.add_all([zahlname, anderer]) db.commit() # Altes Token fuer den Benutzer "42" – ohne uid, also ueber den Namen. alt = jwt.encode( {"sub": "42", "role": "user", "exp": datetime.now(timezone.utc) + timedelta(hours=1)}, einstellungen.jwt_secret, algorithm=einstellungen.jwt_algorithm, ) assert get_current_user(token=alt, db=db).id == zahlname.id