Add change password
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
from contextlib import closing
|
from contextlib import closing
|
||||||
import bcrypt
|
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
|
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
|
||||||
54
app/db.py
54
app/db.py
@@ -1,5 +1,6 @@
|
|||||||
import sqlite3
|
import sqlite3
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import bcrypt
|
||||||
|
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
DB_PATH = BASE_DIR / "data" / "app.db"
|
DB_PATH = BASE_DIR / "data" / "app.db"
|
||||||
@@ -8,3 +9,56 @@ DB_PATH = BASE_DIR / "data" / "app.db"
|
|||||||
def get_conn():
|
def get_conn():
|
||||||
# check_same_thread=False, damit Streamlit mehrere Threads nutzen kann
|
# check_same_thread=False, damit Streamlit mehrere Threads nutzen kann
|
||||||
return sqlite3.connect(DB_PATH, check_same_thread=False)
|
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"
|
||||||
|
|||||||
32
app/main.py
32
app/main.py
@@ -2,7 +2,7 @@ import streamlit as st
|
|||||||
import yaml
|
import yaml
|
||||||
from yaml.loader import SafeLoader
|
from yaml.loader import SafeLoader
|
||||||
import streamlit_authenticator as stauth
|
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__
|
from version import __version__
|
||||||
|
|
||||||
|
|
||||||
@@ -83,6 +83,36 @@ def main():
|
|||||||
return
|
return
|
||||||
|
|
||||||
# ---- Ab hier eingeloggt (persistenter Cookie) ----
|
# ---- 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)
|
role = get_role_for_user(username)
|
||||||
|
|
||||||
authenticator.logout("Logout", "sidebar")
|
authenticator.logout("Logout", "sidebar")
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from logging_config import setup_logging
|
|||||||
import os
|
import os
|
||||||
from contextlib import closing
|
from contextlib import closing
|
||||||
import getpass
|
import getpass
|
||||||
from auth_core import create_user
|
from auth import create_user
|
||||||
|
|
||||||
APP_ENV = os.environ.get("APP_ENV", "dev")
|
APP_ENV = os.environ.get("APP_ENV", "dev")
|
||||||
logger = setup_logging(APP_ENV)
|
logger = setup_logging(APP_ENV)
|
||||||
@@ -98,6 +98,7 @@ def create_admin_user():
|
|||||||
if admin_exists():
|
if admin_exists():
|
||||||
logger.info("Adminkonto existiert bereits! Kein initiales Konto angelegt.")
|
logger.info("Adminkonto existiert bereits! Kein initiales Konto angelegt.")
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.info("Adminkonto wird angelegt ...")
|
logger.info("Adminkonto wird angelegt ...")
|
||||||
|
|
||||||
pw1 = getpass.getpass("Passwort: ")
|
pw1 = getpass.getpass("Passwort: ")
|
||||||
|
|||||||
BIN
data/app.db
BIN
data/app.db
Binary file not shown.
8
migrations/20251130_161100_add_col_newpwd_users.sql
Normal file
8
migrations/20251130_161100_add_col_newpwd_users.sql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
begin;
|
||||||
|
|
||||||
|
alter table users
|
||||||
|
add column new_pwd integer not null default 1;
|
||||||
|
|
||||||
|
INSERT INTO schema_version (version) VALUES ('20251130_161100_add_col_newpwd_users');
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
Reference in New Issue
Block a user