feat(hosting): signed preview URLs for POC sites

Replace the standalone POC viewer (duplicate rendering) with signed
JWT preview tokens that bypass StorefrontAccessMiddleware:

Architecture:
1. Admin clicks Preview → route generates signed JWT
2. Redirects to /storefront/{subdomain}/homepage?_preview=token
3. Middleware validates token signature + expiry + store_id
4. Sets request.state.is_preview = True, skips subscription check
5. Full storefront renders with HostWizard preview banner injected

New files:
- app/core/preview_token.py: create_preview_token/verify_preview_token

Changes:
- middleware/storefront_access.py: preview token bypass before sub check
- storefront/base.html: preview banner injection via is_preview state
- hosting/routes/pages/public.py: redirect with signed token (was direct render)
- hosting/routes/api/admin_sites.py: GET /sites/{id}/preview-url endpoint

Removed:
- hosting/templates/hosting/public/poc-viewer.html (replaced by storefront)

Benefits: one rendering path, all section types work, shareable 24h links.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-02 22:41:34 +02:00
parent e492e5f71c
commit cff0af31be
6 changed files with 139 additions and 244 deletions

View File

@@ -128,6 +128,21 @@ class StorefrontAccessMiddleware(BaseHTTPMiddleware):
store = getattr(request.state, "store", None)
# Case 0: Preview token bypass (POC site previews)
preview_token = request.query_params.get("_preview")
if preview_token and store:
from app.core.preview_token import verify_preview_token
if verify_preview_token(preview_token, store.id):
request.state.is_preview = True
request.state.subscription = None
request.state.subscription_tier = None
logger.info(
"[STOREFRONT_ACCESS] Preview token valid for store '%s'",
store.subdomain,
)
return await call_next(request)
# Case 1: No store detected at all
if not store:
return self._render_unavailable(request, "not_found")