"""Dev-only admin seed — ensures a known portal admin exists.""" from __future__ import annotations import os from typing import Iterable from auth import hash_password from db import get_connection from psycopg2.extras import RealDictCursor from services.actors import create_actor, get_human_actor from services.audit import log_audit from services.registration import provision_system_admin, user_count DEV_ADMIN_EMAIL = "lars@stommer.com" DEV_ADMIN_PASSWORD = "12345678" DEV_ADMIN_NAME = "Lars Stommer" DEV_TENANT_SLUG = "stommer" DEV_TENANT_NAME = "Stommer" def _normalize_emails(emails: Iterable[str]) -> tuple[str, ...]: return tuple(sorted({email.strip().lower() for email in emails if email.strip()})) def delete_users_except(keep_emails: Iterable[str]) -> int: """Remove all users not in keep_emails (with FK-safe order). Returns deleted count.""" keep = _normalize_emails(keep_emails) if not keep: raise ValueError("keep_emails must not be empty") conn = get_connection() try: with conn.cursor() as cur: cur.execute("DELETE FROM actors WHERE actor_type = 'human' AND user_id IS NULL") cur.execute( """ DELETE FROM actors WHERE user_id IN ( SELECT id FROM users WHERE lower(email) <> ALL(%s) ) """, (list(keep),), ) cur.execute( """ DELETE FROM tenant_memberships WHERE user_id IN ( SELECT id FROM users WHERE lower(email) <> ALL(%s) ) """, (list(keep),), ) cur.execute( """ DELETE FROM sessions WHERE user_id IN ( SELECT id FROM users WHERE lower(email) <> ALL(%s) ) """, (list(keep),), ) cur.execute( """ DELETE FROM audit_log WHERE user_id IN ( SELECT id FROM users WHERE lower(email) <> ALL(%s) ) """, (list(keep),), ) cur.execute( "DELETE FROM users WHERE lower(email) <> ALL(%s) RETURNING email", (list(keep),), ) removed = cur.fetchall() conn.commit() return len(removed) finally: conn.close() def _ensure_tenant(conn, *, slug: str, name: str) -> dict: with conn.cursor(cursor_factory=RealDictCursor) as cur: cur.execute( """ INSERT INTO tenants (slug, name) VALUES (%s, %s) ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name RETURNING id, slug, name """, (slug, name), ) return dict(cur.fetchone()) def ensure_dev_admin( *, email: str = DEV_ADMIN_EMAIL, password: str = DEV_ADMIN_PASSWORD, display_name: str = DEV_ADMIN_NAME, tenant_slug: str = DEV_TENANT_SLUG, tenant_name: str = DEV_TENANT_NAME, ) -> dict: """Dev seed: remove other users, ensure portal admin + tenant + human actor.""" if os.getenv("ENVIRONMENT", "development").strip().lower() == "production": raise RuntimeError("ensure_dev_admin darf nicht in Production laufen") normalized_email = email.strip().lower() removed = delete_users_except([normalized_email]) if removed: print(f"[dev_admin] {removed} fremde User entfernt") if user_count() == 0: result = provision_system_admin( email=normalized_email, password=password, display_name=display_name, tenant_slug=tenant_slug, tenant_name=tenant_name, source="dev_seed", ) print(f"[dev_admin] Portal-Admin angelegt: {normalized_email}") return result conn = get_connection() try: with conn.cursor(cursor_factory=RealDictCursor) as cur: tenant = _ensure_tenant(conn, slug=tenant_slug, name=tenant_name) tenant_id = str(tenant["id"]) cur.execute( """ UPDATE users SET password_hash = %s, display_name = %s, portal_role = 'admin', updated_at = NOW() WHERE lower(email) = %s RETURNING id, email, display_name, portal_role """, (hash_password(password), display_name.strip(), normalized_email), ) row = cur.fetchone() if not row: raise RuntimeError(f"Dev-Admin {normalized_email} fehlt nach Bereinigung") user = dict(row) user_id = str(user["id"]) cur.execute( """ INSERT INTO tenant_memberships (tenant_id, user_id, tenant_role) VALUES (%s, %s, 'owner') ON CONFLICT (tenant_id, user_id) DO UPDATE SET tenant_role = 'owner' """, (tenant_id, user_id), ) conn.commit() finally: conn.close() if not get_human_actor(tenant_id, user_id): create_actor( tenant_id=tenant_id, actor_type="human", name=display_name.strip(), user_id=user_id, ) log_audit( "auth.dev_admin_ensure", user_id=user_id, tenant_id=tenant_id, details={"email": normalized_email, "removed_users": removed}, ) print(f"[dev_admin] Portal-Admin bestätigt: {normalized_email}") return { "user": { "id": user_id, "email": user["email"], "display_name": user["display_name"], "portal_role": user["portal_role"], }, "tenant": {"id": tenant_id, "slug": tenant["slug"], "name": tenant["name"]}, }