@decocms/nextjs 7.28.0-beta.4 → 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 +22 -11
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
|
@@ -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,17 +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 };
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
// declare `previewHosts` (e.g. ["fila.vtex.app", "localhost:3100"]).
|
|
126
|
-
// Read here, from the SETUP-TIME base blocks — never via loadBlocks()
|
|
127
|
-
// at request time, where the draft override is merged in: an allowlist
|
|
128
|
-
// readable through the override could be rewritten by the very draft it
|
|
129
|
-
// gates. DECO_ALLOWED_PREVIEW_HOSTS remains an operational override.
|
|
130
|
-
const siteBlock = blocks.site as { previewHosts?: unknown } | undefined;
|
|
131
|
-
if (Array.isArray(siteBlock?.previewHosts)) {
|
|
132
|
-
setDraftPreviewHosts(siteBlock.previewHosts);
|
|
133
|
-
}
|
|
143
|
+
// Covers the blocksDir mode, where blocks only exist after the fs read.
|
|
144
|
+
installPreviewHosts(blocks);
|
|
134
145
|
|
|
135
146
|
createSiteSetup({
|
|
136
147
|
sections: options.sections,
|