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
This commit is contained in:
Junv (via Hermes)
2026-06-14 11:43:33 +10:00
parent a8a09c6675
commit c76e3f86a8
2 changed files with 42 additions and 11 deletions
+9 -10
View File
@@ -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}
}
}
+33 -1
View File
@@ -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)