From 2d94a3aa263038850d4704acb90ec1eeb1539c92 Mon Sep 17 00:00:00 2001 From: "Hermes (Server)" Date: Wed, 26 Aug 2026 20:16:27 +0200 Subject: [PATCH] Spec: YouTube-Gast-Zugang mit Tages-Limit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Design-Dokument für den neuen anonymen Gast-Zugang zum YouTube-Proxy (5 Downloads/Tag, keine Cookies) neben dem bestehenden Baka-Auth-Cloud-Zugang. Mit Dustin im Brainstorming abgestimmt: geräte-gebundener Gast-Token, bewusste Wahl in der UI, Reset nach Server-Kalendertag, Suche bleibt unbegrenzt. Server-seitiger Teil (yt_proxy.py) liegt außerhalb dieses Worktrees — die Spec beschreibt den Vertrag dafür. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01CcDiyJdVRqh1TtJk5JiabX --- .../2026-08-26-youtube-guest-quota-design.md | 215 ++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-26-youtube-guest-quota-design.md diff --git a/docs/superpowers/specs/2026-08-26-youtube-guest-quota-design.md b/docs/superpowers/specs/2026-08-26-youtube-guest-quota-design.md new file mode 100644 index 0000000..bcc935d --- /dev/null +++ b/docs/superpowers/specs/2026-08-26-youtube-guest-quota-design.md @@ -0,0 +1,215 @@ +# YouTube-Tab: Gast-Zugang mit Tages-Limit + +Status: Approved (Dustin, 2026-08-26) — bereit für Implementierungsplan. + +## Kontext + +Der YouTube-Downloader/die YT-Suche laufen über den Baka-Proxy +(`yt.baka-net.de` → `yt_proxy.py`), der für **jede** Anfrage einen gültigen +Baka-Auth-Bearer-JWT verlangt (`auth_common.verify_token`). Seit dem Fix vom +2026-08-25/26 melden sich Server-User (Dustin, Baka, Tinker) automatisch im +Hintergrund an (`BakaAuth.autoAnmelden`, siehe `lib/services/baka_auth.dart`), +Gäste sehen weiterhin den manuellen „Beim Baka-Konto anmelden“-Dialog. + +Neue Anforderung (Dustin, 2026-08-26): Jeder Cloud-Account-User (die +baka-auth-User: baka, dustin, tinker — und künftig per Einladungscode +registrierte weitere User) bekommt automatisch Dustins YouTube-Cookies und +hat kein Limit. **Gäste ohne Cloud-Account sollen die App trotzdem nutzen +können, aber mit einem Tages-Limit von 5 Downloads und ohne Cookies.** + +Heute ist das serverseitig unmöglich: Ohne gültigen Bearer-Token antwortet +jeder Endpunkt mit 401. Es gibt keinen Weg, einen Gast über mehrere +Anfragen hinweg wiederzuerkennen. + +## Ziel + +Ein dritter, anonymer Zugangsweg („Gast-Token“) neben dem bestehenden +Baka-Auth-JWT, mit serverseitig durchgesetztem Tages-Limit für Downloads. +Suche bleibt für Gäste unbegrenzt. Cookies bleiben Cloud-Accounts +vorbehalten. + +## Nicht-Ziele + +- Kein Ersatz für das bestehende Baka-Auth-System — beide Wege bestehen + parallel. +- Kein Schutz gegen App-Neuinstallation/Token-Löschen — ein Gast kann sich + durch Neuinstallation ein neues Kontingent holen. Das ist eine bewusste + Hürde, kein harter Schutz (siehe Entscheidung unten). +- Keine Änderung an „nur Audio“ — das gilt schon für alle User (yt-dlp + extrahiert immer MP3, es gibt keine Video-Download-Option in der App). +- Keine UI/Server-Änderung an bestehenden Cloud-Account-Flows außer der + Cookie-Beschränkung (die war vorher implizit für alle gleich, jetzt + explizit an den Zugangsweg gekoppelt). + +## Entscheidungen (aus dem Brainstorming, mit Dustin abgestimmt) + +| Frage | Entscheidung | +|---|---| +| Gast-Identität | Geräte-gebundener, anonymer Token — kein IP-basiertes Limit, keine Einladungscode-Pflicht für Gäste | +| UX-Fluss | Bewusste Wahl: Gast sieht „Anmelden“ UND „Als Gast fortfahren (5/Tag)“ als zwei Buttons, kein automatisches Freischalten | +| Reset-Zeitpunkt | Kalendertag nach Server-Zeit (Aingrad), kein rollierendes 24h-Fenster | +| Suche fürs Limit | Suche bleibt für Gäste unbegrenzt — nur `POST /api/yt-dl` zählt | +| Cookies für Gäste | Nein — nur Cloud-Accounts bekommen Dustins YouTube-Cookies; der `cookies`-Parameter wird bei Gast-Token-Anfragen ignoriert | + +## Architektur + +Drei Zugangsklassen beim Proxy: + +1. **Cloud-Account** (`Authorization: Bearer `, wie heute) — unbegrenzt, + Cookies automatisch. +2. **Gast** (`X-Guest-Token: `, neu) — 5 Downloads/Kalendertag, + Suche unbegrenzt, keine Cookies. +3. **Kein Zugang** — weder Bearer noch gültiger Gast-Token → 401 wie heute. + +## Server-seitige Komponenten + +**Betrifft `/home/dustin/scripts/yt_proxy.py` und ggf. `auth_common.py` — +liegt im `claude-server`-Worktree, nicht in `~/mello-dev/app`. Diese Spec +beschreibt den Vertrag, den die App-Seite braucht; die Umsetzung macht die +Server-Session.** + +### Neuer Endpunkt: `POST /api/guest-token` + +Kein Auth-Header nötig. Erzeugt einen neuen, zufälligen Token +(`secrets.token_hex(24)`, 48 Hex-Zeichen) und legt eine Zeile in einer +neuen SQLite-Tabelle an: + +```sql +CREATE TABLE IF NOT EXISTS guest_quota ( + token TEXT PRIMARY KEY, + count INTEGER NOT NULL DEFAULT 0, + count_date TEXT NOT NULL, -- 'YYYY-MM-DD', Server-Zeit + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); +``` + +Antwort `200`: `{"guest_token": ""}`. + +Speicherort: neue Datei `/home/dustin/yt-cache/guest_quota.db` (analog zum +bestehenden `DOWNLOAD_DIR`-Cache, getrennt vom Lieder-Register und von +`baka_auth.db`, da funktional unabhängig). + +### `_checke_auth()` erweitert + +Bisher: `True`/`False`. Neu: gibt zurück, welche Zugriffsart vorliegt +(z. B. ein kleines Ergebnis-Objekt `{"cloud": bool, "guest_token": str|None}`) +statt nur ja/nein: + +- `Authorization: Bearer ` gültig → Cloud-Zugriff (wie heute). +- Sonst, falls `X-Guest-Token` gesetzt und in `guest_quota` vorhanden → + Gast-Zugriff mit dem jeweiligen Token. +- Sonst → kein Zugriff, 401 wie heute. + +### `GET /api/search` + +Cloud- **und** Gast-Zugriff erlaubt, keine Zählung für Gäste. + +### `POST /api/yt-dl` + +- Cloud-Zugriff: wie heute, `cookies`-Parameter aus dem Body wirksam. +- Gast-Zugriff: `cookies` wird serverseitig auf `False` erzwungen + (Body-Wert ignoriert). Vor dem Download: `count_date` der Token-Zeile + mit dem heutigen Server-Datum vergleichen — weicht es ab, `count` auf 0 + und `count_date` auf heute zurücksetzen (Tageswechsel). Ist danach + `count >= 5` → `429` mit + `{"error": "Tages-Limit erreicht (5/Tag) — ab morgen wieder verfügbar", "cooldown": }`. + Sonst: Download wie bisher, bei Erfolg `count += 1` in derselben + Transaktion wie die Antwort (kein Doppelzählen bei Netzwerkfehlern nach + dem Schreiben — Erfolg zuerst prüfen, dann zählen). +- Erfolgsantwort bekommt zusätzlich `"guest_remaining": ` (nur bei + Gast-Zugriff; bei Cloud-Zugriff entfällt das Feld), damit die App den + Zähler ohne Extra-Anfrage aktualisieren kann. + +### `GET /api/dl/` + +Cloud- und Gast-Zugriff erlaubt (die Datei wurde beim `yt-dl`-Aufruf schon +gezählt, hier nur Auslieferung). + +### Fehlerfall: unbekannter/ungültiger Gast-Token + +Antwort wie „kein Zugriff“ (401 mit `{"error": "Unauthorized"}` — nicht von +einem abgelaufenen Cloud-Token zu unterscheiden nötig, die App behandelt +beide gleich: neuen Gast-Token holen, siehe unten). + +## App-seitige Komponenten (diese Seite implementiere ich) + +### Neuer Service `GastZugang` (`lib/services/gast_zugang.dart`) + +Analog zu `BakaAuth`, aber ohne Passwort: + +```dart +class GastZugang extends ChangeNotifier { + String? _token; + int? _verbleibend; // zuletzt vom Server gemeldeter Stand, nur fürs UI + + bool get hatToken => _token != null; + int? get verbleibend => _verbleibend; + Map get gastHeader => + {if (_token != null) 'X-Guest-Token': _token!}; + + Future holeToken(); // POST /api/guest-token, speichert Token sicher + void merkeVerbleibend(int n); // aus der yt-dl-Antwort übernehmen +} +``` + +Token-Speicherung: `flutter_secure_storage`, eigener Schlüssel +`guest_token` (gleiches Muster wie `BakaAuth`/`TokenSpeicher`). + +### `YtDownloadService` / `YtSearchService` + +Bekommen einen optionalen zweiten Header-Lieferanten: statt nur +`auth.authHeader` können sie auch `gast.gastHeader` verwenden, je nachdem +welcher Zugang aktiv ist. `herunterladen()` liest bei Gast-Zugriff +`daten['guest_remaining']` aus der Erfolgsantwort und ruft +`gast.merkeVerbleibend(...)`. Ein `429` wird wie die bestehenden +Fehlerfälle über `_scheitere(text)` gemeldet (Klartext aus der +Server-Antwort, kein neuer Fehlerpfad nötig). + +### UI (`_YouTubeBereich` in `downloads_screen.dart`, `YoutubeSearchScreen`) + +Für echte Gäste (`!_serverUser && !auth.istAngemeldet && !gast.hatToken`): +zwei Buttons nebeneinander/untereinander — „Beim Baka-Konto anmelden“ +(bestehend) und „Als Gast fortfahren (5 Downloads/Tag)“ (neu, ruft +`gast.holeToken()`). Nach erfolgreichem Gast-Token: Adressfeld/Suchfeld wie +gewohnt freigeschaltet, zusätzlich ein kleiner Text „noch N von 5 heute“ +(aus `gast.verbleibend`, `null` vor dem ersten Download = kein Zähler +sichtbar, erst nach dem ersten Download bekannt). + +Der bestehende Cookie-Schalter (`SwitchListTile`, „YouTube-Cookies des +Servers verwenden“) wird für Gäste ausgeblendet — der Server ignoriert den +Wert ohnehin, ein sichtbarer aber wirkungsloser Schalter wäre irreführend. + +## Fehlerfälle (App-Seite) + +- **Gast-Token vom Server abgelehnt** (401 trotz gesetztem `X-Guest-Token`, + z. B. weil die Server-DB zurückgesetzt wurde): App holt automatisch + einen neuen Token und meldet den fehlgeschlagenen Versuch wie einen + normalen Fehler („bitte erneut versuchen“) — kein automatischer Retry + des Downloads selbst, um keine Doppel-Downloads auszulösen. +- **429 Tages-Limit erreicht**: Klartext-Fehlermeldung aus der + Server-Antwort, kein Absturz, kein automatischer Retry. +- **App-Neuinstallation**: neuer Gast-Token, neues Kontingent — bewusst + akzeptiert (siehe Nicht-Ziele). +- **Netzwerkfehler bei `POST /api/guest-token`**: wie bestehende + „Proxy nicht erreichbar“-Fehler behandelt. + +## Tests + +- **Server** (schreibt die `claude-server`-Session): Unit-Tests für + `guest_quota`-Zählung, Tageswechsel-Reset, Cookie-Ausschluss bei + Gast-Zugriff, 429 bei erreichtem Limit, Suche ohne Zählung. +- **App** (schreibe ich, TDD): `GastZugang` (Token holen, speichern, + `merkeVerbleibend`), Widget-Tests für die zwei Gast-Buttons und den + Zähler-Text, `YtDownloadService`/`YtSearchService` mit Gast-Header statt + Bearer-Header, 429-Fehlertext-Anzeige. + +## Offene Abhängigkeit + +Diese Spec beschreibt einen Vertrag zwischen App und Server. Die +App-seitige Implementierung (Service, UI) kann erst sinnvoll gegen den +echten Server getestet werden, wenn `POST /api/guest-token` und die +erweiterte `_checke_auth()`-Logik in `yt_proxy.py` existieren. Bis dahin +entwickle ich die App-Seite gegen einen gemockten Proxy (wie bei +`BakaAuth`/`YtDownloadService` schon üblich) und dokumentiere die exakten +Endpunkt-Erwartungen oben so genau, dass beide Seiten unabhängig +implementiert werden können.