Three-tier access: Off → Demo → Showcase

Off: login required for everything

Demo: public gets Team UI + run modes + admin page (browse only)
  Blocked: /logs, /admin/monitor, /history, threat intel APIs,
  sentinel, wall-of-shame, meta-pipelines, self-reports, vectors

Showcase: public gets full read-only access to ALL pages
  Allowed: admin, monitor, logs, threat intel, enrichment,
  lab, history, self-analysis, meta-pipelines
  Blocked: config changes, bans, deletes, bulk operations

Admin (logged in): full access to everything always

SHOWCASE_ONLY_ROUTES set defines which pages/APIs are
blocked in basic demo but allowed in showcase mode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
root 2026-03-26 05:29:39 -05:00
parent ffd5e43709
commit eea8ff46db

View File

@ -126,19 +126,48 @@ def login_required(f):
return decorated return decorated
# Pages/APIs that require showcase mode (blocked in basic demo)
SHOWCASE_ONLY_ROUTES = {
"/logs", "/admin/monitor", "/history",
"/api/admin/logs", "/api/admin/monitor", "/api/admin/sentinel",
"/api/admin/security", "/api/admin/security/enrich", "/api/admin/wall-of-shame",
"/api/meta-pipelines", "/api/self-reports", "/api/self-analyze",
"/api/runs/vectors", "/api/runs/tags",
}
def admin_required(f): def admin_required(f):
@wraps(f) @wraps(f)
def decorated(*args, **kwargs): def decorated(*args, **kwargs):
# Demo/showcase mode: full read access to everything
if is_demo(): if is_demo():
path = request.path
is_showcase = _demo_mode.get("showcase", False)
# Demo mode (not showcase): only allow admin page itself (GET) for browsing
# Block deeper pages like logs, monitor, history
if not is_showcase:
# Check if this route needs showcase
for route in SHOWCASE_ONLY_ROUTES:
if path == route or path.startswith(route + "/"):
if not is_admin():
if path.startswith("/api/"):
return jsonify({"error": "showcase mode required", "demo": True}), 403
return redirect("/")
break
# GET requests: allow (admin page view in demo, everything in showcase)
if request.method == "GET": if request.method == "GET":
return f(*args, **kwargs) return f(*args, **kwargs)
# Allow specific read-like POSTs (enrichment, self-analysis, team runs)
if request.path in DEMO_ALLOWED_POSTS: # POSTs: allow read-like actions
if path in DEMO_ALLOWED_POSTS:
return f(*args, **kwargs) return f(*args, **kwargs)
# Block destructive writes
# Block destructive writes for non-admins
if not is_admin(): if not is_admin():
return jsonify({"error": "demo mode: read-only", "demo": True}), 403 return jsonify({"error": "demo mode: read-only", "demo": True}), 403
# Normal auth: require login + admin role
if not session.get("user_id"): if not session.get("user_id"):
if request.path.startswith("/api/"): if request.path.startswith("/api/"):
return jsonify({"error": "unauthorized"}), 401 return jsonify({"error": "unauthorized"}), 401
@ -3017,8 +3046,8 @@ ADMIN_HTML = r"""
<button class="btn btn-r" onclick="setDemoMode('off')">Off</button> <button class="btn btn-r" onclick="setDemoMode('off')">Off</button>
</div> </div>
</h3> </h3>
<p style="font-size:12px;color:var(--text2);margin-bottom:6px"><strong>Demo</strong> public can use Team UI and run modes. No admin access.</p> <p style="font-size:12px;color:var(--text2);margin-bottom:6px"><strong>Demo</strong> public can use Team UI, run modes, and browse the Admin panel. Cannot access Logs, Monitor, History, or deep admin features.</p>
<p style="font-size:12px;color:var(--text2);margin-bottom:10px"><strong>Showcase</strong> full read-only access to everything: Admin, Monitor, Logs, Threat Intel, Lab, History. Cannot change settings or delete data.</p> <p style="font-size:12px;color:var(--text2);margin-bottom:10px"><strong>Showcase</strong> full read-only access to everything: Admin, Monitor, Logs, Threat Intel, Lab, History. Can run enrichments and self-analysis. Cannot change settings or delete data. Use this for client demos.</p>
<div id="demo-status-admin" style="font-size:13px">Status: <strong style="color:var(--text2)">Off</strong></div> <div id="demo-status-admin" style="font-size:13px">Status: <strong style="color:var(--text2)">Off</strong></div>
</div> </div>
<div class="card"> <div class="card">