"""Anmeldung, Passwortrichtlinie und die serverseitige Sperre offener Pflichten.""" from fastapi.testclient import TestClient from conftest import ADMIN_PASSWORD, login_ready, new_password, new_user def _client() -> TestClient: from app.main import app return TestClient(app) def test_login_wrong_password(client): r = client.post("/api/auth/login", data={"username": "admin", "password": "falsch"}) assert r.status_code == 401 def test_login_unknown_user(client): r = client.post("/api/auth/login", data={"username": "gibtsnicht", "password": "egal"}) assert r.status_code == 401 def test_login_returns_status_ok(client): r = client.post("/api/auth/login", data={"username": "admin", "password": ADMIN_PASSWORD}) assert r.status_code == 200 body = r.json() assert body["status"] == "ok" assert body["user"]["username"] == "admin" def test_inactive_account_cannot_log_in(admin): username, password = new_user(admin) liste = admin.get("/api/users").json() u = next(x for x in liste if x["username"] == username) assert admin.patch(f"/api/users/{u['id']}", json={"is_active": False}).status_code == 200 r = _client().post("/api/auth/login", data={"username": username, "password": password}) assert r.status_code == 403 def test_fresh_user_is_blocked_until_password_changed(admin): """Vom Admin angelegte Konten kommen nur an die Wechsel-Endpunkte.""" username, password = new_user(admin) c = _client() assert c.post("/api/auth/login", data={"username": username, "password": password}).status_code == 200 # Eigene Daten und der Wechsel selbst bleiben erreichbar … me = c.get("/api/auth/me").json() assert me["must_change_password"] is True assert me["must_change_password_reason"] == "temp" assert c.get("/api/policy").status_code == 200 # … alles Fachliche nicht. r = c.get("/api/accounts") assert r.status_code == 403 assert r.json()["detail"] == "password_change_required" neu = new_password() assert c.post( "/api/auth/change-password", json={"current_password": password, "new_password": neu}, ).status_code == 200 assert c.get("/api/accounts").status_code == 200 def test_change_password_checks_current(admin): username, password = new_user(admin) c, _gueltig = login_ready(username, password) r = c.post( "/api/auth/change-password", json={"current_password": "stimmt-nicht", "new_password": new_password()}, ) assert r.status_code == 400 def test_change_password_rejects_same_password(admin): username, password = new_user(admin) c, gueltig = login_ready(username, password) r = c.post( "/api/auth/change-password", json={"current_password": gueltig, "new_password": gueltig}, ) assert r.status_code == 400 def test_change_password_enforces_policy(admin): username, password = new_user(admin) c, gueltig = login_ready(username, password) # Zu kurz, keine Grossbuchstaben, keine Ziffer. r = c.post( "/api/auth/change-password", json={"current_password": gueltig, "new_password": "kurz"}, ) assert r.status_code == 400 assert "Richtlinie" in r.json()["detail"] def test_create_user_enforces_policy(admin): r = admin.post("/api/users", json={"username": "schwach_x", "password": "abc", "role": "user"}) assert r.status_code == 400 def test_policy_is_admin_only(admin): other, _ = login_ready(*new_user(admin)) assert other.get("/api/policy").status_code == 200 assert other.put("/api/policy", json={"min_length": 8}).status_code == 403 def test_tightened_policy_forces_change_on_next_login(policy_admin): """Kernpunkt: der Hash gibt nichts her, aber beim Anmelden liegt das Passwort im Klartext vor – dort wird die Richtlinie geprüft.""" username, password = new_user(policy_admin) c, gueltig = login_ready(username, password) assert c.get("/api/auth/me").json()["must_change_password"] is False # Richtlinie verschärfen: das bisherige Passwort ist nun zu kurz. assert policy_admin.put("/api/policy", json={ "min_length": 40, "require_upper": True, "require_lower": True, "require_digit": True, "require_special": False, "mfa_required": False, }).status_code == 200 zweiter = _client() assert zweiter.post( "/api/auth/login", data={"username": username, "password": gueltig} ).status_code == 200 me = zweiter.get("/api/auth/me").json() assert me["must_change_password"] is True assert me["must_change_password_reason"] == "policy" assert zweiter.get("/api/accounts").status_code == 403 def test_policy_min_length_unter_untergrenze_wird_abgelehnt(policy_admin): vorher = policy_admin.get("/api/policy").json() assert policy_admin.put("/api/policy", json={"min_length": 4}).status_code == 422 # Die gespeicherte Richtlinie bleibt unverändert. assert policy_admin.get("/api/policy").json() == vorher def test_change_password_revokes_other_sessions(admin): username, password = new_user(admin) erste, gueltig = login_ready(username, password) zweite = _client() assert zweite.post( "/api/auth/login", data={"username": username, "password": gueltig} ).status_code == 200 neu = new_password() assert erste.post( "/api/auth/change-password", json={"current_password": gueltig, "new_password": neu}, ).status_code == 200 # Die andere Sitzung ist beendet, die eigene läuft weiter. assert zweite.get("/api/auth/me").status_code == 401 assert erste.get("/api/auth/me").status_code == 200 def test_logout_ends_session(admin): c, _ = login_ready(*new_user(admin)) assert c.post("/api/auth/logout").status_code == 204 assert c.get("/api/auth/me").status_code == 401