Reservierung aufheben und offene Galerie entschieden
- 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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GR4bNaj9GtRu57J4Niii8o
This commit is contained in:
@@ -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 |
|
| Frontend | **Variante A** – Jinja2 + HTMX + Tailwind, ein einziger Dienst, kein Node.js |
|
||||||
| Erfassen/Bearbeiten/Löschen | Hinter **einem Passwort** |
|
| Erfassen/Bearbeiten/Löschen | Hinter **einem Passwort** |
|
||||||
| Reservieren | **Ohne Anmeldung** |
|
| 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 |
|
| Erreichbarkeit | **Aus dem Internet**, per Reverse-Proxy mit HTTPS |
|
||||||
|
|
||||||
Die letzten beiden zusammen sind der eigentliche Knackpunkt dieses Projekts:
|
Die Kombination aus „frei zugänglich" und „Reservieren ohne Anmeldung" ist der
|
||||||
ein Endpunkt, den jeder im Internet ohne Anmeldung auslösen kann, der den
|
eigentliche Knackpunkt: ein Endpunkt, den jeder im Internet ohne jede Hürde
|
||||||
Zustand ändert. Alles unter „Absicherung" ergibt sich daraus.
|
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
|
2. **`reservation_token` (neu) in `items`.** Zufälliger Wert, der beim
|
||||||
Reservieren erzeugt wird. Nur wer ihn hat, kann die eigene Reservierung
|
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,
|
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
|
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
|
### 2. Reservieren ohne Anmeldung
|
||||||
|
|
||||||
Ein offener, zustandsändernder Endpunkt im Internet. Ohne Bremse kann ein
|
Ein offener, zustandsändernder Endpunkt im Internet – bei frei zugänglicher
|
||||||
Skript in Sekunden alles reservieren oder `reserved_by` als Werbefläche
|
Galerie ohne jede vorgelagerte Hürde. Ohne Bremse kann ein Skript in Sekunden
|
||||||
missbrauchen.
|
alles reservieren oder `reserved_by` als Werbefläche missbrauchen.
|
||||||
|
|
||||||
- **Rate-Limit pro IP** (`slowapi`), z.B. 5 Reservierungen pro Stunde.
|
- **Rate-Limit pro IP** (`slowapi`), z.B. 5 Reservierungen pro Stunde.
|
||||||
- `reserved_by` begrenzen (Länge, keine URLs) und beim Anzeigen maskieren.
|
- `reserved_by` begrenzen (Länge, keine URLs) und beim Anzeigen maskieren.
|
||||||
Jinja2 maskiert von sich aus – entscheidend ist, `|safe` dort nirgends zu
|
Jinja2 maskiert von sich aus – entscheidend ist, `|safe` dort nirgends zu
|
||||||
verwenden.
|
verwenden.
|
||||||
- Reservierungen sind **jederzeit vom Betreiber aufhebbar** (Admin-Ansicht),
|
- Reservierungen sind **jederzeit vom Betreiber aufhebbar**, auch mehrere auf
|
||||||
damit eine Missbrauchswelle in einem Rutsch aufgeräumt werden kann.
|
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
|
### 3. Anmeldung fürs Erfassen
|
||||||
|
|
||||||
@@ -302,24 +339,11 @@ Gleiches Vorgehen wie bei der Kantone-App
|
|||||||
|
|
||||||
## Offene Punkte
|
## Offene Punkte
|
||||||
|
|
||||||
1. **Wer darf eine Reservierung wieder aufheben?** `projekt.md` sieht
|
1. **Benachrichtigung bei Reservierung?** Nicht in `projekt.md`. Ohne sie musst
|
||||||
`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
|
|
||||||
du selbst nachschauen. Ein einfacher Weg wäre eine Nachricht per E-Mail oder
|
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
|
Telegram. Kann auch später kommen – dann aber besser gleich als eigener
|
||||||
Meilenstein statt nachträglich eingeschoben.
|
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
|
Falls du später nach „von wem" oder „Jahrgang" filtern willst, wäre jetzt
|
||||||
der günstige Zeitpunkt für ein zusätzliches Feld.
|
der günstige Zeitpunkt für ein zusätzliches Feld.
|
||||||
|
|||||||
@@ -79,8 +79,13 @@ Eine schlanke, mobile-optimierte Web-App zum Katalogisieren, Präsentieren und R
|
|||||||
### Reservierung & Status
|
### Reservierung & Status
|
||||||
- `POST /api/v1/items/{id}/reserve`
|
- `POST /api/v1/items/{id}/reserve`
|
||||||
- **Body:** `{"reserved_by": "Familie Meier"}`
|
- **Body:** `{"reserved_by": "Familie Meier"}`
|
||||||
|
- **Response:** enthält das `reservation_token` – daraus wird der Link zum
|
||||||
|
Aufheben gebildet.
|
||||||
- `POST /api/v1/items/{id}/release`
|
- `POST /api/v1/items/{id}/release`
|
||||||
- Setzt Status zurück auf `available` und löscht `reserved_by`.
|
- 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`
|
- `POST /api/v1/items/{id}/mark-given`
|
||||||
- Setzt Status auf `given_away`.
|
- Setzt Status auf `given_away`.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user