Dustin hat beim Durchsprechen der Proxy-Architektur bestätigt: auch Dustin/Baka/Tinker bekommen ein Tages-Limit (100 statt 5 bei Gästen), nicht mehr unbegrenzt. Serverseitiger Vertrag (cloud_quota-Tabelle, cloud_remaining-Feld) und App-Seite (BakaAuth.verbleibend, UI-Text) dokumentiert, analog zum bestehenden Gast-Limit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012A2pmbnVNPiHdyf2GW8eLP
296 lines
14 KiB
Markdown
296 lines
14 KiB
Markdown
# 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 |
|
|
| Limit für Cloud-Accounts | **Nachtrag 2026-08-29:** 100 Downloads/Kalendertag je Nutzer (Dustin/Baka/Tinker je einzeln gezählt) — ersetzt das ursprüngliche „kein Limit". Grund: dieselbe Absicherung wie beim Gast-Limit, nur großzügiger, da Cloud-Accounts vertraute Nutzer mit Cookie-Zugriff sind. |
|
|
|
|
## Architektur
|
|
|
|
Drei Zugangsklassen beim Proxy:
|
|
|
|
1. **Cloud-Account** (`Authorization: Bearer <JWT>`, wie heute) — **100
|
|
Downloads/Kalendertag je Nutzer** (Nachtrag 2026-08-29, ersetzt
|
|
„unbegrenzt"), Cookies automatisch, Suche unbegrenzt (wie bei Gästen).
|
|
2. **Gast** (`X-Guest-Token: <token>`, neu) — 5 Downloads/Kalendertag,
|
|
Suche unbegrenzt, keine Cookies.
|
|
3. **Kein Zugang** — weder Bearer noch gültiger Gast-Token → 401 wie heute.
|
|
|
|
## Nachtrag 2026-08-29: 100/Tag-Limit auch für Cloud-Accounts
|
|
|
|
Dustin hat beim erneuten Durchsprechen der Architektur (nach den
|
|
Login-Race-Fixes) bestätigt, dass auch Cloud-Accounts (Dustin, Baka,
|
|
Tinker) ein Tages-Limit bekommen sollen — 100 statt der ursprünglich
|
|
geplanten 5, aber nicht mehr unbegrenzt. Grund: dieselbe
|
|
Missbrauchs-/Kosten-Absicherung wie beim Gast-Limit (YouTube-Rate-Limits,
|
|
Serverlast), nur mit großzügigerem Kontingent, weil Cloud-Accounts
|
|
vertraute, bekannte Nutzer sind und zusätzlich Cookie-Zugriff haben.
|
|
|
|
**Übernommene Entscheidungen des Gast-Limits (analog, keine neue
|
|
Diskussion nötig):** Reset zum Kalendertag (Server-Zeit), Suche zählt
|
|
nicht, nur `POST /api/yt-dl` zählt, Klartext-429-Fehlermeldung ohne
|
|
automatischen Retry.
|
|
|
|
**Unterschied zum Gast-Limit:** Cloud-Accounts zählen **pro Nutzername**
|
|
(aus dem verifizierten JWT), nicht pro Gerät/Token — ein Nutzer, der die
|
|
App auf zwei Geräten installiert hat, teilt sich also ein gemeinsames
|
|
Kontingent (folgerichtig, da derselbe Baka-Account).
|
|
|
|
### Server-seitige Änderungen (zusätzlich zu den bestehenden Abschnitten oben — Hermes/`claude-server`-Worktree)
|
|
|
|
- Neue Tabelle (oder Erweiterung von `guest_quota`, falls strukturell
|
|
identisch — Entscheidung liegt bei der Server-Session) analog zu
|
|
`guest_quota`, aber mit `username` statt `token` als Schlüssel:
|
|
```sql
|
|
CREATE TABLE IF NOT EXISTS cloud_quota (
|
|
username TEXT PRIMARY KEY,
|
|
count INTEGER NOT NULL DEFAULT 0,
|
|
count_date TEXT NOT NULL
|
|
);
|
|
```
|
|
- `POST /api/yt-dl` bei Cloud-Zugriff (gültiger Bearer-JWT): dieselbe
|
|
Tageswechsel-Reset-Logik wie bei Gästen, aber Schlüssel = Nutzername aus
|
|
dem JWT. Bei `count >= 100` → `429` mit
|
|
`{"error": "Tages-Limit erreicht (100/Tag) — ab morgen wieder verfügbar", "cooldown": <Sekunden bis Mitternacht>}`.
|
|
Erfolgsantwort bekommt zusätzlich `"cloud_remaining": <int>` (nur bei
|
|
Cloud-Zugriff, analog zu `guest_remaining` bei Gästen — beide Felder
|
|
schließen sich gegenseitig aus, nie beide gleichzeitig gesetzt).
|
|
- `GET /api/search` bleibt für Cloud-Accounts unverändert unbegrenzt (wie
|
|
für Gäste).
|
|
|
|
### App-seitige Änderungen (diese Seite implementiere ich)
|
|
|
|
- `BakaAuth` (`lib/services/baka_auth.dart`) bekommt — analog zu
|
|
`GastZugang.verbleibend`/`.merkeVerbleibend(n)` — ein neues Feld
|
|
`int? verbleibend` (Getter) und `void merkeVerbleibend(int n)`, das den
|
|
zuletzt vom Server gemeldeten Kontingent-Stand hält (nur fürs Anzeigen,
|
|
`null` vor dem ersten Download in dieser Session).
|
|
- `YtDownloadService.herunterladen()` (`lib/services/yt_download_service.dart`):
|
|
liest nach einem erfolgreichen `POST /api/yt-dl` zusätzlich
|
|
`daten['cloud_remaining']` aus, und ruft bei `!gastAktiv` (also
|
|
Cloud-Zugriff) `auth.merkeVerbleibend(cloudRest)` auf — analog zur
|
|
bestehenden `gast!.merkeVerbleibend(gastRest)`-Zeile. Ein `429` läuft
|
|
bereits durch den bestehenden generischen `_fehlerText(antwort)`-Pfad
|
|
(kein neuer Fehlerfall nötig, der zeigt jeden Server-`error`-Text aus dem
|
|
200-fremden Statuscode an — funktioniert für 429 genauso wie für 401
|
|
außerhalb des Spezialfalls).
|
|
- UI (`_YouTubeBereich` in `lib/downloads/downloads_screen.dart`,
|
|
`YoutubeSearchScreen` in `lib/downloads/youtube_search_screen.dart`):
|
|
zeigt analog zum bestehenden Gast-Text („Als Gast unterwegs — noch N von
|
|
5 heute") jetzt auch für angemeldete Cloud-Accounts einen Text „Noch N
|
|
von 100 heute", sobald `auth.verbleibend != null` — platziert an
|
|
vergleichbarer Stelle wie der Gast-Text, aber nur sichtbar wenn
|
|
`auth.istAngemeldet` (nicht für Gäste, die haben ihren eigenen Text).
|
|
|
|
### Tests (Nachtrag)
|
|
|
|
- **App:** `BakaAuth.merkeVerbleibend`, `YtDownloadService` liest
|
|
`cloud_remaining` bei Cloud-Zugriff (nicht bei Gast-Zugriff — die beiden
|
|
Felder dürfen sich nicht vermischen), Widget-Test für den neuen
|
|
„Noch N von 100 heute"-Text in beiden Bildschirmen.
|
|
- **Server** (Hermes): analog zu den bestehenden Gast-Tests, plus ein Test,
|
|
der sicherstellt, dass Gast- und Cloud-Kontingente unabhängig
|
|
voneinander gezählt werden (ein Cloud-Login verbraucht kein
|
|
Gast-Kontingent und umgekehrt — sie sind ohnehin durch unterschiedliche
|
|
Auth-Wege getrennt, aber das schadet nicht, explizit zu testen).
|
|
|
|
## 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": "<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-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": <Sekunden bis Mitternacht>}`.
|
|
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": <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:
|
|
|
|
```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<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ü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.
|