siftlode/backend/app/youtube
npeter83 e92751dbce fix(auth): security hardening — token encryption, timing, adoption + isolation
From the auth /security-review (contained fixes; architectural SA3 proxy-trust +
SA4 session-revocation deferred to the user):
- SB1: encrypt the OAuth access_token at rest (was plaintext while refresh_token
  was encrypted) — a ~1h Google bearer credential. New security.decrypt_optional()
  falls back to a refresh for legacy plaintext tokens; column is unbounded String.
  E2E-verified: token refreshed → stored as Fernet ciphertext → 319-subscription
  YouTube sync succeeded.
- SA5: password_login is no longer a timing/enumeration oracle — it always runs
  argon2 (against a decoy hash for unknown/passwordless emails), so response time
  can't reveal whether an account exists.
- SB2: Google login only ADOPTS+activates a pre-existing password account when
  Google actually attests email_verified (defense-in-depth against takeover); and
  the email sync won't overwrite with a value another account owns (avoids a 500).
- demo_login clears the wallet first (demo can't switch to / act as a real account
  via the multi-account header); switch_account rejects a suspended target (which
  would otherwise clear the whole session on the next request).

Re-review clean; ruff clean; localdev boots; YouTube auth path E2E-verified.
2026-07-11 21:26:39 +02:00
..
__init__.py feat: M2 (part 1) — subscription import, YouTube client, quota guard 2026-06-11 01:22:07 +02:00
client.py fix(auth): security hardening — token encryption, timing, adoption + isolation 2026-07-11 21:26:39 +02:00
rss.py chore: rebrand Subfeed -> Siftlode 2026-06-14 04:40:22 +02:00
search_scrape.py feat(search): zero-quota InnerTube scrape source + search_source toggle 2026-06-29 22:29:54 +02:00
shorts.py fix: address reader-UI feedback (shorts, search, tags, login, UX) 2026-06-11 03:07:49 +02:00