Zum Inhalt

User Management – Technische Dokumentation

Sovereign MoE Orchestrator – User Management System
Version 1.0 · April 2026


Architektur-Überblick

graph TD
    subgraph ADMIN_UI["Admin UI · Port 8088"]
        USERS_ROUTE["/users — User CRUD,\nBudget, Permissions, API Keys"]
        PORTAL_ROUTE["/user/* — User-Portal\n(Login, Dashboard, Usage)"]
        DB_PY["database.py\nSQLite CRUD + Redis-Sync"]
    end

    SQLITE[("SQLite\n/opt/moe-infra/userdb/users.db\nWAL-Mode")]

    subgraph ORCH["Orchestrator · Port 8002"]
        VALIDATE["_validate_api_key()\nRedis-Lookup (TTL 300s)"]
        LOG_USAGE["_log_usage_to_db()\nasync fire&forget"]
        INC_BUDGET["_increment_user_budget()\nRedis-Counter"]
        PROM_METRICS["PROM_TOKENS\nPROM_REQUESTS\n(user_id Label)"]
    end

    subgraph REDIS_KEYS["Redis (terra_cache · Port 6379)"]
        KEY_APIKEY["user:apikey:{sha256}  HASH  TTL 300s"]
        KEY_DAILY["user:{id}:tokens:daily:{date}"]
        KEY_MONTHLY["user:{id}:tokens:monthly:{YYYY-MM}"]
        KEY_TOTAL["user:{id}:tokens:total"]
    end

    ADMIN_UI <-->|"shared Docker volume"| SQLITE
    SQLITE <--> ORCH
    VALIDATE <--> KEY_APIKEY
    INC_BUDGET --> KEY_DAILY & KEY_MONTHLY & KEY_TOTAL

Datenbank-Schema

Datei: /opt/moe-infra/userdb/users.db (SQLite, WAL-Modus)

Tabellen

users

Spalte Typ Beschreibung
id TEXT PK UUID4 hex
username TEXT UNIQUE Anmeldename
email TEXT UNIQUE E-Mail
display_name TEXT Anzeigename
hashed_password TEXT bcrypt-Hash (via passlib)
is_active INTEGER 1=aktiv, 0=gesperrt
is_admin INTEGER 1=Admin-Rechte
created_at TEXT ISO-8601 UTC
updated_at TEXT ISO-8601 UTC

api_keys

Spalte Typ Beschreibung
id TEXT PK UUID4 hex
user_id TEXT FK→users Zugehöriger User
key_hash TEXT UNIQUE SHA-256 des raw key
key_prefix TEXT Erste 16 Zeichen (Anzeige)
label TEXT Benutzerdef. Bezeichnung
is_active INTEGER 1=aktiv
created_at TEXT ISO-8601 UTC
last_used_at TEXT Letzter Einsatz (NULL=nie)
expires_at TEXT Ablaufdatum (NULL=unbegrenzt)

Raw API Keys werden nie gespeichert. Nur der SHA-256-Hash wird für Lookups verwendet. Das Key-Format ist moe-sk-{48 random hex chars}.

token_budgets

Spalte Typ Beschreibung
user_id TEXT PK FK 1:1 mit users
daily_limit INTEGER NULL = unlimitiert
monthly_limit INTEGER NULL = unlimitiert
total_limit INTEGER NULL = unlimitiert
updated_at TEXT Letztes Update

permissions

Spalte Typ Beschreibung
id TEXT PK UUID4 hex
user_id TEXT FK Zugehöriger User
resource_type TEXT expert_template, cc_profile, model_endpoint, skill, mcp_tool, moe_mode
resource_id TEXT z.B. tmpl-abc123, profile-moe, qwen2.5-coder:32b@N04-RTX, native
granted_at TEXT ISO-8601 UTC

Default: alle Zugriffe gesperrt. Nur explizit eingetragene Permissions sind erlaubt.

Permission-Typen:

resource_type Bedeutung Beispiel resource_id
expert_template Experten-Konfigurationspaket tmpl-abc123
cc_profile Claude Code Integrations-Profil profile-moe
model_endpoint Native LLM via OpenAI-API llama3.3:70b@N04-RTX
moe_mode MoE-Verarbeitungsmodus native, moe_orchestrated, moe_reasoning
skill Claude Code Skill calc
mcp_tool MCP-Tool precision-calc

usage_log

