fix: user routes mit cleanen String-Templates — kein f-string/.format-Mix
This commit is contained in:
@@ -2352,23 +2352,24 @@ def submission_delete(sub_id: int):
|
|||||||
</html>"""
|
</html>"""
|
||||||
|
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════
|
||||||
# ═══════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════
|
||||||
# Nutzerprofil-System (Cross-Channel Identity)
|
# Nutzerprofil-System (Cross-Channel Identity)
|
||||||
# ═══════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
import hashlib, secrets
|
import hashlib as _hashlib
|
||||||
|
|
||||||
def _hash_password(password: str) -> str:
|
def _hash_password(password: str) -> str:
|
||||||
"""SHA-256 mit Salt."""
|
"""SHA-256 mit Salt."""
|
||||||
salt = secrets.token_hex(16)
|
salt = secrets.token_hex(16)
|
||||||
h = hashlib.sha256((salt + password).encode()).hexdigest()
|
h = _hashlib.sha256((salt + password).encode()).hexdigest()
|
||||||
return f"{salt}${h}"
|
return f"{salt}${h}"
|
||||||
|
|
||||||
def _check_password(password: str, stored: str) -> bool:
|
def _check_password(password: str, stored: str) -> bool:
|
||||||
"""Vergleicht Passwort mit gespeichertem Hash."""
|
"""Vergleicht Passwort mit gespeichertem Hash."""
|
||||||
try:
|
try:
|
||||||
salt, h = stored.split("$", 1)
|
salt, h = stored.split("$", 1)
|
||||||
return hashlib.sha256((salt + password).encode()).hexdigest() == h
|
return _hashlib.sha256((salt + password).encode()).hexdigest() == h
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -2394,6 +2395,91 @@ def _get_user_from_session(db) -> dict | None:
|
|||||||
).fetchone()
|
).fetchone()
|
||||||
return dict(row) if row else None
|
return dict(row) if row else None
|
||||||
|
|
||||||
|
# ── HTML-Templates (plain strings, no f-string needed) ──────────────
|
||||||
|
|
||||||
|
_REGISTER_TPL = """<!DOCTYPE html>
|
||||||
|
<html lang="de"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Registrieren — BSN Community</title>
|
||||||
|
<style>
|
||||||
|
body{font-family:-apple-system,sans-serif;background:#1a1a2e;color:#eee;display:flex;justify-content:center;align-items:center;min-height:100vh;margin:0}
|
||||||
|
.card{background:#16213e;border-radius:16px;padding:2rem;width:100%;max-width:400px;box-shadow:0 8px 32px #00000040}
|
||||||
|
h1{color:#20228a;margin:0 0 1rem;font-size:1.4rem;text-align:center}
|
||||||
|
input{width:100%;padding:0.8rem;margin:0.4rem 0;border-radius:8px;border:1px solid #333;background:#0f3460;color:#fff;box-sizing:border-box;font-size:1rem}
|
||||||
|
.btn{width:100%;padding:0.8rem;background:#20228a;color:#fff;border:none;border-radius:8px;font-size:1rem;cursor:pointer;margin-top:0.5rem}
|
||||||
|
.btn:hover{background:#2a2caa}
|
||||||
|
.error{color:#ff453a;font-size:0.85rem;margin:0.5rem 0}
|
||||||
|
.link{text-align:center;margin-top:1rem;font-size:0.85rem}
|
||||||
|
.link a{color:#5e5ce6}
|
||||||
|
</style></head><body>
|
||||||
|
<div class="card">
|
||||||
|
<h1>📋 Registrieren</h1>
|
||||||
|
{error_html}
|
||||||
|
<form method="post">
|
||||||
|
<input name="phone" placeholder="Telefon (z.B. +49152...)" value="{phone_val}" required>
|
||||||
|
<input name="name" placeholder="Dein Name" required>
|
||||||
|
<input name="password" type="password" placeholder="Passwort (min. 4 Zeichen)" required>
|
||||||
|
<button class="btn" type="submit">Registrieren</button>
|
||||||
|
</form>
|
||||||
|
<div class="link">Bereits registriert? <a href="/login">Einloggen</a></div>
|
||||||
|
</div></body></html>"""
|
||||||
|
|
||||||
|
_LOGIN_TPL = """<!DOCTYPE html>
|
||||||
|
<html lang="de"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Login — BSN Community</title>
|
||||||
|
<style>
|
||||||
|
body{font-family:-apple-system,sans-serif;background:#1a1a2e;color:#eee;display:flex;justify-content:center;align-items:center;min-height:100vh;margin:0}
|
||||||
|
.card{background:#16213e;border-radius:16px;padding:2rem;width:100%;max-width:400px;box-shadow:0 8px 32px #00000040}
|
||||||
|
h1{color:#20228a;margin:0 0 1rem;font-size:1.4rem;text-align:center}
|
||||||
|
input{width:100%;padding:0.8rem;margin:0.4rem 0;border-radius:8px;border:1px solid #333;background:#0f3460;color:#fff;box-sizing:border-box;font-size:1rem}
|
||||||
|
.btn{width:100%;padding:0.8rem;background:#20228a;color:#fff;border:none;border-radius:8px;font-size:1rem;cursor:pointer;margin-top:0.5rem}
|
||||||
|
.btn:hover{background:#2a2caa}
|
||||||
|
.error{color:#ff453a;font-size:0.85rem;margin:0.5rem 0}
|
||||||
|
.link{text-align:center;margin-top:1rem;font-size:0.85rem}
|
||||||
|
.link a{color:#5e5ce6}
|
||||||
|
</style></head><body>
|
||||||
|
<div class="card">
|
||||||
|
<h1>🔐 Login</h1>
|
||||||
|
{error_html}
|
||||||
|
<form method="post">
|
||||||
|
<input name="phone" placeholder="Telefon" value="{phone_val}" required>
|
||||||
|
<input name="password" type="password" placeholder="Passwort" required>
|
||||||
|
<button class="btn" type="submit">Einloggen</button>
|
||||||
|
</form>
|
||||||
|
<div class="link">Neu hier? <a href="/register">Registrieren</a></div>
|
||||||
|
</div></body></html>"""
|
||||||
|
|
||||||
|
_PROFILE_TPL = """<!DOCTYPE html>
|
||||||
|
<html lang="de"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Profil — BSN Community</title>
|
||||||
|
<style>
|
||||||
|
body{font-family:-apple-system,sans-serif;background:#1a1a2e;color:#eee;display:flex;justify-content:center;align-items:center;min-height:100vh;margin:0}
|
||||||
|
.card{background:#16213e;border-radius:16px;padding:2rem;width:100%;max-width:400px;box-shadow:0 8px 32px #00000040}
|
||||||
|
h1{color:#20228a;margin:0 0 1rem;font-size:1.4rem}
|
||||||
|
.field{margin:0.8rem 0}
|
||||||
|
.label{color:#888;font-size:0.8rem;text-transform:uppercase}
|
||||||
|
.value{font-size:1.1rem}
|
||||||
|
input{width:100%;padding:0.6rem;margin:0.3rem 0;border-radius:6px;border:1px solid #333;background:#0f3460;color:#fff;box-sizing:border-box}
|
||||||
|
.btn{padding:0.6rem 1.2rem;background:#20228a;color:#fff;border:none;border-radius:6px;cursor:pointer}
|
||||||
|
.btn:hover{background:#2a2caa}
|
||||||
|
.msg{color:#30d158;margin:0.5rem 0}
|
||||||
|
.nav{margin-top:1rem;display:flex;gap:0.5rem}
|
||||||
|
</style></head><body>
|
||||||
|
<div class="card">
|
||||||
|
<h1>👤 Mein Profil</h1>
|
||||||
|
{msg_html}
|
||||||
|
<div class="field"><div class="label">Telefon</div><div class="value">{phone}</div></div>
|
||||||
|
<form method="post">
|
||||||
|
<div class="field"><div class="label">Name</div>
|
||||||
|
<input name="name" value="{name}" placeholder="Dein Name"></div>
|
||||||
|
<button class="btn" type="submit">Speichern</button>
|
||||||
|
</form>
|
||||||
|
<div class="nav">
|
||||||
|
<a href="/" style="color:#5e5ce6">← Startseite</a>
|
||||||
|
<a href="/logout" style="color:#ff453a">Abmelden</a>
|
||||||
|
</div>
|
||||||
|
</div></body></html>"""
|
||||||
|
|
||||||
|
|
||||||
@app.route("/register", methods=["GET", "POST"])
|
@app.route("/register", methods=["GET", "POST"])
|
||||||
def user_register():
|
def user_register():
|
||||||
db = get_db()
|
db = get_db()
|
||||||
@@ -2415,34 +2501,14 @@ def user_register():
|
|||||||
db.commit()
|
db.commit()
|
||||||
user_id = db.execute("SELECT last_insert_rowid()").fetchone()[0]
|
user_id = db.execute("SELECT last_insert_rowid()").fetchone()[0]
|
||||||
token = _create_session(db, user_id)
|
token = _create_session(db, user_id)
|
||||||
resp = make_response(redirect("/profile"))
|
from flask import make_response, redirect as _redir
|
||||||
|
resp = make_response(_redir("/profile"))
|
||||||
resp.set_cookie("bsn_session", token, max_age=30*86400, httponly=True)
|
resp.set_cookie("bsn_session", token, max_age=30*86400, httponly=True)
|
||||||
return resp
|
return resp
|
||||||
return f"""<!DOCTYPE html>
|
phone_val = request.form.get("phone", "")
|
||||||
<html lang="de"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
error_html = "<div class='error'>" + error + "</div>" if error else ""
|
||||||
<title>Registrieren — BSN Community</title>
|
return _REGISTER_TPL.replace("{phone_val}", phone_val).replace("{error_html}", error_html)
|
||||||
<style>
|
|
||||||
body{{font-family:-apple-system,sans-serif;background:#1a1a2e;color:#eee;display:flex;justify-content:center;align-items:center;min-height:100vh;margin:0}}
|
|
||||||
.card{{background:#16213e;border-radius:16px;padding:2rem;width:100%;max-width:400px;box-shadow:0 8px 32px #00000040}}
|
|
||||||
h1{{color:#20228a;margin:0 0 1rem;font-size:1.4rem;text-align:center}}
|
|
||||||
input{{width:100%;padding:0.8rem;margin:0.4rem 0;border-radius:8px;border:1px solid #333;background:#0f3460;color:#fff;box-sizing:border-box;font-size:1rem}}
|
|
||||||
.btn{{width:100%;padding:0.8rem;background:#20228a;color:#fff;border:none;border-radius:8px;font-size:1rem;cursor:pointer;margin-top:0.5rem}}
|
|
||||||
.btn:hover{{background:#2a2caa}}
|
|
||||||
.error{{color:#ff453a;font-size:0.85rem;margin:0.5rem 0}}
|
|
||||||
.link{{text-align:center;margin-top:1rem;font-size:0.85rem}}
|
|
||||||
.link a{{color:#5e5ce6}}
|
|
||||||
</style></head><body>
|
|
||||||
<div class="card">
|
|
||||||
<h1>📋 Registrieren</h1>
|
|
||||||
{"<div class='error'>{}</div>".format(error) if error else ""}
|
|
||||||
<form method="post">
|
|
||||||
<input name="phone" placeholder="Telefon (z.B. +49152...)" value="{{}}" required>
|
|
||||||
<input name="name" placeholder="Dein Name" required>
|
|
||||||
<input name="password" type="password" placeholder="Passwort (min. 4 Zeichen)" required>
|
|
||||||
<button class="btn" type="submit">Registrieren</button>
|
|
||||||
</form>
|
|
||||||
<div class="link">Bereits registriert? <a href="/login">Einloggen</a></div>
|
|
||||||
</div></body></html>""".format(request.form.get("phone",""))
|
|
||||||
|
|
||||||
@app.route("/login", methods=["GET", "POST"])
|
@app.route("/login", methods=["GET", "POST"])
|
||||||
def user_login():
|
def user_login():
|
||||||
@@ -2456,33 +2522,14 @@ def user_login():
|
|||||||
error = "Telefon oder Passwort falsch."
|
error = "Telefon oder Passwort falsch."
|
||||||
else:
|
else:
|
||||||
token = _create_session(db, user["id"])
|
token = _create_session(db, user["id"])
|
||||||
resp = make_response(redirect("/profile"))
|
from flask import make_response, redirect as _redir
|
||||||
|
resp = make_response(_redir("/profile"))
|
||||||
resp.set_cookie("bsn_session", token, max_age=30*86400, httponly=True)
|
resp.set_cookie("bsn_session", token, max_age=30*86400, httponly=True)
|
||||||
return resp
|
return resp
|
||||||
return f"""<!DOCTYPE html>
|
phone_val = request.form.get("phone", "")
|
||||||
<html lang="de"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
error_html = "<div class='error'>" + error + "</div>" if error else ""
|
||||||
<title>Login — BSN Community</title>
|
return _LOGIN_TPL.replace("{phone_val}", phone_val).replace("{error_html}", error_html)
|
||||||
<style>
|
|
||||||
body{{font-family:-apple-system,sans-serif;background:#1a1a2e;color:#eee;display:flex;justify-content:center;align-items:center;min-height:100vh;margin:0}}
|
|
||||||
.card{{background:#16213e;border-radius:16px;padding:2rem;width:100%;max-width:400px;box-shadow:0 8px 32px #00000040}}
|
|
||||||
h1{{color:#20228a;margin:0 0 1rem;font-size:1.4rem;text-align:center}}
|
|
||||||
input{{width:100%;padding:0.8rem;margin:0.4rem 0;border-radius:8px;border:1px solid #333;background:#0f3460;color:#fff;box-sizing:border-box;font-size:1rem}}
|
|
||||||
.btn{{width:100%;padding:0.8rem;background:#20228a;color:#fff;border:none;border-radius:8px;font-size:1rem;cursor:pointer;margin-top:0.5rem}}
|
|
||||||
.btn:hover{{background:#2a2caa}}
|
|
||||||
.error{{color:#ff453a;font-size:0.85rem;margin:0.5rem 0}}
|
|
||||||
.link{{text-align:center;margin-top:1rem;font-size:0.85rem}}
|
|
||||||
.link a{{color:#5e5ce6}}
|
|
||||||
</style></head><body>
|
|
||||||
<div class="card">
|
|
||||||
<h1>🔐 Login</h1>
|
|
||||||
{"<div class='error'>{}</div>".format(error) if error else ""}
|
|
||||||
<form method="post">
|
|
||||||
<input name="phone" placeholder="Telefon" value="{{}}" required>
|
|
||||||
<input name="password" type="password" placeholder="Passwort" required>
|
|
||||||
<button class="btn" type="submit">Einloggen</button>
|
|
||||||
</form>
|
|
||||||
<div class="link">Neu hier? <a href="/register">Registrieren</a></div>
|
|
||||||
</div></body></html>""".format(request.form.get("phone",""))
|
|
||||||
|
|
||||||
@app.route("/logout")
|
@app.route("/logout")
|
||||||
def user_logout():
|
def user_logout():
|
||||||
@@ -2491,16 +2538,19 @@ def user_logout():
|
|||||||
db = get_db()
|
db = get_db()
|
||||||
db.execute("DELETE FROM user_sessions WHERE token=?", (token,))
|
db.execute("DELETE FROM user_sessions WHERE token=?", (token,))
|
||||||
db.commit()
|
db.commit()
|
||||||
resp = make_response(redirect("/"))
|
from flask import make_response, redirect as _redir
|
||||||
|
resp = make_response(_redir("/"))
|
||||||
resp.set_cookie("bsn_session", "", max_age=0)
|
resp.set_cookie("bsn_session", "", max_age=0)
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
|
||||||
@app.route("/profile", methods=["GET", "POST"])
|
@app.route("/profile", methods=["GET", "POST"])
|
||||||
def user_profile():
|
def user_profile():
|
||||||
db = get_db()
|
db = get_db()
|
||||||
user = _get_user_from_session(db)
|
user = _get_user_from_session(db)
|
||||||
if not user:
|
if not user:
|
||||||
return redirect("/login")
|
from flask import redirect as _redir
|
||||||
|
return _redir("/login")
|
||||||
msg = ""
|
msg = ""
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
name = request.form.get("name", "").strip()
|
name = request.form.get("name", "").strip()
|
||||||
@@ -2508,36 +2558,8 @@ def user_profile():
|
|||||||
db.execute("UPDATE users SET name=? WHERE id=?", (name, user["id"]))
|
db.execute("UPDATE users SET name=? WHERE id=?", (name, user["id"]))
|
||||||
db.commit()
|
db.commit()
|
||||||
msg = "✅ Profil aktualisiert."
|
msg = "✅ Profil aktualisiert."
|
||||||
return f"""<!DOCTYPE html>
|
msg_html = "<div class='msg'>" + msg + "</div>" if msg else ""
|
||||||
<html lang="de"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
return _PROFILE_TPL.replace("{phone}", user["phone"]).replace("{name}", user["name"] or "").replace("{msg_html}", msg_html)
|
||||||
<title>Profil — BSN Community</title>
|
|
||||||
<style>
|
|
||||||
body{{font-family:-apple-system,sans-serif;background:#1a1a2e;color:#eee;display:flex;justify-content:center;align-items:center;min-height:100vh;margin:0}}
|
|
||||||
.card{{background:#16213e;border-radius:16px;padding:2rem;width:100%;max-width:400px;box-shadow:0 8px 32px #00000040}}
|
|
||||||
h1{{color:#20228a;margin:0 0 1rem;font-size:1.4rem}}
|
|
||||||
.field{{margin:0.8rem 0}}
|
|
||||||
.label{{color:#888;font-size:0.8rem;text-transform:uppercase}}
|
|
||||||
.value{{font-size:1.1rem}}
|
|
||||||
input{{width:100%;padding:0.6rem;margin:0.3rem 0;border-radius:6px;border:1px solid #333;background:#0f3460;color:#fff;box-sizing:border-box}}
|
|
||||||
.btn{{padding:0.6rem 1.2rem;background:#20228a;color:#fff;border:none;border-radius:6px;cursor:pointer}}
|
|
||||||
.btn:hover{{background:#2a2caa}}
|
|
||||||
.msg{{color:#30d158;margin:0.5rem 0}}
|
|
||||||
.nav{{margin-top:1rem;display:flex;gap:0.5rem}}
|
|
||||||
</style></head><body>
|
|
||||||
<div class="card">
|
|
||||||
<h1>👤 Mein Profil</h1>
|
|
||||||
{"<div class='msg'>{}</div>".format(msg) if msg else ""}
|
|
||||||
<div class="field"><div class="label">Telefon</div><div class="value">{user['phone']}</div></div>
|
|
||||||
<form method="post">
|
|
||||||
<div class="field"><div class="label">Name</div>
|
|
||||||
<input name="name" value="{user['name'] or ''}" placeholder="Dein Name"></div>
|
|
||||||
<button class="btn" type="submit">Speichern</button>
|
|
||||||
</form>
|
|
||||||
<div class="nav">
|
|
||||||
<a href="/" style="color:#5e5ce6">← Startseite</a>
|
|
||||||
<a href="/logout" style="color:#ff453a">Abmelden</a>
|
|
||||||
</div>
|
|
||||||
</div></body></html>"""
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
|
|||||||
Reference in New Issue
Block a user