From 3cf6903df5d5dcea6a01ed68d690d103d6be8312 Mon Sep 17 00:00:00 2001 From: Stefan Date: Sat, 29 Aug 2026 21:50:34 +0200 Subject: [PATCH] Reservierung aufheben und offene Galerie entschieden MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Aufheben darf der Reservierende selbst (per Token-Link) und der Betreiber. Ein offener release-Endpunkt wäre die naheliegende, aber falsche Variante: dann löscht jeder Besucher fremde Reservierungen, und weil reserved_by mitgeht, bleibt nicht mal nachvollziehbar, dass jemand reserviert hatte. Token wird beim Aufheben und beim Erledigen gelöscht, Vergleich mit compare_digest. - Die Galerie bleibt frei zugänglich, ohne Zugangscode. Damit wird das Rate-Limit zur einzigen Bremse vor dem Reservieren-Endpunkt, und zwei Dinge gehören zwingend dazu: reserved_by erscheint öffentlich nur als "reserviert", und noindex/robots.txt verhindern, dass Fotos und Texte dauerhaft im Suchindex landen. projekt.md bei /release entsprechend präzisiert. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01GR4bNaj9GtRu57J4Niii8o --- Plan.md | 72 ++++++++++++++++++++++++++++++++++++------------------ projekt.md | 5 ++++ 2 files changed, 53 insertions(+), 24 deletions(-) diff --git a/Plan.md b/Plan.md index 008f61d..19e95fe 100644 --- a/Plan.md +++ b/Plan.md @@ -10,11 +10,15 @@ offen waren, und um die Punkte, die durch den Internet-Zugang dazukommen. | Frontend | **Variante A** – Jinja2 + HTMX + Tailwind, ein einziger Dienst, kein Node.js | | Erfassen/Bearbeiten/Löschen | Hinter **einem Passwort** | | Reservieren | **Ohne Anmeldung** | +| Reservierung aufheben | **Der Reservierende selbst** (per Token-Link) **und der Betreiber** | +| Galerie | **Frei zugänglich**, ohne Zugangscode | | Erreichbarkeit | **Aus dem Internet**, per Reverse-Proxy mit HTTPS | -Die letzten beiden zusammen sind der eigentliche Knackpunkt dieses Projekts: -ein Endpunkt, den jeder im Internet ohne Anmeldung auslösen kann, der den -Zustand ändert. Alles unter „Absicherung" ergibt sich daraus. +Die Kombination aus „frei zugänglich" und „Reservieren ohne Anmeldung" ist der +eigentliche Knackpunkt: ein Endpunkt, den jeder im Internet ohne jede Hürde +auslösen kann und der den Zustand ändert. Alles unter „Absicherung" ergibt +sich daraus – bei einer offenen Galerie ist das Rate-Limit keine Kür, sondern +das Einzige, was zwischen einem Skript und dem kompletten Bestand steht. --- @@ -86,7 +90,8 @@ Das Schema wird grundsätzlich übernommen. Fünf Ergänzungen: 2. **`reservation_token` (neu) in `items`.** Zufälliger Wert, der beim Reservieren erzeugt wird. Nur wer ihn hat, kann die eigene Reservierung - wieder aufheben. Begründung unten unter „Offene Punkte". + wieder aufheben; der Betreiber kann es ohnehin. Wird beim Aufheben und + beim Erledigen wieder gelöscht. Einzelheiten unter „Absicherung", Punkt 2. 3. **`sizes` als eigene Tabelle.** `projekt.md` sieht `GET /api/v1/sizes` vor, aber keine Tabelle dazu. Grössen aus den vorhandenen Einträgen zu @@ -176,16 +181,48 @@ bei einem Heimnetz-Dienst. Punkte 1 und 2 sind die wichtigsten. ### 2. Reservieren ohne Anmeldung -Ein offener, zustandsändernder Endpunkt im Internet. Ohne Bremse kann ein -Skript in Sekunden alles reservieren oder `reserved_by` als Werbefläche -missbrauchen. +Ein offener, zustandsändernder Endpunkt im Internet – bei frei zugänglicher +Galerie ohne jede vorgelagerte Hürde. Ohne Bremse kann ein Skript in Sekunden +alles reservieren oder `reserved_by` als Werbefläche missbrauchen. - **Rate-Limit pro IP** (`slowapi`), z.B. 5 Reservierungen pro Stunde. - `reserved_by` begrenzen (Länge, keine URLs) und beim Anzeigen maskieren. Jinja2 maskiert von sich aus – entscheidend ist, `|safe` dort nirgends zu verwenden. -- Reservierungen sind **jederzeit vom Betreiber aufhebbar** (Admin-Ansicht), - damit eine Missbrauchswelle in einem Rutsch aufgeräumt werden kann. +- Reservierungen sind **jederzeit vom Betreiber aufhebbar**, auch mehrere auf + einmal, damit eine Missbrauchswelle in einem Rutsch aufgeräumt werden kann. + +**Wer darf aufheben – und wie das ohne Konten funktioniert** + +Zwei Wege, entsprechend der Entscheidung oben: + +1. **Der Reservierende selbst.** Beim Reservieren erzeugt die Anwendung ein + `reservation_token` (Zufallswert) und zeigt danach einen Link + `/items/{id}/release?token=…` – zum Merken oder Weiterschicken. Nur wer + ihn hat, kann *diese* Reservierung zurücknehmen. +2. **Der Betreiber**, aus der Admin-Ansicht heraus, ohne Token. + +Ein offener `release`-Endpunkt ohne Token wäre die naheliegende, aber falsche +Variante: dann könnte jeder Besucher die Reservierung eines anderen löschen – +und weil `reserved_by` mitgelöscht wird, bliebe nicht einmal nachvollziehbar, +dass überhaupt jemand reserviert hatte. + +Das Token wird beim Aufheben und beim Erledigen (`mark-given`) gelöscht, damit +ein alter Link nicht später eine neue Reservierung eines anderen aufhebt. +Verglichen wird es mit `secrets.compare_digest`. + +### 2b. Folgen der offenen Galerie + +Die Galerie ist ohne Zugangscode erreichbar. Das ist eine bewusste +Entscheidung; zwei Dinge gehören dann aber dazu: + +- **`reserved_by` nicht öffentlich anzeigen** (siehe Abschnitt 5). In der + Galerie steht nur „reserviert", der Name erscheint ausschliesslich in der + Admin-Ansicht. Ohne Zugangscode wäre der Name sonst für jeden lesbar. +- **`noindex` und eine `robots.txt`.** Frei erreichbar heisst nicht, dass die + Seite in Suchergebnissen auftauchen muss. Ohne das landen Fotos und Texte + im Index von Google und sind auch dann noch auffindbar, wenn die Börse + längst abgeräumt ist. ### 3. Anmeldung fürs Erfassen @@ -302,24 +339,11 @@ Gleiches Vorgehen wie bei der Kantone-App ## Offene Punkte -1. **Wer darf eine Reservierung wieder aufheben?** `projekt.md` sieht - `POST /items/{id}/release` vor, sagt aber nicht, wer ihn aufrufen darf. Ist - er offen, kann jeder die Reservierung eines anderen löschen. Vorschlag: beim - Reservieren wird ein Token vergeben und als Link angezeigt („Reservierung - aufheben") – wer ihn hat, kann die eigene zurücknehmen; der Betreiber kann - es ohnehin. Bitte bestätigen, dann kommt `reservation_token` ins Modell. - -2. **Soll die Galerie überhaupt öffentlich sein?** Auch ohne Schreibzugriff - zeigt sie Kinderkleidung, Namen und indirekt den Wohnort. Ein einfacher - Zugangscode für die ganze Seite (einmal eingeben, bleibt im Cookie) würde - Suchmaschinen und Zufallsbesucher aussperren, ohne dass jemand ein Konto - braucht. Empfehlung: ja, zusätzlich zu `noindex`. Deine Entscheidung. - -3. **Benachrichtigung bei Reservierung?** Nicht in `projekt.md`. Ohne sie musst +1. **Benachrichtigung bei Reservierung?** Nicht in `projekt.md`. Ohne sie musst du selbst nachschauen. Ein einfacher Weg wäre eine Nachricht per E-Mail oder Telegram. Kann auch später kommen – dann aber besser gleich als eigener Meilenstein statt nachträglich eingeschoben. -4. **Mehrere Kinder / Grössenverläufe?** Aktuell ist alles ein flacher Bestand. +2. **Mehrere Kinder / Grössenverläufe?** Aktuell ist alles ein flacher Bestand. Falls du später nach „von wem" oder „Jahrgang" filtern willst, wäre jetzt der günstige Zeitpunkt für ein zusätzliches Feld. diff --git a/projekt.md b/projekt.md index 534c2da..3b61b19 100644 --- a/projekt.md +++ b/projekt.md @@ -79,8 +79,13 @@ Eine schlanke, mobile-optimierte Web-App zum Katalogisieren, Präsentieren und R ### Reservierung & Status - `POST /api/v1/items/{id}/reserve` - **Body:** `{"reserved_by": "Familie Meier"}` + - **Response:** enthält das `reservation_token` – daraus wird der Link zum + Aufheben gebildet. - `POST /api/v1/items/{id}/release` - Setzt Status zurück auf `available` und löscht `reserved_by`. + - **Erlaubt für:** den Reservierenden (mit gültigem `token`) oder den + angemeldeten Betreiber. Ohne beides: `403`. Sonst könnte jeder Besucher + fremde Reservierungen löschen. - `POST /api/v1/items/{id}/mark-given` - Setzt Status auf `given_away`.