feat(downloads): per-user, per-host yt-dlp cookie/auth support (S6)

Each user stores their own login cookies (Netscape cookies.txt) for an
auth-walled download host (Facebook, Instagram, X, TikTok, Reddit) in
Settings; a download of an auth-walled link on that host is retried with
the acting user's cookie.

Two load-bearing security controls, both in downloads.credentials as the
single source of truth so enqueue and the worker can't drift:
- cache privacy: a cookie-authed download salts its MediaAsset format_sig
  with the user id, so the authed asset is private and never cache-served
  to another user. The worker attaches a cookie ONLY when the asset it is
  downloading into IS that private, salted identity.
- host scope (SEC-12): a cookie is resolved only for a URL whose netloc
  the credential owns (netloc-scoped _host_is); a look-alike/unrelated
  host gets none.

cookies_enc is Fernet-encrypted at rest, only decrypted in-worker into a
0600 file under staging/.auth/ (survives the retry wipe + _produced_file
scan) and unlinked in finally. The blob is never returned by a route,
logged, audited, or written to a job's error/snapshot. cookiefile is set
after the adapter's extra merge so an adapter can't clobber it. Settings
UI is write-only (host + set/updated only, never echoed). New
download_credentials table + migration 0074; per-user routes; HU+EN i18n.

Tests: host-scope, cache-privacy (no cross-user serve), encrypt-at-rest,
write-only API, worker attach-gate; FE dom test for the write-only field.
This commit is contained in:
2026-08-28 03:53:04 +02:00
parent 5280949667
commit bc33b2ed16
21 changed files with 1030 additions and 13 deletions
@@ -17,5 +17,7 @@
"sync_pause": "Sync paused",
"sync_resume": "Sync resumed",
"job_run": "Scheduler job run",
"audit_clear": "Audit log cleared"
"audit_clear": "Audit log cleared",
"download_credential_set": "Download cookies set",
"download_credential_delete": "Download cookies removed"
}
+6 -1
View File
@@ -218,7 +218,7 @@
},
"login_required": {
"short": "Sign-in required",
"body": "This video needs a signed-in, age-verified, or members-only account to view, so it can't be downloaded here."
"body": "This video needs a signed-in account (age-restricted, members-only, or a private group) to view. Add your cookies for this site under Settings → Account → Download authentication to download it."
},
"unsupported_host": {
"short": "Site not supported",
@@ -253,6 +253,11 @@
"body": "Something went wrong while downloading this item. The technical details below may help."
}
}
},
"cookies": {
"empty": "Paste your cookies first.",
"format": "That doesn't look like a cookies.txt file (Netscape format).",
"generic": "Those cookies couldn't be saved."
}
}
}
@@ -104,6 +104,26 @@
"failed": "Couldn't sign out other sessions. Please try again."
}
},
"downloadAuth": {
"title": "Download authentication",
"intro": "Some sites (e.g. a private Facebook group you belong to) only let logged-in members view a video. Paste your browser cookies for a site here and your downloads from it will use your login. Cookies are encrypted, kept private to your account, and only ever sent to the matching site.",
"configured": "Set · updated {{when}}",
"notSet": "Not set",
"set": "Set cookies",
"update": "Update",
"remove": "Remove",
"save": "Save",
"cancel": "Cancel",
"saving": "Saving…",
"placeholder": "Paste the contents of your cookies.txt (Netscape format) here",
"help": "Export a cookies.txt for this site with a browser extension, then paste it here. It's write-only — we never show it back.",
"saved": "Cookies saved.",
"removed": "Cookies removed.",
"failed": "Couldn't save those cookies.",
"removeConfirmTitle": "Remove cookies?",
"removeConfirmBody": "Downloads from this site will stop using your login.",
"removeConfirmAction": "Remove"
},
"demo": {
"title": "Demo access",
"intro": "Emails here can enter the shared demo account straight from the login page — no Google sign-in. They just type the address into the field and are let in after a moment.",
@@ -17,5 +17,7 @@
"sync_pause": "Szinkron szüneteltetve",
"sync_resume": "Szinkron folytatva",
"job_run": "Ütemező job lefutott",
"audit_clear": "Napló ürítve"
"audit_clear": "Napló ürítve",
"download_credential_set": "Letöltési cookie-k beállítva",
"download_credential_delete": "Letöltési cookie-k eltávolítva"
}
+6 -1
View File
@@ -218,7 +218,7 @@
},
"login_required": {
"short": "Bejelentkezés szükséges",
"body": "Ehhez a videóhoz bejelentkezett, kor-ellenőrzött vagy tagsági fiók kell, ezért itt nem tölthető le."
"body": "Ehhez a videóhoz bejelentkezett fiók kell (korhatáros, csak tagoknak szóló vagy zárt csoport). Töltsd fel a cookie-kat ehhez az oldalhoz a Beállítások → Fiók → Letöltés-hitelesítés alatt a letöltéshez."
},
"unsupported_host": {
"short": "Nem támogatott oldal",
@@ -253,6 +253,11 @@
"body": "Valami hiba történt az elem letöltése közben. Az alábbi technikai részletek segíthetnek."
}
}
},
"cookies": {
"empty": "Előbb illeszd be a cookie-kat.",
"format": "Ez nem tűnik cookies.txt fájlnak (Netscape formátum).",
"generic": "A cookie-kat nem sikerült menteni."
}
}
}
@@ -104,6 +104,26 @@
"failed": "Nem sikerült kijelentkeztetni a többi munkamenetet. Próbáld újra."
}
},
"downloadAuth": {
"title": "Letöltés-hitelesítés",
"intro": "Egyes oldalak (pl. egy zárt Facebook-csoport, aminek tagja vagy) csak bejelentkezett tagoknak mutatják a videót. Illeszd be ide a böngésződ cookie-jait egy oldalhoz, és az onnan induló letöltések a te bejelentkezésedet használják. A cookie-k titkosítva, csak a te fiókodhoz kötve tárolódnak, és kizárólag az adott oldalnak küldjük el őket.",
"configured": "Beállítva · frissítve: {{when}}",
"notSet": "Nincs beállítva",
"set": "Cookie-k beállítása",
"update": "Frissítés",
"remove": "Eltávolítás",
"save": "Mentés",
"cancel": "Mégse",
"saving": "Mentés…",
"placeholder": "Illeszd be ide a cookies.txt tartalmát (Netscape formátum)",
"help": "Exportálj cookies.txt-t ehhez az oldalhoz egy böngészőbővítménnyel, majd illeszd be ide. Csak írható — sosem mutatjuk vissza.",
"saved": "Cookie-k mentve.",
"removed": "Cookie-k eltávolítva.",
"failed": "A cookie-kat nem sikerült menteni.",
"removeConfirmTitle": "Cookie-k eltávolítása?",
"removeConfirmBody": "Az erről az oldalról induló letöltések nem használják tovább a bejelentkezésedet.",
"removeConfirmAction": "Eltávolítás"
},
"demo": {
"title": "Demo hozzáférés",
"intro": "Az itt szereplő e-mailek a bejelentkező oldalról közvetlenül beléphetnek a közös demo fiókba — Google-bejelentkezés nélkül. Csak beírják a címet a mezőbe, és pár pillanat múlva bekerülnek.",