@tideworthy/tidysites-platform 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0
4
+
5
+ - `tidyPlatform()` registers a middleware: with a `TIDY_FRAME_ANCESTORS` variable on the site (space-separated https origins), responses carry `Content-Security-Policy: frame-ancestors 'self' <origins>` in place of EmDash's `X-Frame-Options: SAMEORIGIN`, so the platform can embed the staging preview. Inert without the variable. New export `@tideworthy/tidysites-platform/middleware`.
6
+
3
7
  ## 0.2.1
4
8
 
5
9
  - Fix: `POST /_tidy/fill` failed every upsert with "internal error" because the content-only seed document carried a numeric version; the seed format's version is the string "1". The document is now validated before it is applied, so a shape problem answers 400 with the validator's paths. 0.2.0's fill route is unusable for writes; removals worked.
package/README.md CHANGED
@@ -39,6 +39,10 @@ The worker entry stays EmDash's own:
39
39
  export { default, PluginBridge } from "@emdash-cms/cloudflare/worker";
40
40
  ```
41
41
 
42
+ ## Framing the staging preview
43
+
44
+ EmDash answers every page with `X-Frame-Options: SAMEORIGIN` unless a Content-Security-Policy is present. `tidyPlatform()` registers a middleware that, when the site carries a `TIDY_FRAME_ANCESTORS` variable (space-separated https origins), replaces that header with `Content-Security-Policy: frame-ancestors 'self' <origins>` so the platform's website page can embed the site. Without the variable nothing changes.
45
+
42
46
  ## Bindings the routes read
43
47
 
44
48
  | Binding | Type | Purpose |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tideworthy/tidysites-platform",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "The Tidysites platform integration for EmDash sites: bootstrap, sessions, maintenance, health, and publish hooks under /_tidy/*.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -18,6 +18,7 @@
18
18
  "exports": {
19
19
  ".": "./src/index.ts",
20
20
  "./plugin": "./src/plugin.ts",
21
+ "./middleware": "./src/middleware.ts",
21
22
  "./client": "./src/client.ts",
22
23
  "./routes/bootstrap": "./src/routes/bootstrap.ts",
23
24
  "./routes/session": "./src/routes/session.ts",
package/src/index.ts CHANGED
@@ -31,13 +31,18 @@ export const PLATFORM_ROUTES = [
31
31
  { pattern: "/_tidy/maintenance", entrypoint: `${PACKAGE}/routes/maintenance` }
32
32
  ] as const;
33
33
 
34
- /** Astro integration: registers every platform route as a server route. */
34
+ /**
35
+ * Astro integration: registers every platform route as a server route and the
36
+ * framing middleware (a `frame-ancestors` policy from TIDY_FRAME_ANCESTORS so
37
+ * the platform can embed the staging preview; inert without the variable).
38
+ */
35
39
  export function tidyPlatform(): AstroIntegration {
36
40
  return {
37
41
  name: PACKAGE,
38
42
  hooks: {
39
- "astro:config:setup": ({ injectRoute }) => {
43
+ "astro:config:setup": ({ injectRoute, addMiddleware }) => {
40
44
  for (const route of PLATFORM_ROUTES) injectRoute({ pattern: route.pattern, entrypoint: route.entrypoint, prerender: false });
45
+ addMiddleware({ entrypoint: `${PACKAGE}/middleware`, order: "pre" });
41
46
  }
42
47
  }
43
48
  };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Framing policy. EmDash's middleware sets `X-Frame-Options: SAMEORIGIN` on
3
+ * every response that has no Content-Security-Policy, which keeps the
4
+ * platform's website page from embedding the staging preview. When the site
5
+ * carries a TIDY_FRAME_ANCESTORS variable (space-separated origins), responses
6
+ * get a `frame-ancestors` policy naming them instead. Nothing changes without
7
+ * the variable.
8
+ */
9
+ const ORIGIN = /^https:\/\/[a-z0-9.-]+(?::\d+)?$/i;
10
+
11
+ /** The origins allowed to frame the site, from the variable's value; invalid entries are dropped. */
12
+ export function frameAncestors(value: unknown): string[] {
13
+ if (typeof value !== "string") return [];
14
+ return [...new Set(value.split(/[\s,]+/).map((s) => s.trim()).filter((s) => ORIGIN.test(s)))];
15
+ }
16
+
17
+ /** Apply the policy to a response's headers. Returns true when something changed. */
18
+ export function applyFramePolicy(headers: Headers, ancestors: string[]): boolean {
19
+ if (ancestors.length === 0) return false;
20
+ const existing = headers.get("Content-Security-Policy");
21
+ if (existing && /frame-ancestors/i.test(existing)) return false;
22
+ const directive = `frame-ancestors 'self' ${ancestors.join(" ")}`;
23
+ headers.set("Content-Security-Policy", existing ? `${existing.replace(/;?\s*$/, "")}; ${directive}` : directive);
24
+ headers.delete("X-Frame-Options");
25
+ return true;
26
+ }
@@ -0,0 +1,20 @@
1
+ /** Astro middleware registered by tidyPlatform(): the framing policy from TIDY_FRAME_ANCESTORS. */
2
+ import type { MiddlewareHandler } from "astro";
3
+ import { workerEnv } from "./lib/env";
4
+ import { applyFramePolicy, frameAncestors } from "./lib/frame";
5
+
6
+ export const onRequest: MiddlewareHandler = async (_context, next) => {
7
+ const response = await next();
8
+ const env = await workerEnv();
9
+ const ancestors = frameAncestors(env?.TIDY_FRAME_ANCESTORS);
10
+ if (ancestors.length === 0) return response;
11
+ // Response headers can be immutable (static assets, redirects); rebuild when so.
12
+ try {
13
+ applyFramePolicy(response.headers, ancestors);
14
+ return response;
15
+ } catch {
16
+ const copy = new Response(response.body, response);
17
+ applyFramePolicy(copy.headers, ancestors);
18
+ return copy;
19
+ }
20
+ };
package/src/version.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  /** Kept in step with package.json by the release checklist in MAINTAINING.md. */
2
- export const VERSION = "0.2.1";
2
+ export const VERSION = "0.3.0";
3
3
  export const PLUGIN_ID = "tidy-platform";