Wettlauf beim Reservieren, 500er bei unsinnigen Eingaben

Drei P3-Funde aus der Durchsicht vom 14.09.:

Reservieren: Statusprüfung und Schreiben waren getrennt - zwei
gleichzeitige Anfragen konnten beide passieren, die zweite überschrieb
Name und Token der ersten, ohne dass die es erfuhr. Jetzt entscheidet
ein UPDATE mit Status-Bedingung; der Verlierer bekommt None und die
Route meldet "schon weg" (Seite) bzw. 409 (API).

/?limit=abc lieferte jedem anonymen Besucher einen internen
Serverfehler, limit=-1 hiess in SQLite "alles". _limit_lesen() fällt
bei Unsinn auf die Seitengrösse zurück und deckelt bei 500.

Erfassen/Bearbeiten: int(category_id) und die Pydantic-Prüfung warfen
im Handler - 500 statt Fehlermeldung. Jetzt Meldung; ausserdem werden
die Angaben VOR den Bildern geprüft, damit bei abgelehnten Angaben
keine verwaisten Bilddateien liegen bleiben.

Vier neue Tests, jeder einmal gegen den alten Code gelaufen und dabei
rot geworden. 84 lokal grün (HEIC-Test braucht pillow-heif, Docker).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-14 17:22:54 +02:00
co-authored by Claude Fable 5
parent feceaacabb
commit c0a18ac735
6 changed files with 133 additions and 26 deletions
+16 -3
View File
@@ -146,7 +146,7 @@ verlässt sich das Image derzeit stillschweigend.
Proxy-IP eintragen; dort ausserdem den Hinweis ergänzen, dass der
veröffentlichte Port nur für den Proxy erreichbar sein darf.
### P3 — Öffentlicher 500er über `?limit=abc`
### P3 — Öffentlicher 500er über `?limit=abc` ✅ ERLEDIGT 14.09.2026
`pages.py` (`galerie`, `liste_ausschnitt`) rechnet
`int(request.query_params.get("limit") or SEITE)` ohne Prüfung — `/?limit=x`
@@ -155,7 +155,7 @@ bedeutet in SQLite „alles". Die API-Seite macht es mit
`Query(ge=1, le=200)` längst richtig; dieselbe Grenze hier nachziehen
(ungültig → Standardwert).
### P3 — Betreiber-Formulare: 500 statt Fehlermeldung
### P3 — Betreiber-Formulare: 500 statt Fehlermeldung ✅ ERLEDIGT 14.09.2026
In `erfassen` und `bearbeiten` (`pages.py`) wird `int(category_id)` ohne
Prüfung gerechnet, und `ItemAnlegen(...)` wirft bei ungültigen Werten eine
@@ -164,7 +164,7 @@ ValidationError **im** Handler — beides ergibt einen 500er statt der sonst
HTML-Formulare (Dropdown, `maxlength`) verhindern es auf dem normalen Weg —
es bricht aber das Muster, das `reservieren` mit try/except vormacht.
### P3 — Reservieren: Prüfen und Setzen sind nicht atomar
### P3 — Reservieren: Prüfen und Setzen sind nicht atomar ✅ ERLEDIGT 14.09.2026
`reservieren` prüft erst `status == available` und schreibt dann. Zwei
gleichzeitige Anfragen können beide die Prüfung passieren; der zweite
@@ -205,3 +205,16 @@ funktionierende Rate-Limits, aber alle Besucher teilen sich die Zähler
über die Proxy-IP. Das ist das kleinere Übel gegenüber frei erfindbaren
Absenderadressen. **Beim nächsten Deployment die Proxy-IP eintragen und
einmal prüfen, dass `request.client.host` die echte Besucher-IP zeigt.**
### Umsetzung der drei P3 am 14.09.2026
| Punkt | Ergebnis |
|---|---|
| `?limit=abc` | `_limit_lesen()` in `pages.py`: ungültig → Seitengrösse, Deckel 500 |
| Betreiber-Formulare | Angaben werden **vor** dem Bildspeichern geprüft; ValueError → Meldung statt 500 (und keine verwaisten Bilddateien mehr) |
| Reservieren | `crud.reservieren()` schreibt per `UPDATE … WHERE status='available'`; der Verlierer des Wettlaufs bekommt None → „schon weg" |
Vier neue Tests decken genau diese Fälle ab; alle vier wurden nach der
Hausregel einmal gegen den alten Code laufen gelassen und sind dabei
nachweislich rot geworden. Suite: **85 Tests** (84 lokal grün, der
HEIC-Test braucht `pillow-heif` und läuft im Docker-Testimage).