Multi-display campaigns, secrets management, and admin UI redesign#1
Open
christian-andersson wants to merge 90 commits intomainfrom
Open
Multi-display campaigns, secrets management, and admin UI redesign#1christian-andersson wants to merge 90 commits intomainfrom
christian-andersson wants to merge 90 commits intomainfrom
Conversation
Add dynamodb backend rename the register endpoint to a ping endpoint Create new registration endpoint Rewrite the client to be more prettier and also show status and last seen
Introduce two new security middleware layers: - XSS protection: input sanitization, output encoding, CSP headers, field-specific sanitization rules - Tenant authorization: role-based access control (Owner/Admin/Member), cached membership validation with 5-min TTL Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Apply tenant authorization middleware to all tenant-specific routes (devices, playlists, playlist groups, tenants) - Add role-based guards: owner-only delete, admin-only invite/role change - Gate debug endpoints behind NODE_ENV === 'development' - Add secure session secret validation (reject weak defaults in production) - Integrate helmet, XSS sanitization, and output encoding into server - Enhance input validation with XSS protection in validators and controllers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Server: add helmet for security headers Client: add @babel/plugin-proposal-private-property-in-object as explicit devDependency to fix deprecation warning Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Update CLAUDE.md with session security, XSS, and tenant auth guidelines - Add server/SECURITY-SESSION.md for session configuration reference - Add server/TENANT-AUTHORIZATION.md for tenant auth architecture - Add server/XSS-PROTECTION.md for XSS prevention documentation - Add DEPENDENCY-SECURITY.md for dependency security policy - Add server/.env.example as configuration template (no real secrets) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- actions/checkout: v4 -> v6.0.2 (SHA-pinned) - docker/setup-buildx-action: v1 -> v3.12.0 (SHA-pinned) - docker/login-action: v1 -> v3.7.0 (SHA-pinned) - docker/build-push-action: v2 -> v6.19.2 (SHA-pinned) SHA-pinning prevents supply chain attacks via mutable version tags. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prevent internal audit/task files and local Claude settings from being tracked in version control. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Ownership is now determined solely by tenant_members.role = 'owner' instead of a dedicated ownerId column on the tenants table. This enables multiple owners and a cleaner permission model: - Owner: full control, can manage all roles, delete empty orgs - Admin: can manage admins and members, cannot touch owners - Member: view only
Previously, the user account was created in completeRegistration before the passkey step. If passkey creation failed, the user was left with no way to log in. Now completeRegistration only stores pending data in the session and returns WebAuthn options directly. The actual user record, authenticator, and tenant setup are created atomically in the new /webauthn/register-new endpoint after passkey verification succeeds.
…rving - Consume email verification tokens on first use to prevent reuse - Use configured env.ORIGIN for verification links instead of attacker-controlled Origin/Referer headers - Canonicalize static file paths with Deno.realPath and prefix check to prevent directory traversal
TypeScript-based provisioner that SSHs into a Raspberry Pi and configures it as a digital signage device. Follows the same ask/save/do step pattern as the original bash installer but written in Deno/TypeScript. Steps: init, OS upgrade, X11/Chromium kiosk, WiFi, OpenFortiVPN, Deno install, signage-client deploy with systemd service. Config answers are saved locally for batch-provisioning multiple devices. Usage: cd signage-client && deno task provision --host=192.168.1.50
Auto-detect Raspberry Pi generation, HDMI ports, boot config path, KMS mode, and WiFi availability via SSH on connect. Steps now adapt: - Boot config: /boot/config.txt (Pi 3/4) vs /boot/firmware/config.txt (Pi 5) - KMS overlay: fake KMS for Pi 3, full KMS for Pi 4/5 - Display: single xrandr for Pi 3 (1 HDMI), dual for Pi 4/5 (2 HDMI) - WiFi: skip entirely if no wlan0 adapter detected (e.g. Pi 3 Model B)
Detect connected displays at runtime and report to server via heartbeat: - Linux: DRM subsystem (/sys/class/drm/) with xrandr fallback - macOS: system_profiler SPDisplaysDataType - Windows: PowerShell WMI with Win32_DesktopMonitor fallback Also adds Windows support to Chrome launcher (paths, where command, temp dir) and display power control (SendMessage API via PowerShell).
Add device_display_campaigns junction table to support assigning different campaigns to each display on a multi-monitor device (e.g. Pi 4/5 with 2 HDMI ports). Remove campaignId column from devices table. Update API route to POST .../displays/:displayName/campaign, return per-display data from GET /api/device/content, and track multiple campaign IDs per device in WebSocket manager. Admin UI configure modal now shows one campaign dropdown per detected display instead of a single global dropdown.
Previously the token was deleted immediately when the verify-email endpoint was hit, before passkey setup. React StrictMode double-fires effects in dev, causing the second call to find no token and show an error. Move deletion to verifyNewRegistration so the token remains valid through the entire registration flow.
ContentManager now handles the new multi-display response format from the server, storing campaigns keyed by display name. Fix player showing old device ID on status page after re-registration by passing player to handleAuthFailure and calling setDeviceInfo with the new credentials.
Secrets store sensitive values (API tokens, passwords) that admins can manage per organization. Values are encrypted at rest using AES-256-GCM with a server-side ENCRYPTION_KEY. Only tenant owners and admins can create, update, or delete secrets. Includes encryption utility, tenant_secrets table with RLS, full CRUD API behind requireTenantAdmin middleware, and admin UI page.
Replace inconsistent button patterns (emoji, text-only, nested divs) with uniform btn btn-info/danger btn-sm + SVG icons. Move shared action-buttons-cell style to common.css. Also move Organizations "Create" button to header row matching other pages.
Restructure Playlists page to use sidebar + details layout matching Organizations page. Left panel lists playlists, clicking one shows its items on the right with header actions. Also: auto-select first item on Organizations page, fix shared error state bleeding from modals to page background, fix long names overflowing sidebar and details panel, use action-buttons-cell consistently on Organizations header, and deduplicate identical display names on macOS (e.g. two "LG HDR 4K" → "LG HDR 4K (1/2)").
Replace single-Chrome approach with multi-display architecture. Each connected display gets its own Chrome instance (positioned via --window-position from screen coordinates), CDP connection, Player, and HealthMonitor. macOS display detection now uses Swift/NSScreen for coordinates and EDID hardware IDs (vendor:model:serial). Falls back to system_profiler. Player logs now include display name for easier debugging. Initial heartbeat sent immediately on startup so server sees displays before content fetch.
Add hardware_id column to device_display_campaigns so campaign assignments survive display name changes and cable swaps. Server stores EDID hardware ID (vendor:model:serial) from the device's reported displays when assigning campaigns, and keys content response by hardware ID. Client matches by hardware ID first, falling back to display name for backward compatibility.
New DELETE /api/device/tenant/:tenantId/devices/:deviceId/campaigns endpoint clears all display campaign assignments for a device. Admin UI configure modal shows a "Clear All" button for quick cleanup of stale assignments after display name changes.
There was a problem hiding this comment.
CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.
kderholtvisma
approved these changes
Mar 16, 2026
SagenKoder
approved these changes
Mar 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Multi-display campaign assignment with per-monitor Chrome instances, encrypted secrets management, and comprehensive admin UI improvements.
Multi-Display Support
campaignIdon devices withdevice_display_campaignsjunction table — each display gets its own campaignPer-Item Cookies & Headers via CDP
Network.setCookieandNetwork.setExtraHTTPHeadersapplied before each navigationEncrypted Tenant Secrets
tenant_secretstable with AES-256-GCM encrypted values (ENCRYPTION_KEY env var)Auth & UI Fixes
Test plan
🤖 Generated with Claude Code