Spalte Typ Beschreibung
id TEXT PK UUID4 hex
user_id TEXT FK Zugehöriger User
api_key_id TEXT FK Genutzter Key
request_id TEXT chat_id vom Orchestrator
model TEXT Angefragtes Modell
moe_mode TEXT Routing-Modus
prompt_tokens INTEGER Eingabe-Tokens
completion_tokens INTEGER Ausgabe-Tokens
total_tokens INTEGER Summe
status TEXT ok, budget_exceeded, error
requested_at TEXT ISO-8601 UTC

API-Kompatibilität

Der Orchestrator (Port 8002) bietet zwei kompatible Schnittstellen:

Endpunkt Format Primäre Nutzung
/v1/messages Anthropic Messages API Claude Code (ANTHROPIC_BASE_URL), Anthropic SDK
/v1/chat/completions OpenAI Chat Completions API Open WebUI, native LLMs, OpenAI SDK

Claude Code kommuniziert ausschließlich über /v1/messages (Anthropic Messages API). Die API-Auswahl hängt vom Client ab — der Orchestrator unterstützt beide Formate transparent.


Redis-Schlüsselstruktur

user:apikey:{sha256_of_key}     HASH  TTL 300s
  Fields:
    user_id          → UUID hex des Users
    username         → Anmeldename (für Logging)
    is_active        → "1" oder "0"
    permissions_json → '{"model_endpoint":["qwen2.5-coder:32b@N04-RTX"],...}'
    budget_daily     → "50000" oder "" (leer = kein Limit)
    budget_monthly   → "500000" oder ""
    budget_total     → "" oder Zahl

user:{user_id}:tokens:daily:{YYYY-MM-DD}    STRING  INCRBY  TTL 48h
user:{user_id}:tokens:monthly:{YYYY-MM}     STRING  INCRBY  TTL 35d
user:{user_id}:tokens:total                 STRING  INCRBY  kein TTL

Cache-Invalidierung

Ereignis Redis-Aktion
User erstellt HSET aller Keys + TTL 300s
User deaktiviert DEL aller Keys
API Key erstellt HSET neuer Key
API Key gesperrt DEL spezifischer Key
Permission geändert HSET aller Keys (permissions_json)
Budget geändert HSET aller Keys (budget_*)

Auth-Flow (Orchestrator)

Client-Request
_extract_api_key(request)
  ├── Authorization: Bearer moe-sk-xxx  →  raw_key
  └── x-api-key: moe-sk-xxx            →  raw_key
_validate_api_key(raw_key)
    ├── SHA-256(raw_key) → key_hash
    ├── Redis HGETALL user:apikey:{key_hash}
    │     ├── Cache-Miss → return None (401)
    │     ├── is_active != "1" → return None (401)
    │     └── Budget-Check:
    │           ├── daily_limit gesetzt?
    │           │     Redis GET user:{id}:tokens:daily:{date}
    │           │     ≥ limit → PROM_BUDGET_EXCEEDED.inc() + return None (429)
    │           ├── monthly_limit gesetzt? → analog
    │           └── total_limit gesetzt? → analog
    └── return {user_id, username, permissions_json, ...}

Pipeline wird ausgeführt (AgentState.user_id gesetzt)
Post-Request (fire-and-forget asyncio.create_task)
    ├── _log_usage_to_db() → SQLite usage_log INSERT
    └── _increment_user_budget() → Redis INCRBY (daily, monthly, total)

Admin-UI Routen

User-Verwaltung (erfordert Admin-Login)

Methode Pfad Funktion
GET /users User-Liste (HTML)
GET /api/users User-Liste JSON
POST /api/users User anlegen
GET /api/users/{id} User-Details (inkl. Keys, Perms, Usage)
PUT /api/users/{id} User aktualisieren
DELETE /api/users/{id} User deaktivieren (soft delete)
PUT /api/users/{id}/budget Token-Budget setzen
GET /api/users/{id}/permissions Freigaben auflisten
POST /api/users/{id}/permissions Freigabe erteilen
DELETE /api/users/{id}/permissions/{pid} Freigabe entziehen
GET /api/users/{id}/keys API Keys auflisten
POST /api/users/{id}/keys API Key erstellen
DELETE /api/users/{id}/keys/{kid} API Key sperren
GET /api/users/{id}/usage Nutzungsstatistik

Expert Templates (erfordert Admin-Login)

Methode Pfad Funktion
GET /templates Templates-Übersicht (HTML)
GET /api/expert-templates Templates auflisten (JSON)
POST /api/expert-templates Template erstellen
PUT /api/expert-templates/{id} Template bearbeiten
DELETE /api/expert-templates/{id} Template löschen

