Files
einkaufsapp/backend/app/bootstrap_admin.py
2026-08-08 20:31:58 +02:00

79 lines
2.6 KiB
Python

"""Anlegen des ersten Administratorkontos beim Start.
Bewusst eng gefasst: Das Konto entsteht nur, wenn die Datenbank noch
*gar keinen* Nutzer enthält. Damit kann ein vergessener Eintrag in der
.env kein bestehendes Konto überschreiben und auch kein zweites
Admin-Konto nachschieben.
Das angelegte Konto trägt `must_change_password`. Bis zum Wechsel sind
alle Routen gesperrt, die an `verified_user` hängen - also alles außer
"eigenes Profil lesen" und "Passwort ändern". Der Wert in der .env ist
nach der ersten Anmeldung damit wertlos.
"""
import logging
from sqlalchemy import func, select
from sqlalchemy.orm import Session
from app.config import settings
from app.models import User
from app.security import hash_password, normalize_email, utcnow
log = logging.getLogger(__name__)
MIN_LENGTH = 12
def ensure_initial_admin(db: Session) -> None:
password = settings.initial_admin_password
user_count = db.scalar(select(func.count()).select_from(User)) or 0
email = normalize_email(settings.admin_email)
if user_count > 0:
if password:
log.warning(
"ADMIN_INITIAL_PASSWORD ist gesetzt, es existieren aber bereits "
"%d Konten - der Wert wird ignoriert. Entferne ihn aus der .env, "
"damit kein Passwort unnötig in der Umgebung steht.",
user_count,
)
return
if not password:
log.info(
"Keine Konten vorhanden und kein ADMIN_INITIAL_PASSWORD gesetzt. "
"Erstes Konto über POST /api/auth/register anlegen (%s wird dabei "
"automatisch Administrator).",
email,
)
return
if len(password) < MIN_LENGTH:
raise RuntimeError(
f"ADMIN_INITIAL_PASSWORD ist zu kurz (mindestens {MIN_LENGTH} Zeichen)."
)
admin = User(
email=email,
display_name="Administrator",
password_hash=hash_password(password),
# Die Adresse gilt als bestätigt: Wer die .env schreiben kann,
# hat ohnehin vollen Zugriff. Ein Verifikationsumweg brächte hier
# keinen Sicherheitsgewinn, nur eine Abhängigkeit vom Mailversand
# bei der Ersteinrichtung.
verified_at=utcnow(),
is_admin=True,
must_change_password=True,
)
db.add(admin)
db.flush()
log.warning(
"Administratorkonto %s aus ADMIN_INITIAL_PASSWORD angelegt. "
"Das Passwort MUSS bei der ersten Anmeldung geändert werden; bis dahin "
"sind alle übrigen Funktionen gesperrt. Danach den Wert aus der .env "
"entfernen.",
email,
)