fix: Trusted Device bei MFA zuverlaessig speichern und pruefen

- Dropdown fuer die Merk-Dauer aktiviert jetzt die Checkbox mit; bisher
  wurde die Auswahl ohne gesetzten Haken verworfen und trotzdem nach
  jedem Login ein Code verlangt.
- Abgelaufene oder defekte Trusted-Device-Eintraege fuehrten beim Login
  zu einem NameError (users_data war nicht geladen); Bereinigung neu
  geschrieben.
- Cookie erhaelt das Secure-Flag, wenn die Verbindung HTTPS ist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik Thiele
2026-09-07 10:46:09 +02:00
parent ac343cef67
commit c56006c18d
2 changed files with 28 additions and 25 deletions

32
app.py
View File

@@ -51,7 +51,7 @@ CONFIG_FILE = os.path.join(BASE_DIR, "config.json") # Persistente Konfiguration
USERS_FILE = os.path.join(BASE_DIR, "users.json") # User-Datenbank USERS_FILE = os.path.join(BASE_DIR, "users.json") # User-Datenbank
HISTORY_FILE = os.path.join(BASE_DIR, "history.json") # Aktivitätsverlauf HISTORY_FILE = os.path.join(BASE_DIR, "history.json") # Aktivitätsverlauf
APP_VERSION = "6.2.3" APP_VERSION = "6.2.4"
UPLOAD_EXTENSIONS = {".jpg", ".jpeg", ".png", ".mp4"} UPLOAD_EXTENSIONS = {".jpg", ".jpeg", ".png", ".mp4"}
app = Flask(__name__) app = Flask(__name__)
@@ -731,29 +731,29 @@ def login():
trust_token = request.cookies.get("mfa_trust") trust_token = request.cookies.get("mfa_trust")
if trust_token: if trust_token:
now = datetime.now() now = datetime.now()
# Abgelaufene Einträge bereinigen treffer = None
active = [d for d in user.trusted_devices aktiv = []
if d.get("token") != trust_token or
datetime.fromisoformat(d["expires"]) > now]
changed = len(active) != len(user.trusted_devices)
for d in user.trusted_devices: for d in user.trusted_devices:
if d.get("token") == trust_token:
try: try:
expires = datetime.fromisoformat(d["expires"]) expires = datetime.fromisoformat(d["expires"])
if expires > now: except (ValueError, KeyError, TypeError):
continue # kaputter Eintrag fliegt raus
if expires <= now:
continue # abgelaufener Eintrag fliegt raus
aktiv.append(d)
if d.get("token") == trust_token:
treffer = d
if len(aktiv) != len(user.trusted_devices):
users_data = load_users()
users_data.setdefault(email, {})["trusted_devices"] = aktiv
save_users(users_data)
if treffer:
# Trusted Device gültig → MFA überspringen # Trusted Device gültig → MFA überspringen
login_user(user) login_user(user)
add_history_entry("user_login", f"User '{email}' angemeldet (Trusted Device)", email) add_history_entry("user_login", f"User '{email}' angemeldet (Trusted Device)", email)
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):
pass
if changed:
users_data[email]["trusted_devices"] = active
save_users(users_data)
session["mfa_user"] = email session["mfa_user"] = email
session["mfa_password_ok"] = True session["mfa_password_ok"] = True
return redirect("/mfa/verify") return redirect("/mfa/verify")
@@ -828,7 +828,7 @@ def mfa_verify():
resp.set_cookie("mfa_trust", token, max_age=days * 86400, resp.set_cookie("mfa_trust", token, max_age=days * 86400,
path="/", path="/",
httponly=True, samesite="Lax", httponly=True, samesite="Lax",
secure=False) secure=request.is_secure)
return resp return resp
except (ValueError, TypeError): except (ValueError, TypeError):
pass pass

View File

@@ -46,7 +46,7 @@
<input class="form-check-input" type="checkbox" id="remember" name="remember" value="7"> <input class="form-check-input" type="checkbox" id="remember" name="remember" value="7">
<label class="form-check-label" for="remember"> <label class="form-check-label" for="remember">
Gerät für Gerät für
<select class="form-select form-select-sm d-inline-block w-auto" id="remember_days" onchange="document.getElementById('remember').value=this.value"> <select class="form-select form-select-sm d-inline-block w-auto" id="remember_days">
<option value="1">1 Tag</option> <option value="1">1 Tag</option>
<option value="3">3 Tage</option> <option value="3">3 Tage</option>
<option value="7" selected>7 Tage</option> <option value="7" selected>7 Tage</option>
@@ -61,10 +61,13 @@
var cb = document.getElementById('remember'); var cb = document.getElementById('remember');
var sel = document.getElementById('remember_days'); var sel = document.getElementById('remember_days');
cb.addEventListener('change', function() { cb.addEventListener('change', function() {
if (this.checked) this.value = sel.value; this.value = sel.value;
}); });
// Wer im Dropdown eine Dauer waehlt, will das Geraet merken:
// Checkbox automatisch mit aktivieren.
sel.addEventListener('change', function() { sel.addEventListener('change', function() {
if (cb.checked) cb.value = this.value; cb.checked = true;
cb.value = this.value;
}); });
})(); })();
</script> </script>