From c76e3f86a89e031f9f6e7929c7c0c309e21a10ff Mon Sep 17 00:00:00 2001 From: "Junv (via Hermes)" Date: Sun, 14 Jun 2026 11:43:33 +1000 Subject: [PATCH] fix(demo-service): drafts accessible when authenticated - New /site-content endpoint serves files with visibility+auth check - All paths proxied through API (not Caddy file_server) - Drafts: 401 canvas page (no auth) or 200 content (with auth) - Nonexistent: 404 canvas page - Preserves proper HTTP status codes --- home-apps/demo-service/Caddyfile | 19 ++++++++--------- home-apps/demo-service/service.py | 34 ++++++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/home-apps/demo-service/Caddyfile b/home-apps/demo-service/Caddyfile index e66439e..d68cbed 100644 --- a/home-apps/demo-service/Caddyfile +++ b/home-apps/demo-service/Caddyfile @@ -33,16 +33,15 @@ reverse_proxy localhost:3000 } - # Root → redirect to admin - handle / { - reverse_proxy localhost:3000 - } - - # Static demo sites — public, no auth - handle_path /* { - root * /data/demos - file_server { - index index.html + # ── Site content: API handles visibility + auth ── + # Public sites served without auth; drafts require login + handle { + rewrite * /site-content{uri} + reverse_proxy localhost:3000 { + header_up Host {host} + header_up X-Forwarded-For {remote_host} + header_up X-Forwarded-Proto https + header_up Cookie {http.request.header.Cookie} } } diff --git a/home-apps/demo-service/service.py b/home-apps/demo-service/service.py index 12c47b0..42c65be 100644 --- a/home-apps/demo-service/service.py +++ b/home-apps/demo-service/service.py @@ -13,7 +13,7 @@ from pathlib import Path from typing import Optional from fastapi import FastAPI, Request, HTTPException, UploadFile, File, Form -from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse +from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse, FileResponse from pydantic import BaseModel DEMO_ROOT = Path(os.environ.get("DEMO_ROOT", "/data/demos")) @@ -134,6 +134,38 @@ def error_page(): """Serve the canvas error page for any HTTP error.""" return HTMLResponse(AUTH_REQUIRED_HTML) +@app.get("/site-content", response_class=FileResponse) +@app.get("/site-content/{path:path}") +async def site_content(path: str = "", request: Request = None): + """Serve site files. Drafts require auth.""" + if not path: + raise HTTPException(404) + parts = path.split("/", 1) + site_name = parts[0] + sub = parts[1] if len(parts) > 1 else "" + if not site_name or not site_exists(site_name): + return HTMLResponse(AUTH_REQUIRED_HTML, status_code=404) + visible = is_site_visible(site_name) + if not visible: + if not check_auth(request): + return HTMLResponse(AUTH_REQUIRED_HTML, status_code=401) + site_dir = DEMO_ROOT / site_name + target = (site_dir / sub) if sub else (site_dir / "index.html") + if not target.exists(): + if not sub or sub == "index.html": + for fb in [".index.html.draft", ".index.html.bak"]: + p = site_dir / fb + if p.exists(): + target = p + break + if not target.exists(): + return HTMLResponse(AUTH_REQUIRED_HTML, status_code=404) + if target.is_dir(): + target = target / "index.html" + if not target.exists(): + return HTMLResponse(AUTH_REQUIRED_HTML, status_code=404) + return FileResponse(target) + @app.get("/api/sites") def list_sites(request: Request): user = require_auth(request)