Skip to content

Commit 1320948

Browse files
committed
Add support for skew protection
1 parent 1868c99 commit 1320948

File tree

16 files changed

+512
-28
lines changed

16 files changed

+512
-28
lines changed

examples/playground15/next.config.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare";
1+
import { initOpenNextCloudflareForDev, getDeploymentId } from "@opennextjs/cloudflare";
22

33
initOpenNextCloudflareForDev();
44

@@ -10,6 +10,7 @@ const nextConfig = {
1010
// Generate source map to validate the fix for opennextjs/opennextjs-cloudflare#341
1111
serverSourceMaps: true,
1212
},
13+
deploymentId: getDeploymentId(),
1314
};
1415

1516
export default nextConfig;

examples/playground15/open-next.config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { defineCloudflareConfig } from "@opennextjs/cloudflare";
2-
import kvIncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/kv-incremental-cache";
1+
import { defineCloudflareConfig, type OpenNextConfig } from "@opennextjs/cloudflare";
2+
import r2IncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/r2-incremental-cache";
33

44
export default defineCloudflareConfig({
55
incrementalCache: kvIncrementalCache,

packages/cloudflare/src/api/config.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,4 +169,11 @@ export function getOpenNextConfig(buildOpts: BuildOptions): OpenNextConfig {
169169
return buildOpts.config;
170170
}
171171

172+
/**
173+
* @returns Unique deployment ID
174+
*/
175+
export function getDeploymentId(): string {
176+
return `dpl-${new Date().getTime().toString(36)}`;
177+
}
178+
172179
export type { OpenNextConfig };
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
export * from "./cloudflare-context.js";
2-
export { defineCloudflareConfig, type OpenNextConfig } from "./config.js";
2+
export { defineCloudflareConfig, getDeploymentId, type OpenNextConfig } from "./config.js";

packages/cloudflare/src/cli/build/build.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { compileCacheAssetsManifestSqlFile } from "./open-next/compile-cache-ass
1313
import { compileEnvFiles } from "./open-next/compile-env-files.js";
1414
import { compileImages } from "./open-next/compile-images.js";
1515
import { compileInit } from "./open-next/compile-init.js";
16+
import { compileSkewProtection } from "./open-next/compile-skew-protection.js";
1617
import { compileDurableObjects } from "./open-next/compileDurableObjects.js";
1718
import { createServerBundle } from "./open-next/createServerBundle.js";
1819
import { createWranglerConfigIfNotExistent } from "./utils/index.js";
@@ -69,6 +70,7 @@ export async function build(
6970

7071
// Compile image helpers
7172
compileImages(options);
73+
compileSkewProtection(options, config);
7274

7375
// Compile middleware
7476
await createMiddleware(options, { forceOnlyBuildOnce: true });

packages/cloudflare/src/cli/build/open-next/compile-init.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export async function compileInit(options: BuildOptions) {
1515

1616
const nextConfig = loadConfig(path.join(options.appBuildOutputPath, ".next"));
1717
const basePath = nextConfig.basePath ?? "";
18+
const deploymentId = nextConfig.deploymentId ?? "";
1819

1920
await build({
2021
entryPoints: [initPath],
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import path from "node:path";
2+
import { fileURLToPath } from "node:url";
3+
4+
import type { BuildOptions } from "@opennextjs/aws/build/helper.js";
5+
import { build } from "esbuild";
6+
7+
import type { OpenNextConfig } from "../../../api";
8+
9+
export async function compileSkewProtection(options: BuildOptions, config: OpenNextConfig) {
10+
const currentDir = path.join(path.dirname(fileURLToPath(import.meta.url)));
11+
const templatesDir = path.join(currentDir, "../../templates");
12+
const initPath = path.join(templatesDir, "skew-protection.js");
13+
14+
await build({
15+
entryPoints: [initPath],
16+
outdir: path.join(options.outputDir, "cloudflare"),
17+
bundle: false,
18+
minify: false,
19+
format: "esm",
20+
target: "esnext",
21+
platform: "node",
22+
define: {
23+
__SKEW_PROTECTION_ENABLED__: JSON.stringify(config.cloudflare?.skewProtectionEnabled ?? false),
24+
},
25+
});
26+
}

packages/cloudflare/src/cli/commands/deploy.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { BuildOptions } from "@opennextjs/aws/build/helper.js";
22

33
import type { OpenNextConfig } from "../../api/config.js";
4+
import { DEPLOYMENT_MAPPING_ENV_NAME } from "../templates/skew-protection.js";
45
import { getWranglerEnvironmentFlag, runWrangler } from "../utils/run-wrangler.js";
6+
import { getEnvFromPlatformProxy, quoteShellMeta } from "./helpers.js";
57
import { populateCache } from "./populate-cache.js";
8+
import { getDeploymentMapping } from "./skew-protection.js";
69

710
export async function deploy(
811
options: BuildOptions,
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { getPlatformProxy, type GetPlatformProxyOptions } from "wrangler";
2+
3+
export type WorkerEnvVar = Record<keyof CloudflareEnv, string | undefined>;
4+
5+
/**
6+
* Return the string env vars from the worker.
7+
*
8+
* @param options Options to pass to `getPlatformProxy`, i.e. to set the environment
9+
* @returns the env vars
10+
*/
11+
export async function getEnvFromPlatformProxy(options: GetPlatformProxyOptions) {
12+
const envVars = {} as WorkerEnvVar;
13+
const proxy = await getPlatformProxy<CloudflareEnv>(options);
14+
Object.entries(proxy.env).forEach(([key, value]) => {
15+
if (typeof value === "string") {
16+
envVars[key as keyof CloudflareEnv] = value;
17+
}
18+
});
19+
await proxy.dispose();
20+
return envVars;
21+
}
22+
23+
/**
24+
* Escape shell metacharacters.
25+
*
26+
* When `spawnSync` is invoked with `shell: true`, metacharacters need to be escaped.
27+
*
28+
* Based on https://github.com/ljharb/shell-quote/blob/main/quote.js
29+
*
30+
* @param arg
31+
* @returns escaped arg
32+
*/
33+
export function quoteShellMeta(arg: string) {
34+
if (/["\s]/.test(arg) && !/'/.test(arg)) {
35+
return `'${arg.replace(/(['\\])/g, "\\$1")}'`;
36+
}
37+
if (/["'\s]/.test(arg)) {
38+
return `"${arg.replace(/(["\\$`!])/g, "\\$1")}"`;
39+
}
40+
return arg.replace(/([A-Za-z]:)?([#!"$&'()*,:;<=>?@[\\\]^`{|}])/g, "$1\\$2");
41+
}

packages/cloudflare/src/cli/commands/populate-cache.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import type {
1212
import type { IncrementalCache, TagCache } from "@opennextjs/aws/types/overrides.js";
1313
import { globSync } from "glob";
1414
import { tqdm } from "ts-tqdm";
15-
import { getPlatformProxy, type GetPlatformProxyOptions, unstable_readConfig } from "wrangler";
15+
import { unstable_readConfig } from "wrangler";
1616

1717
import {
1818
BINDING_NAME as KV_CACHE_BINDING_NAME,
@@ -36,6 +36,7 @@ import {
3636
import { normalizePath } from "../build/utils/normalize-path.js";
3737
import type { WranglerTarget } from "../utils/run-wrangler.js";
3838
import { runWrangler } from "../utils/run-wrangler.js";
39+
import { getEnvFromPlatformProxy, quoteShellMeta } from "./helpers.js";
3940

4041
async function resolveCacheName(
4142
value:

0 commit comments

Comments
 (0)