Compare commits

...

2 Commits

Author SHA1 Message Date
Erik Thiele
106ec2d897 Fix: Trusted-Device-Cookie überlebt jetzt den Logout
- resp.delete_cookie('mfa_trust') aus /logout entfernt
- Cookie wird nur noch bei MFA-Deaktivierung/Passwort-Reset gelöscht
- Debug-Prints entfernt, users.json aufgeräumt
2026-07-10 16:17:08 +02:00
Erik Thiele
b9e7b2e074 Fix: Trusted-Device-Cookie wurde nicht korrekt gespeichert
- Cookie path explizit auf '/' gesetzt
- login_user() vor Cookie-Setzung aufgerufen (konsistenter Zustand)
- Kein Überschreiben der Response mehr bei must_change_password
2026-07-10 15:44:00 +02:00
2 changed files with 31 additions and 30 deletions

27
app.py
View File

@@ -517,9 +517,6 @@ def login():
# Trusted Device prüfen (Cookie überspringt MFA) # Trusted Device prüfen (Cookie überspringt MFA)
trust_token = request.cookies.get("mfa_trust") trust_token = request.cookies.get("mfa_trust")
if trust_token: if trust_token:
users_data = load_users()
user_data = users_data.get(email, {})
trusted = user_data.get("trusted_devices", [])
now = datetime.now() now = datetime.now()
# Abgelaufene Einträge bereinigen # Abgelaufene Einträge bereinigen
active = [d for d in trusted active = [d for d in trusted
@@ -537,6 +534,8 @@ def login():
if user.must_change_password: if user.must_change_password:
return redirect("/change-password") return redirect("/change-password")
return redirect("/admin") return redirect("/admin")
else:
pass
except (ValueError, KeyError): except (ValueError, KeyError):
pass pass
if changed: if changed:
@@ -566,9 +565,7 @@ def logout():
email = current_user.email if current_user.is_authenticated else "unknown" email = current_user.email if current_user.is_authenticated else "unknown"
logout_user() logout_user()
add_history_entry("user_logout", f"User '{email}' abgemeldet", email) add_history_entry("user_logout", f"User '{email}' abgemeldet", email)
resp = redirect("/login") return redirect("/login")
resp.delete_cookie("mfa_trust")
return resp
# ------------------------------------------------- # -------------------------------------------------
@@ -597,6 +594,8 @@ def mfa_verify():
totp = pyotp.TOTP(user.mfa_secret) totp = pyotp.TOTP(user.mfa_secret)
if code and totp.verify(code): if code and totp.verify(code):
login_user(user)
session.pop("mfa_user", None)
# Trusted Device speichern wenn gewünscht # Trusted Device speichern wenn gewünscht
remember_days = request.form.get("remember") remember_days = request.form.get("remember")
if remember_days: if remember_days:
@@ -604,28 +603,22 @@ def mfa_verify():
days = int(remember_days) days = int(remember_days)
if 1 <= days <= 365: if 1 <= days <= 365:
token = secrets.token_urlsafe(32) token = secrets.token_urlsafe(32)
expires = datetime.now().isoformat() exp_date = datetime.now() + timedelta(days=days)
exp_date = datetime.now()
exp_date = exp_date + timedelta(days=days)
expires = exp_date.isoformat() expires = exp_date.isoformat()
users_data = load_users() users_data = load_users()
trusted = users_data.setdefault(email, {}).setdefault("trusted_devices", []) trusted = users_data.setdefault(email, {}).setdefault("trusted_devices", [])
trusted.append({"token": token, "expires": expires}) trusted.append({"token": token, "expires": expires})
save_users(users_data) save_users(users_data)
resp = redirect("/admin") add_history_entry("user_login", f"User '{email}' angemeldet (mit MFA, Gerät für {days} Tage gespeichert)", email)
target = "/change-password" if user.must_change_password else "/admin"
resp = redirect(target)
resp.set_cookie("mfa_trust", token, max_age=days * 86400, resp.set_cookie("mfa_trust", token, max_age=days * 86400,
path="/",
httponly=True, samesite="Lax", httponly=True, samesite="Lax",
secure=False) secure=False)
login_user(user)
session.pop("mfa_user", None)
add_history_entry("user_login", f"User '{email}' angemeldet (mit MFA, Gerät für {days} Tage gespeichert)", email)
if user.must_change_password:
resp = redirect("/change-password")
return resp return resp
except (ValueError, TypeError): except (ValueError, TypeError):
pass pass
login_user(user)
session.pop("mfa_user", None)
add_history_entry("user_login", f"User '{email}' angemeldet (mit MFA)", email) add_history_entry("user_login", f"User '{email}' angemeldet (mit MFA)", email)
if user.must_change_password: if user.must_change_password:
return redirect("/change-password") return redirect("/change-password")

View File

@@ -15,10 +15,11 @@
"FC204A", "FC204A",
"1D09D7" "1D09D7"
], ],
"mfa_secret": "VOLBXT7HYK6BXBD32EGW7CMFLVD53QON" "mfa_secret": "VOLBXT7HYK6BXBD32EGW7CMFLVD53QON",
"trusted_devices": []
}, },
"erik.thiele@cancom.de": { "erik.thiele@cancom.de": {
"password_hash": "scrypt:32768:8:1$gyBs4dA7DhH9wA0u$c80f25262388095545733c95dd4e569eee610fa849a11dd28d4f504d09e23d3768adad26d4ed671975bd3dcbae5dc43bd982f4d475e6bc9d2ede088cb0b6c025", "password_hash": "scrypt:32768:8:1$D8XYprsEYMO5EPrG$2829b18f77bbd7d9f0cbb945b0bdbc32e16d7889f8711881f82dd35accd26ddb7c31d3fa85e3b89d19d6d1f0b92d8d18765e1c82044188cab8fd0db2d35a0d15",
"role": "admin", "role": "admin",
"sites": [], "sites": [],
"must_change_password": false, "must_change_password": false,
@@ -26,17 +27,23 @@
"last_name": "Thiele", "last_name": "Thiele",
"department": "CSO", "department": "CSO",
"notes": "Standort Stuttgart", "notes": "Standort Stuttgart",
"mfa_secret": "CHKS2QZOJ45TSCLUN7FWO3MLTMRSS6Q3", "mfa_secret": "LR5DYI3SQPMW5XWKO33HPETHY5TPOWRG",
"mfa_enabled": true, "mfa_enabled": true,
"mfa_recovery_codes": [ "mfa_recovery_codes": [
"1FB378", "7BBD93",
"AAD593", "EB1D3B",
"B4BCB9", "BEE291",
"9CB5C3", "D471D5",
"22D614", "D43C00",
"26F3B7", "75DBAC",
"47FB5C", "8B00CB",
"50BB59" "7AF72D"
],
"trusted_devices": [
{
"token": "5kMgj4JPxd2IHuzV5MagAAcYyi_DBImyGimBSvZUNVg",
"expires": "2026-07-24T15:40:28.955130"
}
] ]
}, },
"test1@test.de": { "test1@test.de": {
@@ -51,6 +58,7 @@
"first_name": "test", "first_name": "test",
"last_name": "tester", "last_name": "tester",
"department": "addff", "department": "addff",
"notes": "asdasdasd" "notes": "asdasdasd",
"trusted_devices": []
} }
} }