New /embed/[id] route renders notebooks in a read-only, chromeless layout for iframe embedding. Supports light/dark themes via URL param and postMessage from the parent window. - EmbedLayout: minimal HTML shell, no navbar/footer - EmbedViewer: fetches notebook, runs simulations, syncs theme - EmbedCell: read-only markdown + SPICE cell renderer - SpiceEditor: added readOnly prop (EditorState.readOnly + editable.of) - embed-theme.css: light mode CSS variable overrides - Astro middleware: CSP frame-ancestors on /embed/* routes - Backend: env-configurable CORS origins, CSP header middleware Security hardening from review: - postMessage origin validation (ALLOWED_MESSAGE_ORIGINS) - markdown XSS fix: isSafeUrl() blocks javascript: URIs in links - escapeHtml now covers single quotes - Notebook ID validated against /^[a-zA-Z0-9_-]+$/ - Theme param normalized at Astro boundary - classList.remove/add instead of className stomping
25 lines
718 B
TypeScript
25 lines
718 B
TypeScript
import { defineMiddleware } from 'astro:middleware';
|
|
|
|
// CSP frame-ancestors: controls which origins can embed this site in an iframe.
|
|
// Only applied to /embed/* routes (the main app doesn't need to be framed).
|
|
const FRAME_ANCESTORS = "'self' https://forrest.warehack.ing";
|
|
|
|
export const onRequest = defineMiddleware(async ({ url }, next) => {
|
|
const response = await next();
|
|
|
|
if (url.pathname.startsWith('/embed/')) {
|
|
response.headers.set(
|
|
'Content-Security-Policy',
|
|
`frame-ancestors ${FRAME_ANCESTORS}`,
|
|
);
|
|
} else {
|
|
// Prevent framing of the main app entirely
|
|
response.headers.set(
|
|
'Content-Security-Policy',
|
|
"frame-ancestors 'self'",
|
|
);
|
|
}
|
|
|
|
return response;
|
|
});
|