OOB-Dashboard/app/bootstrap.py

67 lines
2.2 KiB
Python

"""Seed the administrator account named in the environment."""
from __future__ import annotations
import logging
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.config import Settings
from app.models import User, utcnow
from app.security import hash_password
log = logging.getLogger(__name__)
def seed_admin(db: Session, s: Settings) -> User | None:
"""Idempotent. Creates the admin pre-verified on first run; on later runs it
repairs the flags but leaves the password alone -- otherwise every restart
would revert a rotated password and the stale .env value would be a
permanent way in. ADMIN_RESET_PASSWORD=true is the deliberate override."""
if not (s.ADMIN_EMAIL and s.ADMIN_PASSWORD):
log.info("ADMIN_EMAIL/ADMIN_PASSWORD not set; skipping admin seed")
return None
email = s.ADMIN_EMAIL.strip().lower()
user = db.scalar(select(User).where(User.email == email))
if user is None:
user = User(
email=email,
name="Administrator",
password_hash=hash_password(s.ADMIN_PASSWORD.get_secret_value()),
is_admin=True,
is_active=True,
email_verified_at=utcnow(),
)
db.add(user)
db.commit()
log.info("seeded admin account %s", email)
return user
changed = []
if not user.is_admin:
user.is_admin = True
changed.append("is_admin")
if not user.is_active:
user.is_active = True
changed.append("is_active")
if user.email_verified_at is None:
user.email_verified_at = utcnow()
changed.append("email_verified_at")
if user.locked_until is not None or user.failed_login_count:
user.locked_until = None
user.failed_login_count = 0
changed.append("lockout cleared")
if s.ADMIN_RESET_PASSWORD:
user.password_hash = hash_password(s.ADMIN_PASSWORD.get_secret_value())
changed.append("password reset from ADMIN_PASSWORD")
if changed:
db.commit()
log.info("admin account %s updated: %s", email, ", ".join(changed))
else:
log.info("admin account %s already present", email)
return user