fix: JWT secret startup guard and order number advisory lock

H1: model_validator in Settings raises ValueError when jwt_secret_key is
    "changeme" and the process is running inside a container — fast-fail
    prevents insecure deployments; local dev outside Docker is unaffected.

H7: generate_order_number now acquires pg_advisory_xact_lock before SELECT MAX,
    matching the same pattern used in generate_invoice_number (billing/service.py).
    Concurrent order creation can no longer race to produce duplicate numbers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 13:45:39 +02:00
co-authored by Claude Sonnet 4.6
parent df304dc021
commit 3c2d0816e5
3 changed files with 21 additions and 3 deletions
+6
View File
@@ -534,6 +534,12 @@ Der Admin-Settings-Endpunkt (`GET /api/admin/settings`) erfordert `global_admin`
### 2026-07-22 | Workflow-Editor | _legacy_dispatch umging cancelled/rejected Pre-Check
`_legacy_dispatch` in `dispatch_service.py` rief `render_order_line_task.delay()` direkt auf und umging damit den Pre-Check in `dispatch_order_line_render` (der cancelled/rejected Lines überspringt). Alle Legacy-Dispatch-Pfade (auch im Graph-Fallback) liefen so durch, auch für bereits gecancelte Jobs. **Lösung:** `_legacy_dispatch` ruft jetzt `dispatch_order_line_render.delay()` auf statt `render_order_line_task.delay()` direkt.
### 2026-07-22 | Security | JWT secret "changeme" ohne Startup-Guard
`jwt_secret_key` in `config.py` hatte den Standardwert `"changeme"`. Deployments, die vergessen `JWT_SECRET_KEY` zu setzen, ließen gültige JWTs fälschen. **Lösung:** `@model_validator(mode="after")` in `Settings` — wirft `ValueError` wenn key `"changeme"` ist UND `_is_running_in_container()` True ist. Lokale Entwicklung außerhalb Docker ist nicht betroffen.
### 2026-07-22 | Security | Race condition bei Ordernummer-Generierung
`generate_order_number` in `orders/service.py` verwendete `SELECT MAX(order_number)` ohne Advisory Lock. Bei gleichzeitigen Requests konnten zwei Sessions denselben MAX-Wert lesen und identische Nummern generieren, was zu einem `IntegrityError` beim Flush führte. **Lösung:** `pg_advisory_xact_lock(hashtext(:key))` vor dem SELECT — gleicher Pattern wie `generate_invoice_number` in `billing/service.py`. Lock wird beim Commit/Rollback automatisch freigegeben.
### 2026-07-22 | Security | localhost:8888 hardcoded in chat_service.py
`chat_service.py` verwendete `httpx.AsyncClient(base_url="http://localhost:8888", ...)` an 5 Stellen. In Docker löst `localhost` nicht zur Backend-Adresse auf, wenn der Service in einem anderen Container läuft. **Lösung:** `internal_api_base_url: str = "http://localhost:8888"` zu `config.py` Settings hinzugefügt (env-konfigurierbar via `INTERNAL_API_BASE_URL`). Alle 5 Stellen nutzen jetzt `settings.internal_api_base_url`.