feat(demo): scheduled demo-account reset (5c)

A security audit of every mutating endpoint found no isolation gaps — all routes
scope by current_user, admin routes are gated, and the demo account is sandboxed
in its own user_id space (can't reach others' data, escalate, or hit admin
routes). The remaining demo concern is communal pollution of its own shared state,
so add an automatic reset: a new 'demo_reset' scheduler job (admin-tunable
interval, default 12h) reuses the manual reset logic, and no-ops without ever
creating a demo account if none exists. Verified: a triggered run wiped the demo's
video states and re-seeded its sample playlists.
This commit is contained in:
npeter83 2026-06-19 15:25:13 +02:00
parent 1f3d2b6b58
commit c70ca495cb
6 changed files with 39 additions and 12 deletions

View file

@ -56,6 +56,7 @@ JOB_INTERVALS: dict[str, int] = {
"subscriptions": settings.subscriptions_resync_minutes,
"playlist_sync": settings.playlist_sync_minutes,
"maintenance": settings.maintenance_interval_minutes,
"demo_reset": settings.demo_reset_minutes,
}
# Sane bounds for an admin-set interval (minutes).
@ -253,6 +254,20 @@ def _maintenance_job() -> None:
_job("maintenance", work)
def _demo_reset_job() -> None:
# Auto-clean the shared demo sandbox. No-op (and never creates one) if there's no demo
# account. Lazy import avoids a routes<->scheduler import cycle.
def work(db):
from app.models import User
demo = db.query(User).filter(User.is_demo.is_(True)).one_or_none()
if demo is None:
return {"skipped": "no demo account"}
from app.routes.admin import reset_demo_state
return {"playlists_seeded": reset_demo_state(db, demo)}
_job("demo_reset", work)
# job_id -> wrapper. The single source of truth for which jobs exist and how to run one,
# shared by start_scheduler (recurring registration) and trigger_job (manual "run now").
JOB_FUNCS: dict[str, Callable[[], None]] = {
@ -264,6 +279,7 @@ JOB_FUNCS: dict[str, Callable[[], None]] = {
"subscriptions": _subscriptions_job,
"playlist_sync": _playlist_sync_job,
"maintenance": _maintenance_job,
"demo_reset": _demo_reset_job,
}