"""
rndrSBC - Production Web Management Dashboard & Secure API
Hardened server with first-run authentication, session validation, path traversal protection,
safe command execution, quiet hours scheduling, and multi-playlist management.
"""
import os
import sys
import json
import time
import io
import secrets
import logging
import subprocess
import threading
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
import urllib.parse
from PIL import Image
# Secure password hashing from Werkzeug
from werkzeug.security import generate_password_hash, check_password_hash
from core.paths import CONFIG_PATH, resolve
# Onboarding: QR claim-token flow + AP-mode provisioning
from server.onboarding import (
claim_url_for_token,
validate_claim_token,
consume_claim_token,
issue_claim_token,
invalidate_unclaimed_tokens,
onboarding_state,
ap_manager as onboarding_ap_manager,
)
logger = logging.getLogger("rndrSBC.server")
# In-memory active session tokens: {session_token: {"created_at": float, "user": "admin"}}
ACTIVE_SESSIONS: dict[str, dict] = {}
# Sessions are also persisted to CONFIG_PATH so a service restart does not
# log every client out (otherwise stats / OTA / photos / dev-studio all 401
# until a manual re-login).
_SESSION_LOCK = threading.Lock()
SESSION_TTL_SECS = 86400 * 7 # 7 days
def _load_sessions(force: bool = False) -> None:
"""Populate ACTIVE_SESSIONS from the persisted copy in CONFIG_PATH."""
if not force and ACTIVE_SESSIONS:
return
try:
if os.path.exists(CONFIG_PATH):
with open(CONFIG_PATH, "r") as f:
cfg = json.load(f)
saved = cfg.get("admin_sessions") or {}
now = time.time()
for tok, meta in saved.items():
if now - float(meta.get("created_at", 0)) < SESSION_TTL_SECS:
ACTIVE_SESSIONS[tok] = meta
except Exception:
logger.debug("Could not load persisted admin sessions", exc_info=True)
def _save_sessions() -> None:
"""Persist ACTIVE_SESSIONS into CONFIG_PATH (best-effort, atomic)."""
try:
with _SESSION_LOCK:
cfg = {}
if os.path.exists(CONFIG_PATH):
with open(CONFIG_PATH, "r") as f:
cfg = json.load(f)
cfg["admin_sessions"] = {
tok: m for tok, m in ACTIVE_SESSIONS.items()
}
tmp = CONFIG_PATH + ".tmp"
with open(tmp, "w") as f:
json.dump(cfg, f, indent=2)
os.replace(tmp, CONFIG_PATH)
except Exception:
logger.debug("Could not persist admin sessions", exc_info=True)
DASHBOARD_HTML = """
rndrSBC Management Portal
rS
rndrSBC Portal
E-Paper Management
🔒
Administrator sign-in required
This page is locked behind admin authentication.
Display Hardware Settings
Configure physical SPI screen drivers and resolution targets
0.1–1.0; higher = more intense color on Inky panels
Quiet Hours & Timezone
Suspend physical e-Paper refreshes during sleeping hours
Enable Night Quiet Mode
Puts display to sleep and prevents night flashing
NEXT pin 5
PREV pin 6
TOGGLE QUIET pin 12
Wire buttons to GND; cycle widgets, go back, or pause/restart refreshes. Disabled automatically on simulator/laptop (no GPIO).
🔄 Display Transitions
Auto: B/W e-paper, LCD & OLED partial-refresh automatically; 7-color/BWR panels always full-refresh. Full: forces full frame every refresh.
🔒 Admin Security & Password
Update your dashboard administrator password
🔒
Welcome to rndrSBC
Please establish an administrator password to secure your display management portal.