@decocms/nextjs 7.28.0-beta.3 → 7.28.0-beta.5
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/package.json +3 -3
- package/src/draftMiddleware.test.ts +8 -0
- package/src/draftMiddleware.ts +14 -6
- package/src/setup.ts +23 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/nextjs",
|
|
3
|
-
"version": "7.28.0-beta.
|
|
3
|
+
"version": "7.28.0-beta.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=24"
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"lint:unused": "knip"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@decocms/blocks": "7.28.0-beta.
|
|
33
|
-
"@decocms/blocks-admin": "7.28.0-beta.
|
|
32
|
+
"@decocms/blocks": "7.28.0-beta.5",
|
|
33
|
+
"@decocms/blocks-admin": "7.28.0-beta.5"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"next": ">=15.0.0",
|
|
@@ -32,6 +32,14 @@ describe("prepareDraft", () => {
|
|
|
32
32
|
);
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
+
it("passes through when no env override is set — the page gate is authoritative", () => {
|
|
36
|
+
// The site-block hosts live in the server-components graph; middleware
|
|
37
|
+
// can't see them, so without the env kill switch it must not block.
|
|
38
|
+
delete process.env.DECO_ALLOWED_PREVIEW_HOSTS;
|
|
39
|
+
const req = request("https://anything.example/p?__draft=abc.localhost@v1");
|
|
40
|
+
expect(prepareDraft(req).pointer).toBe("abc.localhost@v1");
|
|
41
|
+
});
|
|
42
|
+
|
|
35
43
|
it("is inert on a host outside the allowlist — the production domain", () => {
|
|
36
44
|
// Same build, different Host: no cookie, no rewrite, nothing touched.
|
|
37
45
|
const req = request("https://fila.com.br/p?__draft=abc@v1");
|
package/src/draftMiddleware.ts
CHANGED
|
@@ -40,12 +40,20 @@ import {
|
|
|
40
40
|
const INERT: DraftMiddlewareDecision = { pointer: null, setCookie: null, clearCookie: false };
|
|
41
41
|
|
|
42
42
|
export function prepareDraft(request: NextRequest): DraftMiddlewareDecision {
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
// The AUTHORITATIVE host gate lives page-side (`ensureDraft`): the site
|
|
44
|
+
// block's `previewHosts` are installed in the server-components graph, and
|
|
45
|
+
// middleware runs in a separate module graph/runtime that never sees them.
|
|
46
|
+
// Middleware therefore hard-gates only on the env override — keeping
|
|
47
|
+
// DECO_ALLOWED_PREVIEW_HOSTS a full kill switch — and otherwise passes the
|
|
48
|
+
// decision through. Worst case on a non-preview host is a cookie the
|
|
49
|
+
// page-side gate then ignores; drafts never render there.
|
|
50
|
+
if (process.env.DECO_ALLOWED_PREVIEW_HOSTS) {
|
|
51
|
+
const host =
|
|
52
|
+
request.headers.get("x-forwarded-host") ??
|
|
53
|
+
request.headers.get("host") ??
|
|
54
|
+
request.nextUrl.host;
|
|
55
|
+
if (!isDraftHostAllowed(host)) return INERT;
|
|
56
|
+
}
|
|
49
57
|
return decideDraft(new URL(request.url), request.cookies.get(DRAFT_COOKIE)?.value ?? null);
|
|
50
58
|
}
|
|
51
59
|
|
package/src/setup.ts
CHANGED
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
* deploys must ship the directory alongside the server bundle).
|
|
39
39
|
*/
|
|
40
40
|
import type { ApplySectionConventionsInput } from "@decocms/blocks/cms";
|
|
41
|
-
import { applySectionConventions, loadBlocks } from "@decocms/blocks/cms";
|
|
41
|
+
import { applySectionConventions, loadBlocks, setDraftPreviewHosts } from "@decocms/blocks/cms";
|
|
42
42
|
import { loadDecofileDirectory } from "@decocms/blocks/cms/loadDecofileDirectory";
|
|
43
43
|
import { createSiteSetup, type SiteSetupOptions } from "@decocms/blocks/setup";
|
|
44
44
|
|
|
@@ -110,9 +110,29 @@ export interface NextSetupOptions {
|
|
|
110
110
|
* instead, nothing imports the JSON, so edits invalidate nothing and dev
|
|
111
111
|
* serves stale content until a full restart.
|
|
112
112
|
*/
|
|
113
|
+
/**
|
|
114
|
+
* Install the site block's `previewHosts` as the draft-preview allowlist.
|
|
115
|
+
*
|
|
116
|
+
* Reads the BASE blocks handed to setup — never `loadBlocks()` at request
|
|
117
|
+
* time, where the draft override is merged in: an allowlist readable through
|
|
118
|
+
* the override could be rewritten by the very draft it gates.
|
|
119
|
+
* `DECO_ALLOWED_PREVIEW_HOSTS` remains an operational override that replaces
|
|
120
|
+
* this list when set.
|
|
121
|
+
*/
|
|
122
|
+
function installPreviewHosts(blocks: Record<string, unknown> | undefined): void {
|
|
123
|
+
const site = blocks?.site as { previewHosts?: unknown } | undefined;
|
|
124
|
+
if (Array.isArray(site?.previewHosts)) setDraftPreviewHosts(site.previewHosts);
|
|
125
|
+
}
|
|
126
|
+
|
|
113
127
|
export function createNextSetup(options: NextSetupOptions): () => Promise<void> {
|
|
114
128
|
let setupPromise: Promise<void> | null = null;
|
|
115
129
|
|
|
130
|
+
// Draft preview opt-in from the repo: read SYNCHRONOUSLY at createNextSetup
|
|
131
|
+
// time (module evaluation), not inside the lazy ensureSetup — pages call
|
|
132
|
+
// `ensureDraft` BEFORE they resolve CMS content, so hosts installed lazily
|
|
133
|
+
// would arrive after the gate already said no on the first request.
|
|
134
|
+
installPreviewHosts(options.blocks);
|
|
135
|
+
|
|
116
136
|
return function ensureSetup(): Promise<void> {
|
|
117
137
|
setupPromise ??= (async () => {
|
|
118
138
|
const dirBlocks =
|
|
@@ -120,6 +140,8 @@ export function createNextSetup(options: NextSetupOptions): () => Promise<void>
|
|
|
120
140
|
? {}
|
|
121
141
|
: await loadDecofileDirectory(options.blocksDir ?? ".deco/blocks");
|
|
122
142
|
const blocks = { ...dirBlocks, ...options.blocks };
|
|
143
|
+
// Covers the blocksDir mode, where blocks only exist after the fs read.
|
|
144
|
+
installPreviewHosts(blocks);
|
|
123
145
|
|
|
124
146
|
createSiteSetup({
|
|
125
147
|
sections: options.sections,
|