User-Portal (erfordert User-Login)

Methode Pfad Funktion
GET/POST /user/login User-Login
GET /user/logout Logout
GET /user/dashboard Dashboard
GET/POST /user/profile Profil & Passwort
GET /user/usage Nutzungshistorie
GET /user/billing Abrechnung
GET/POST /user/keys API Keys
POST /user/keys/{id}/revoke Key sperren

Prometheus-Metriken

Geänderte Metriken (neues Label user_id)

# Vorher:
moe_tokens_total{model, token_type, node}
moe_requests_total{mode, cache_hit}

# Nachher:
moe_tokens_total{model, token_type, node, user_id}
moe_requests_total{mode, cache_hit, user_id}

Nicht-authentifizierte Anfragen tragen user_id="anon".

Neue Metrik

moe_budget_exceeded_total{user_id, limit_type}
# limit_type: "daily" | "monthly" | "total"

Beispiel-PromQL-Queries

# Tokens pro User (letzte Stunde)
sum by (user_id) (increase(moe_tokens_total[1h]))

# Anfragen pro User heute
sum by (user_id) (increase(moe_requests_total[24h]))

# Budget-Überschreitungen dieser Woche
sum by (user_id, limit_type) (increase(moe_budget_exceeded_total[7d]))

# Top-5 User nach Verbrauch (gesamt)
topk(5, sum by (user_id) (moe_tokens_total))

Deployment

Neue Dienste / Volumes

# docker-compose.yml Ergänzungen:

volumes:
  userdb_data:        # Shared zwischen moe-admin und langgraph-app
    driver: local
    driver_opts:
      type: none
      o: bind
      device: /opt/moe-infra/userdb

# moe-admin erhält:
environment:
  - REDIS_URL=redis://terra_cache:6379
  - DB_PATH=/app/userdb/users.db
volumes:
  - userdb_data:/app/userdb

# langgraph-app erhält:
environment:
  - DB_PATH=/app/userdb/users.db
volumes:
  - userdb_data:/app/userdb

Rebuild & Deploy

cd /opt/deployment/moe-infra

# Admin-UI neu bauen (neue requirements + database.py)
sudo docker compose build moe-admin

# Orchestrator neu bauen (aiosqlite in requirements)
sudo docker compose build langgraph-app

# Services neu starten
sudo docker compose up -d moe-admin langgraph-app

Verifikation

# DB-Datei prüfen
ls -la /opt/moe-infra/userdb/

# Admin-UI Logs (DB-Init)
sudo docker logs moe-admin --tail 20

# Test: User anlegen via API
curl -s -X POST http://localhost:8088/api/users \
  -H "Content-Type: application/json" \
  -d '{"username":"testuser","email":"test@example.com","password":"test1234"}' \
  -b "session=<admin-session-cookie>"

# Redis-Key prüfen nach Key-Erstellung:
sudo docker exec terra_cache redis-cli keys "user:apikey:*"

Sicherheitshinweise

  1. Passwort-Hashing: bcrypt via passlib (work factor 12), asyncio.to_thread für Non-Blocking
  2. API Key Storage: SHA-256 One-Way-Hash, raw key nie persistent gespeichert
  3. CSRF-Schutz: Alle POST-Formulare im User-Portal nutzen CSRF-Tokens (Starlette SessionMiddleware)
  4. Session-Isolation: Admin-Session (authenticated) und User-Session (user_authenticated) sind getrennte Keys im selben Session-Store
  5. SQLite WAL-Modus: Ermöglicht concurrent reads zwischen zwei Docker-Containern ohne Locks
  6. Redis-TTL: API-Key-Cache verfällt nach 300 Sekunden — bei Revoke sofortige DEL
  7. Principle of Least Privilege: Neuer User hat keine Permissions — alles muss explizit erteilt werden

Grafana Dashboard

Das Dashboard MoE – User Metriken (UID: moe-users-v1) ist unter
/opt/grafana/dashboards/moe-users.json gespeichert und wird automatisch via Provisioning geladen.

Panels: - Stat: Anfragen gesamt, Tokens gesamt, Budget-Überschreitungen (24h), Aktive User (1h) - Timeseries: Token-Verbrauch pro User (Rate 5m), Anfragen pro User, Budget-Überschreitungen - Table: Top User nach Token-Verbrauch - Bar Chart: Token-Verbrauch nach User × Modell

Template-Variable: user_id — filtert alle Panels auf ausgewählte User.


Stand: April 2026 · MoE Sovereign Orchestrator