Skip to content

Accounts and Security

Iain Smith edited this page Sep 2, 2026 · 2 revisions

Accounts and Security

Sign-up

Email + display name → a short-lived verification link is emailed (or logged, with console) → on verify you're prompted to add a passkey. A password is optional; if you set one it's checked for length and against Have I Been Pwned (k-anonymity range query — the password never leaves your server).

Sign-in

  • Passkeys first — WebAuthn, via browser autofill where supported. Passkeys need HTTPS or a localhost origin; if the RP-ID / origin is misconfigured the error says so plainly. See Reverse Proxy and HTTPS.
  • Password fallback — Argon2id, then a TOTP step if 2FA is enabled.
  • Enumeration-resistant responses, lockout with exponential backoff, every attempt audit-logged.

Two-factor

Settings → Two-factor — TOTP with a QR enrolment and 10 single-use recovery codes.

Sessions

  • The access token lives in the SPA's memory for 15 minutes.
  • The refresh token is an opaque value in an HttpOnly; Secure; SameSite=Lax cookie scoped to /api/auth, 60-day sliding, rotated on every use with replay detection — a reused token revokes the whole chain.
  • Settings → Sessions lists your devices (one row per sign-in) and lets you revoke any of them, or all others at once.

Hardening

  • The app sends its own security response headers — CSP, X-Content-Type-Options, Referrer-Policy, X-Frame-Options, and HSTS on an https:// origin in env=prod (see Reverse Proxy and HTTPS).
  • User-supplied source/EPG URLs go through an SSRF guard that resolves the host, rejects private / loopback / link-local / CGNAT addresses, and pins the connection to the validated IP (no DNS-rebinding). Widen it deliberately with TVTIMES_FETCH_ALLOWLIST (Configuration).
  • XMLTV is parsed with entity expansion disabled (no billion-laughs / XXE), and a gzip feed's decompressed size is capped.

Multi-tenant model

One account is one tenant today, but tenant is modelled separately, so shared/household accounts are an additive change — not a migration. The watchlist is already per user for this reason.

Clone this wiki locally