Add session-state and cookies
This commit is contained in:
144
app/auth.py
144
app/auth.py
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user