Add session-state and cookies

This commit is contained in:
hansi
2025-11-30 12:35:21 +01:00
parent 06e5322931
commit 84e1e152f8
9 changed files with 524 additions and 50 deletions

View File

@@ -1,4 +1,7 @@
# app/auth.py
import sqlite3
import time
import secrets
from contextlib import closing
import bcrypt
@@ -6,6 +9,38 @@ import streamlit as st
from db import get_conn
SESSION_LIFETIME_SECONDS = 8 * 60 * 60 # 8 Stunden
# ---------- User-DB ----------
def init_auth_db():
"""Legt die users-Tabelle an, falls sie noch nicht existiert."""
with closing(get_conn()) as conn, conn:
conn.execute(
"""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password_hash BLOB NOT NULL,
role TEXT NOT NULL DEFAULT 'user'
)
"""
)
# Sessions-Tabelle
conn.execute(
"""
CREATE TABLE IF NOT EXISTS sessions (
id TEXT PRIMARY KEY,
username TEXT NOT NULL,
created_at INTEGER NOT NULL,
expires_at INTEGER NOT NULL,
FOREIGN KEY (username) REFERENCES users(username) ON DELETE CASCADE
)
"""
)
def create_user(username: str, password: str, role: str = "user") -> bool:
pw_hash = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt())
try:
@@ -32,15 +67,64 @@ def verify_user(username: str, password: str):
return (ok, role) if ok else (False, None)
# ---------- Session / Helper ----------
def set_session_user(username: str, role: str):
# ---------- Session-DB ----------
def create_session(username: str) -> str:
"""Erzeugt eine neue Session-ID und speichert sie in der DB."""
sid = secrets.token_urlsafe(32) # schön zufällig, URL-tauglich
now = int(time.time())
expires_at = now + SESSION_LIFETIME_SECONDS
with closing(get_conn()) as conn, conn:
conn.execute(
"""
INSERT INTO sessions (id, username, created_at, expires_at)
VALUES (?, ?, ?, ?)
""",
(sid, username, now, expires_at),
)
return sid
def get_session(sid: str):
"""Liefert (username, role), wenn Session existiert und gültig ist, sonst (None, None)."""
now = int(time.time())
with closing(get_conn()) as conn:
row = conn.execute(
"""
SELECT s.username, u.role
FROM sessions s
JOIN users u ON u.username = s.username
WHERE s.id = ? AND s.expires_at > ?
""",
(sid, now),
).fetchone()
if not row:
return None, None
return row[0], row[1]
def delete_session(sid: str):
with closing(get_conn()) as conn, conn:
conn.execute("DELETE FROM sessions WHERE id = ?", (sid,))
# Optional: abgelaufene Sessions aufräumen
def cleanup_sessions():
now = int(time.time())
with closing(get_conn()) as conn, conn:
conn.execute("DELETE FROM sessions WHERE expires_at <= ?", (now,))
# ---------- SessionState-Helper ----------
def set_session_user(username: str, role: str, sid: str | None = None):
st.session_state["auth"] = True
st.session_state["username"] = username
st.session_state["role"] = role
if sid is not None:
st.session_state["session_id"] = sid
def clear_session_user():
for k in ["auth", "username", "role"]:
for k in ["auth", "username", "role", "session_id"]:
st.session_state.pop(k, None)
@@ -49,18 +133,34 @@ def is_authenticated() -> bool:
def current_user():
"""(username, role) oder (None, None)"""
return st.session_state.get("username"), st.session_state.get("role")
return (
st.session_state.get("username"),
st.session_state.get("role"),
)
def ensure_logged_in():
"""
Am Anfang jeder Page aufrufen.
Wenn kein Login: Login-View anzeigen und Script stoppen.
Prüft erst st.session_state, dann ?session_id=... in der URL.
"""
if not is_authenticated():
login_view()
st.stop()
if is_authenticated():
return
# Versuch: session_id aus URL
params = st.experimental_get_query_params()
sid_list = params.get("session_id")
sid = sid_list[0] if sid_list else None
if sid:
username, role = get_session(sid)
if username:
set_session_user(username, role, sid)
return
# Wenn wir hier landen: kein gültiger Login → Login-View anzeigen
login_view()
st.stop()
# ---------- UI ----------
@@ -73,9 +173,18 @@ def login_view():
submitted = st.form_submit_button("Anmelden")
if submitted:
ok, role = verify_user(u.strip(), p)
username = u.strip()
ok, role = verify_user(username, p)
if ok:
set_session_user(u.strip(), role)
# neue Session in DB
sid = create_session(username)
# SessionState setzen
set_session_user(username, role, sid)
# URL-Parameter setzen (Arme-Leute-Cookie)
st.experimental_set_query_params(session_id=sid)
st.success("Erfolgreich angemeldet.")
st.rerun()
else:
@@ -88,7 +197,16 @@ def authed_header():
return
st.sidebar.write(f"Angemeldet als **{username}** ({role})")
if st.sidebar.button("Logout"):
st.session_state.pop("app_started_logged", None)
# Session in DB löschen, falls vorhanden
sid = st.session_state.get("session_id")
if sid:
delete_session(sid)
clear_session_user()
st.rerun()
# URL-Parameter entfernen
st.experimental_set_query_params()
st.rerun()