Extend the public /api/content/:id/file gate to unlock content referenced
by widgets (previously only playlists unlocked it), so device browsers
and kiosk iframes can fetch logos and background images that widgets
embed.
Security: scope the widget lookup to the content owner's widgets only
(w.user_id = content.user_id). Otherwise a user could unlock another
user's content file by creating their own widget whose config references
the victim's content UUID. The pre-existing playlist gate has the same
shape and is left for a separate fix.
Also adds a 30/min rate limit on POST /api/widgets/preview, which
inlines user content as base64 and is memory-intensive.
Perf note: the widgets.config LIKE scan is O(n). Fine at current scale;
revisit with a content_widget_refs join table if the widget table grows.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Player polls /api/version every 30s and reloads if the hash changes.
Server hash now includes player/index.html and sw.js so player code
updates are detected without requiring a hard refresh.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add requireGroupOwnership middleware to all group endpoints
- Whitelist allowed command types (screen_on/off, launch, update, reboot, shutdown)
- Validate color format as #RRGGBB
- Escape all user-controlled strings (device/group names, emails) in dashboard HTML
- Restrict trust proxy to first hop only (prevents IP spoofing + rate limit bypass)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Dashboard now organizes devices by group with colored section headers
- Group command endpoint (POST /groups/:id/command) sends to all members
- Manage modal with multi-group confirmation prompt
- Destructive commands (reboot/shutdown) require confirmation
- Ungrouped devices shown separately at bottom
- trust proxy + X-Forwarded-For for real client IPs behind Nginx
- Hide Android-only telemetry (battery/storage/RAM/CPU/WiFi) for web players
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Make assignments.content_id nullable so widgets can be assigned to playlists
- Fix designer publish to use vw units matching preview (was hardcoded px)
- Add px-to-vw conversion in text widget renderer for backward compat
- Fix webpage widget zoom scaling
- Add widget rendering support in fullscreen player mode
- Set no-cache headers on JS/CSS/HTML for instant updates (ETag/304)
- Set 30-day cache on media files and uploaded content for Cloudflare
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Push playlist updates to devices instantly via WebSocket on all
assignment mutations (add, update, delete, reorder, copy)
- Fix YouTube videos skipping early: remove duration_sec timeout (was
defaulting to 10s), use generation counter to ignore stale player
callbacks, disable YouTube loop param for multi-item playlists
- Auto-fetch YouTube video title via oEmbed API when no name provided
- Show actual video duration in M:SS format in playlist instead of
misleading assignment duration_sec
- Pre-fill server URL from origin on web player setup
- Bump playlist poll interval to 5min (fallback only, push is primary)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ScreenTinker - open source digital signage management software.
MIT License, all features included, no license gates.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>