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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CcDiyJdVRqh1TtJk5JiabX
9.5 KiB
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:
- Cloud-Account (
Authorization: Bearer <JWT>, wie heute) — unbegrenzt, Cookies automatisch. - Gast (
X-Guest-Token: <token>, neu) — 5 Downloads/Kalendertag, Suche unbegrenzt, keine Cookies. - 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:
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": "<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 <JWT>gültig → Cloud-Zugriff (wie heute).- Sonst, falls
X-Guest-Tokengesetzt und inguest_quotavorhanden → 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:
cookieswird serverseitig aufFalseerzwungen (Body-Wert ignoriert). Vor dem Download:count_dateder Token-Zeile mit dem heutigen Server-Datum vergleichen — weicht es ab,countauf 0 undcount_dateauf heute zurücksetzen (Tageswechsel). Ist danachcount >= 5→429mit{"error": "Tages-Limit erreicht (5/Tag) — ab morgen wieder verfügbar", "cooldown": <Sekunden bis Mitternacht>}. Sonst: Download wie bisher, bei Erfolgcount += 1in 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": <int>(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/<file>
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:
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<String, String> get gastHeader =>
{if (_token != null) 'X-Guest-Token': _token!};
Future<String?> 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ürguest_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/YtSearchServicemit 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.