Add change password

This commit is contained in:
handi
2025-11-30 16:34:34 +01:00
parent 111d0d2756
commit 5277830c50
6 changed files with 143 additions and 3 deletions

View File

@@ -3,7 +3,7 @@
from contextlib import closing
import bcrypt
from db import get_conn
from db import get_conn #, create_user, verify_user, get_role_for_user
# ---------------------------------------------------------------------------
@@ -133,3 +133,50 @@ def load_credentials_from_db() -> dict:
}
return creds
# ---------------------------------------------------------------------------
# Muss das Passwort geändert werden (Admin-Vorgabe)?
# ---------------------------------------------------------------------------
def needs_password_change(username: str) -> bool:
"""Prüft, ob der User ein neues Passwort setzen muss (new_pwd = 1)."""
with closing(get_conn()) as conn:
row = conn.execute(
"SELECT new_pwd FROM users WHERE username = ?",
(username,),
).fetchone()
if not row:
return False
return row[0] == 1
# ---------------------------------------------------------------------------
# Passwort ändern durch den Benutzer
# ---------------------------------------------------------------------------
def update_password(username: str, new_password: str, reset_flag: bool = True) -> bool:
"""
Setzt ein neues Passwort für den User.
Wenn reset_flag=True: new_pwd wird auf 0 zurückgesetzt.
"""
pw_hash = bcrypt.hashpw(new_password.encode("utf-8"), bcrypt.gensalt()).decode("utf-8")
with closing(get_conn()) as conn, conn:
if reset_flag:
conn.execute(
"""
UPDATE users
SET password_hash = ?, new_pwd = 0
WHERE username = ?
""",
(pw_hash, username),
)
else:
conn.execute(
"""
UPDATE users
SET password_hash = ?
WHERE username = ?
""",
(pw_hash, username),
)
return True

View File

@@ -1,5 +1,6 @@
import sqlite3
from pathlib import Path
import bcrypt
BASE_DIR = Path(__file__).resolve().parent.parent
DB_PATH = BASE_DIR / "data" / "app.db"
@@ -8,3 +9,56 @@ DB_PATH = BASE_DIR / "data" / "app.db"
def get_conn():
# check_same_thread=False, damit Streamlit mehrere Threads nutzen kann
return sqlite3.connect(DB_PATH, check_same_thread=False)
def create_user(
username: str,
password: str,
role: str = "user",
email: str | None = None,
firstname: str | None = None,
lastname: str | None = None
) -> bool:
"""
Passwort wird als bcrypt-Hash (TEXT) gespeichert.
"""
pw_hash = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode("utf-8")
try:
with closing(get_conn()) as conn, conn:
conn.execute(
"INSERT INTO users (username, email, password_hash, role, firstname, lastname) VALUES (?, ?, ?, ?, ?, ?)",
(username, email, pw_hash, role, firstname, lastname),
)
return True
except Exception:
return False
def verify_user(username: str, password: str):
"""
Vergleicht eingegebenes Passwort mit dem gespeicherten bcrypt-Hash.
Rückgabe: (True, role) oder (False, None)
"""
with closing(get_conn()) as conn:
row = conn.execute(
"SELECT password_hash, role FROM users WHERE username = ?",
(username,),
).fetchone()
if not row:
return False, None
stored_hash, role = row
# stored_hash ist TEXT → zurück nach bytes
ok = bcrypt.checkpw(password.encode("utf-8"), stored_hash.encode("utf-8"))
return (ok, role) if ok else (False, None)
def get_role_for_user(username: str) -> str:
with closing(get_conn()) as conn:
row = conn.execute(
"SELECT role FROM users WHERE username = ?",
(username,),
).fetchone()
return row[0] if row else "user"

View File

@@ -2,7 +2,7 @@ import streamlit as st
import yaml
from yaml.loader import SafeLoader
import streamlit_authenticator as stauth
from auth_core import load_credentials_from_db, get_role_for_user, create_user
from auth import load_credentials_from_db, get_role_for_user, create_user, needs_password_change, update_password
from version import __version__
@@ -83,6 +83,36 @@ def main():
return
# ---- Ab hier eingeloggt (persistenter Cookie) ----
if needs_password_change(username):
st.warning("Du musst dein Passwort ändern, bevor du die Anwendung nutzen kannst.")
# Damit das Formular nur einmal pro Run erscheint
with st.form("pw_change_form"):
pw1 = st.text_input("Neues Passwort", type="password")
pw2 = st.text_input("Neues Passwort (Wiederholung)", type="password")
submitted = st.form_submit_button("Passwort ändern")
if submitted:
if not pw1 or not pw2:
st.error("Bitte beide Passwortfelder ausfüllen.")
return
if pw1 != pw2:
st.error("Passwörter stimmen nicht überein.")
return
if len(pw1) < 8:
st.error("Passwort sollte mindestens 8 Zeichen lang sein.")
return
update_password(username, pw1, reset_flag=True)
st.success("Passwort wurde geändert.")
# Optional: danach Seite einmal „sauber“ neu laden
st.rerun()
# Solange new_pwd=1 ist, KEINEN weiteren Content anzeigen
return
role = get_role_for_user(username)
authenticator.logout("Logout", "sidebar")

View File

@@ -6,7 +6,7 @@ from logging_config import setup_logging
import os
from contextlib import closing
import getpass
from auth_core import create_user
from auth import create_user
APP_ENV = os.environ.get("APP_ENV", "dev")
logger = setup_logging(APP_ENV)
@@ -98,6 +98,7 @@ def create_admin_user():
if admin_exists():
logger.info("Adminkonto existiert bereits! Kein initiales Konto angelegt.")
return
logger.info("Adminkonto wird angelegt ...")
pw1 = getpass.getpass("Passwort: ")