@appwarden/middleware 3.2.1 → 3.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.
@@ -0,0 +1,354 @@
1
+ // src/utils/debug.ts
2
+ var debug = (...msg) => {
3
+ if (true) {
4
+ const formatted = msg.map((m) => {
5
+ if (typeof m === "object" && m !== null) {
6
+ if (m instanceof Error) {
7
+ return m.stack ?? m.message;
8
+ }
9
+ try {
10
+ return JSON.stringify(m);
11
+ } catch {
12
+ try {
13
+ return String(m);
14
+ } catch {
15
+ return "[Unserializable value]";
16
+ }
17
+ }
18
+ }
19
+ return m;
20
+ });
21
+ console.log(...formatted);
22
+ }
23
+ };
24
+
25
+ // src/utils/cloudflare/cloudflare-cache.ts
26
+ var store = {
27
+ json: (context, cacheKey, options) => {
28
+ const cacheKeyUrl = new URL(cacheKey, context.serviceOrigin);
29
+ return {
30
+ getValue: () => getCacheValue(context, cacheKeyUrl),
31
+ updateValue: (json) => updateCacheValue(context, cacheKeyUrl, json, options?.ttl),
32
+ deleteValue: () => clearCache(context, cacheKeyUrl)
33
+ };
34
+ }
35
+ };
36
+ var getCacheValue = async (context, cacheKey) => {
37
+ const match = await context.cache.match(cacheKey);
38
+ if (!match) {
39
+ debug(`[${cacheKey.pathname}] Cache MISS!`);
40
+ return void 0;
41
+ }
42
+ debug(`[${cacheKey.pathname}] Cache MATCH!`);
43
+ return match;
44
+ };
45
+ var updateCacheValue = async (context, cacheKey, value, ttl) => {
46
+ debug(
47
+ "updating cache...",
48
+ cacheKey.href,
49
+ value,
50
+ ttl ? `expires in ${ttl}s` : ""
51
+ );
52
+ await context.cache.put(
53
+ cacheKey,
54
+ new Response(JSON.stringify(value), {
55
+ headers: {
56
+ "content-type": "application/json",
57
+ ...ttl && {
58
+ "cache-control": `max-age=${ttl}`
59
+ }
60
+ }
61
+ })
62
+ );
63
+ };
64
+ var clearCache = (context, cacheKey) => context.cache.delete(cacheKey);
65
+
66
+ // src/utils/cloudflare/csp-keywords.ts
67
+ var CSP_KEYWORDS = [
68
+ "self",
69
+ "none",
70
+ "unsafe-inline",
71
+ "unsafe-eval",
72
+ "unsafe-hashes",
73
+ "strict-dynamic",
74
+ "report-sample",
75
+ "unsafe-allow-redirects",
76
+ "wasm-unsafe-eval",
77
+ "trusted-types-eval",
78
+ "report-sha256",
79
+ "report-sha384",
80
+ "report-sha512",
81
+ "unsafe-webtransport-hashes"
82
+ ];
83
+ var CSP_KEYWORDS_SET = new Set(CSP_KEYWORDS);
84
+ var isCSPKeyword = (value) => {
85
+ return CSP_KEYWORDS_SET.has(value.toLowerCase());
86
+ };
87
+ var isQuoted = (value) => {
88
+ return value.startsWith("'") && value.endsWith("'");
89
+ };
90
+ var autoQuoteCSPKeyword = (value) => {
91
+ const trimmed = value.trim();
92
+ if (isQuoted(trimmed)) {
93
+ return trimmed;
94
+ }
95
+ if (isCSPKeyword(trimmed)) {
96
+ return `'${trimmed}'`;
97
+ }
98
+ return trimmed;
99
+ };
100
+ var autoQuoteCSPDirectiveValue = (value) => {
101
+ return value.trim().split(/\s+/).filter(Boolean).map(autoQuoteCSPKeyword).join(" ");
102
+ };
103
+ var autoQuoteCSPDirectiveArray = (values) => {
104
+ return values.map(autoQuoteCSPKeyword);
105
+ };
106
+
107
+ // src/utils/print-message.ts
108
+ var addSlashes = (str) => str.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$").replace(/"/g, '\\"').replace(/'/g, "\\'").replace(/\u0000/g, "\\0").replace(/<\/script>/gi, "<\\/script>");
109
+ var printMessage = (message) => `[@appwarden/middleware] ${addSlashes(message)}`;
110
+
111
+ // src/utils/cloudflare/delete-edge-value.ts
112
+ var deleteEdgeValue = async (context) => {
113
+ try {
114
+ switch (context.provider) {
115
+ case "cloudflare-cache": {
116
+ const success = await context.edgeCache.deleteValue();
117
+ if (!success) {
118
+ throw new Error();
119
+ }
120
+ break;
121
+ }
122
+ default:
123
+ throw new Error(`Unsupported provider: ${context.provider}`);
124
+ }
125
+ } catch (e) {
126
+ const message = "Failed to delete edge value";
127
+ console.error(
128
+ printMessage(e instanceof Error ? `${message} - ${e.message}` : message)
129
+ );
130
+ }
131
+ };
132
+
133
+ // src/schemas/helpers.ts
134
+ import { z } from "zod";
135
+ var BoolOrStringSchema = z.union([z.string(), z.boolean()]).optional();
136
+ var BooleanSchema = BoolOrStringSchema.transform((val) => {
137
+ if (val === "true" || val === true) {
138
+ return true;
139
+ } else if (val === "false" || val === false) {
140
+ return false;
141
+ }
142
+ throw new Error("Invalid value");
143
+ });
144
+ var AppwardenApiTokenSchema = z.string().refine((val) => !!val, { message: "appwardenApiToken is required" });
145
+ var LockValue = z.object({
146
+ isLocked: z.number(),
147
+ isLockedTest: z.number(),
148
+ lastCheck: z.number(),
149
+ code: z.string()
150
+ });
151
+
152
+ // src/utils/errors.ts
153
+ var errorsMap = {
154
+ mode: '`CSP_MODE` must be one of "disabled", "report-only", or "enforced"',
155
+ directives: {
156
+ ["DirectivesRequired" /* DirectivesRequired */]: '`CSP_DIRECTIVES` must be provided when `CSP_MODE` is "report-only" or "enforced"',
157
+ ["DirectivesBadParse" /* DirectivesBadParse */]: "Failed to parse `CSP_DIRECTIVES`. Is it a valid JSON string?"
158
+ },
159
+ appwardenApiToken: "Please provide a valid `appwardenApiToken`. Learn more at https://appwarden.com/docs/guides/api-token-management."
160
+ };
161
+ var getErrors = (error) => {
162
+ const matches = [];
163
+ const errors = [...Object.entries(error.flatten().fieldErrors)];
164
+ for (const issue of error.issues) {
165
+ errors.push(
166
+ ...Object.entries(
167
+ "returnTypeError" in issue ? issue.returnTypeError.flatten().fieldErrors : {}
168
+ )
169
+ );
170
+ }
171
+ for (const [field, maybeSchemaErrorKey] of errors) {
172
+ let match = errorsMap[field];
173
+ if (match) {
174
+ if (match instanceof Object) {
175
+ if (maybeSchemaErrorKey) {
176
+ match = match[maybeSchemaErrorKey[0]];
177
+ }
178
+ }
179
+ matches.push(match);
180
+ }
181
+ }
182
+ return matches;
183
+ };
184
+
185
+ // src/utils/cloudflare/get-lock-value.ts
186
+ var getLockValue = async (context) => {
187
+ try {
188
+ let shouldDeleteEdgeValue = false;
189
+ let cacheResponse, lockValue = {
190
+ isLocked: 0,
191
+ isLockedTest: 0,
192
+ lastCheck: Date.now(),
193
+ code: ""
194
+ };
195
+ switch (context.provider) {
196
+ case "cloudflare-cache": {
197
+ cacheResponse = await context.edgeCache.getValue();
198
+ break;
199
+ }
200
+ default:
201
+ throw new Error(`Unsupported provider: ${context.provider}`);
202
+ }
203
+ if (!cacheResponse) {
204
+ return { lockValue: void 0 };
205
+ }
206
+ try {
207
+ const clonedResponse = cacheResponse?.clone();
208
+ lockValue = LockValue.parse(
209
+ clonedResponse ? await clonedResponse.json() : void 0
210
+ );
211
+ } catch (error) {
212
+ console.error(
213
+ printMessage(
214
+ `Failed to parse ${context.keyName} from edge cache - ${error}`
215
+ )
216
+ );
217
+ shouldDeleteEdgeValue = true;
218
+ }
219
+ return { lockValue, shouldDeleteEdgeValue };
220
+ } catch (e) {
221
+ const message = "Failed to retrieve edge value";
222
+ console.error(
223
+ printMessage(e instanceof Error ? `${message} - ${e.message}` : message)
224
+ );
225
+ return { lockValue: void 0 };
226
+ }
227
+ };
228
+
229
+ // src/utils/cloudflare/insert-errors-logs.ts
230
+ var insertErrorLogs = async (context, error) => {
231
+ const errors = getErrors(error);
232
+ for (const err of errors) {
233
+ console.log(printMessage(err));
234
+ }
235
+ return new HTMLRewriter().on("body", {
236
+ element: (elem) => {
237
+ elem.append(
238
+ `<script>
239
+ ${errors.map((err) => `console.error(\`${printMessage(err)}\`)`).join("\n")}
240
+ </script>`,
241
+ { html: true }
242
+ );
243
+ }
244
+ }).transform(await fetch(context.request));
245
+ };
246
+
247
+ // src/utils/cloudflare/make-csp-header.ts
248
+ var addNonce = (value, cspNonce) => value.replace("{{nonce}}", `'nonce-${cspNonce}'`);
249
+ var makeCSPHeader = (cspNonce, directives, mode) => {
250
+ const namesSeen = /* @__PURE__ */ new Set(), result = [];
251
+ Object.entries(directives ?? {}).forEach(([originalName, value]) => {
252
+ const name = originalName.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
253
+ if (namesSeen.has(name)) {
254
+ throw new Error(`${originalName} is specified more than once`);
255
+ }
256
+ namesSeen.add(name);
257
+ let directiveValue;
258
+ if (Array.isArray(value)) {
259
+ directiveValue = autoQuoteCSPDirectiveArray(value).join(" ");
260
+ } else if (value === true) {
261
+ directiveValue = "";
262
+ } else if (typeof value === "string") {
263
+ directiveValue = autoQuoteCSPDirectiveValue(value);
264
+ } else {
265
+ return;
266
+ }
267
+ if (directiveValue) {
268
+ result.push(`${name} ${addNonce(directiveValue, cspNonce)}`);
269
+ } else {
270
+ result.push(name);
271
+ }
272
+ });
273
+ return [
274
+ mode === "enforced" ? "Content-Security-Policy" : "Content-Security-Policy-Report-Only",
275
+ result.join("; ")
276
+ ];
277
+ };
278
+
279
+ // src/utils/cloudflare/sync-edge-value.ts
280
+ var APIError = class extends Error {
281
+ constructor(message) {
282
+ super(message);
283
+ this.name = "APIError";
284
+ }
285
+ };
286
+ var DEFAULT_API_HOSTNAME = "https://api.appwarden.io";
287
+ var syncEdgeValue = async (context) => {
288
+ debug(`syncing with api`);
289
+ try {
290
+ const apiHostname = context.appwardenApiHostname ?? DEFAULT_API_HOSTNAME;
291
+ const response = await fetch(new URL("/v1/status/check", apiHostname), {
292
+ method: "POST",
293
+ headers: { "content-type": "application/json" },
294
+ body: JSON.stringify({
295
+ service: "cloudflare",
296
+ provider: context.provider,
297
+ fqdn: context.requestUrl.hostname,
298
+ appwardenApiToken: context.appwardenApiToken
299
+ })
300
+ });
301
+ if (response.status !== 200) {
302
+ throw new Error(`${response.status} ${response.statusText}`);
303
+ }
304
+ if (response.headers.get("content-type")?.includes("application/json")) {
305
+ const result = await response.json();
306
+ if (result.error) {
307
+ throw new APIError(result.error.message);
308
+ }
309
+ if (!result.content) {
310
+ throw new APIError("no content from api");
311
+ }
312
+ try {
313
+ const parsedValue = LockValue.omit({ lastCheck: true }).parse(
314
+ result.content
315
+ );
316
+ debug(`syncing with api...DONE ${JSON.stringify(parsedValue, null, 2)}`);
317
+ await context.edgeCache.updateValue({
318
+ ...parsedValue,
319
+ lastCheck: Date.now()
320
+ });
321
+ } catch (error) {
322
+ throw new APIError(`Failed to parse check endpoint result - ${error}`);
323
+ }
324
+ }
325
+ } catch (e) {
326
+ const message = "Failed to fetch from check endpoint";
327
+ console.error(
328
+ printMessage(
329
+ e instanceof APIError ? e.message : e instanceof Error ? `${message} - ${e.message}` : message
330
+ )
331
+ );
332
+ }
333
+ };
334
+
335
+ export {
336
+ debug,
337
+ getErrors,
338
+ printMessage,
339
+ BooleanSchema,
340
+ AppwardenApiTokenSchema,
341
+ LockValue,
342
+ store,
343
+ CSP_KEYWORDS,
344
+ isCSPKeyword,
345
+ isQuoted,
346
+ autoQuoteCSPKeyword,
347
+ autoQuoteCSPDirectiveValue,
348
+ autoQuoteCSPDirectiveArray,
349
+ deleteEdgeValue,
350
+ getLockValue,
351
+ insertErrorLogs,
352
+ makeCSPHeader,
353
+ syncEdgeValue
354
+ };
@@ -1,5 +1,7 @@
1
1
  import { Runtime } from '@astrojs/cloudflare';
2
2
  import { APIContext, MiddlewareHandler } from 'astro';
3
+ import { U as UseCSPInput } from '../use-content-security-policy-DUYpyUPy.js';
4
+ import 'zod';
3
5
 
4
6
  /**
5
7
  * Cloudflare runtime context provided by Astro on Cloudflare Workers.
@@ -20,6 +22,8 @@ interface AstroAppwardenConfig {
20
22
  appwardenApiHostname?: string;
21
23
  /** Enable debug logging */
22
24
  debug?: boolean;
25
+ /** Optional Content Security Policy configuration */
26
+ contentSecurityPolicy?: UseCSPInput;
23
27
  }
24
28
  /**
25
29
  * Configuration function that receives the Cloudflare runtime and returns the config.
@@ -1,21 +1,27 @@
1
+ import {
2
+ useContentSecurityPolicy
3
+ } from "../chunk-BYRGGUK7.js";
1
4
  import {
2
5
  validateConfig
3
- } from "../chunk-COV6SHCD.js";
6
+ } from "../chunk-7AVYENM2.js";
4
7
  import {
5
8
  checkLockStatus
6
- } from "../chunk-MDODCAA3.js";
9
+ } from "../chunk-PH77FI6C.js";
7
10
  import {
8
- AppwardenApiTokenSchema,
9
- BooleanSchema,
10
11
  TEMPORARY_REDIRECT_STATUS,
11
12
  buildLockPageUrl,
12
13
  createRedirect,
13
14
  isOnLockPage
14
- } from "../chunk-ZX5QO4Y2.js";
15
+ } from "../chunk-SUZPTFWY.js";
15
16
  import {
16
- isHTMLRequest,
17
+ UseCSPInputSchema,
18
+ isHTMLRequest
19
+ } from "../chunk-HCGLR3Z3.js";
20
+ import {
21
+ AppwardenApiTokenSchema,
22
+ BooleanSchema,
17
23
  printMessage
18
- } from "../chunk-L5EQIJZB.js";
24
+ } from "../chunk-ZBYVJ3HA.js";
19
25
 
20
26
  // src/schemas/astro-cloudflare.ts
21
27
  import { z } from "zod";
@@ -27,7 +33,9 @@ var AstroCloudflareConfigSchema = z.object({
27
33
  /** Optional custom API hostname (defaults to https://api.appwarden.io) */
28
34
  appwardenApiHostname: z.string().optional(),
29
35
  /** Enable debug logging */
30
- debug: BooleanSchema.default(false)
36
+ debug: BooleanSchema.default(false),
37
+ /** Optional Content Security Policy configuration */
38
+ contentSecurityPolicy: z.lazy(() => UseCSPInputSchema).optional()
31
39
  });
32
40
 
33
41
  // src/adapters/astro-cloudflare.ts
@@ -74,7 +82,23 @@ function createAppwardenMiddleware(configFn) {
74
82
  }
75
83
  return createRedirect(lockPageUrl);
76
84
  }
77
- return next();
85
+ const response = await next();
86
+ if (config.contentSecurityPolicy) {
87
+ const cspContext = {
88
+ request,
89
+ response,
90
+ hostname: new URL(request.url).hostname,
91
+ waitUntil: (fn) => runtime.ctx.waitUntil(fn)
92
+ };
93
+ await useContentSecurityPolicy(config.contentSecurityPolicy)(
94
+ cspContext,
95
+ async () => {
96
+ }
97
+ // no-op next
98
+ );
99
+ return cspContext.response;
100
+ }
101
+ return response;
78
102
  } catch (error) {
79
103
  if (error instanceof Response) {
80
104
  throw error;
@@ -1,4 +1,6 @@
1
1
  import { NextRequest, NextFetchEvent, NextResponse } from 'next/server';
2
+ import { U as UseCSPInput } from '../use-content-security-policy-DUYpyUPy.js';
3
+ import 'zod';
2
4
 
3
5
  /**
4
6
  * Cloudflare runtime context provided by @opennextjs/cloudflare.
@@ -20,6 +22,8 @@ interface NextJsCloudflareAppwardenConfig {
20
22
  appwardenApiHostname?: string;
21
23
  /** Enable debug logging */
22
24
  debug?: boolean;
25
+ /** Optional Content Security Policy configuration (headers only, no HTML rewriting; `{{nonce}}` placeholders are not supported) */
26
+ contentSecurityPolicy?: UseCSPInput;
23
27
  }
24
28
  /**
25
29
  * Configuration function that receives the Cloudflare runtime and returns the config.
@@ -1,20 +1,23 @@
1
1
  import {
2
2
  validateConfig
3
- } from "../chunk-COV6SHCD.js";
3
+ } from "../chunk-7AVYENM2.js";
4
4
  import {
5
5
  checkLockStatus
6
- } from "../chunk-MDODCAA3.js";
6
+ } from "../chunk-PH77FI6C.js";
7
7
  import {
8
- AppwardenApiTokenSchema,
9
- BooleanSchema,
10
8
  TEMPORARY_REDIRECT_STATUS,
11
9
  buildLockPageUrl,
12
10
  isOnLockPage
13
- } from "../chunk-ZX5QO4Y2.js";
11
+ } from "../chunk-SUZPTFWY.js";
12
+ import {
13
+ UseCSPInputSchema,
14
+ isHTMLRequest
15
+ } from "../chunk-HCGLR3Z3.js";
14
16
  import {
15
- isHTMLRequest,
17
+ AppwardenApiTokenSchema,
18
+ BooleanSchema,
16
19
  printMessage
17
- } from "../chunk-L5EQIJZB.js";
20
+ } from "../chunk-ZBYVJ3HA.js";
18
21
 
19
22
  // src/adapters/nextjs-cloudflare.ts
20
23
  import {
@@ -23,6 +26,17 @@ import {
23
26
 
24
27
  // src/schemas/nextjs-cloudflare.ts
25
28
  import { z } from "zod";
29
+ var NextJsCloudflareCSPInputSchema = UseCSPInputSchema.refine(
30
+ (values) => {
31
+ if (!values.directives) return true;
32
+ const serialized = JSON.stringify(values.directives);
33
+ return !serialized.includes("{{nonce}}");
34
+ },
35
+ {
36
+ path: ["directives"],
37
+ message: "Nonce-based CSP is not supported in the Next.js Cloudflare adapter. Remove '{{nonce}}' placeholders from your CSP directives, as this adapter does not inject nonces into HTML."
38
+ }
39
+ );
26
40
  var NextJsCloudflareConfigSchema = z.object({
27
41
  /** The slug/path of the lock page to redirect to when the site is locked */
28
42
  lockPageSlug: z.string(),
@@ -31,7 +45,9 @@ var NextJsCloudflareConfigSchema = z.object({
31
45
  /** Optional custom API hostname (defaults to https://api.appwarden.io) */
32
46
  appwardenApiHostname: z.string().optional(),
33
47
  /** Enable debug logging */
34
- debug: BooleanSchema.default(false)
48
+ debug: BooleanSchema.default(false),
49
+ /** Optional Content Security Policy configuration (headers only, no HTML rewriting; '{{nonce}}' is not supported) */
50
+ contentSecurityPolicy: z.lazy(() => NextJsCloudflareCSPInputSchema).optional()
35
51
  });
36
52
 
37
53
  // src/adapters/nextjs-cloudflare.ts
@@ -63,6 +79,17 @@ function createAppwardenMiddleware(configFn) {
63
79
  const lockPageUrl = buildLockPageUrl(config.lockPageSlug, request.url);
64
80
  return NextResponse.redirect(lockPageUrl, TEMPORARY_REDIRECT_STATUS);
65
81
  }
82
+ if (config.contentSecurityPolicy && config.contentSecurityPolicy.mode !== "disabled") {
83
+ const { makeCSPHeader } = await import("../cloudflare-LUT5TVEV.js");
84
+ const [headerName, headerValue] = makeCSPHeader(
85
+ "",
86
+ config.contentSecurityPolicy.directives,
87
+ config.contentSecurityPolicy.mode
88
+ );
89
+ const response = NextResponse.next();
90
+ response.headers.set(headerName, headerValue);
91
+ return response;
92
+ }
66
93
  return NextResponse.next();
67
94
  } catch (error) {
68
95
  console.error(
@@ -1,3 +1,6 @@
1
+ import { U as UseCSPInput } from '../use-content-security-policy-DUYpyUPy.js';
2
+ import 'zod';
3
+
1
4
  /**
2
5
  * Cloudflare context provided by React Router on Cloudflare Workers.
3
6
  * This is the shape of `context.cloudflare` in React Router loaders/actions.
@@ -23,6 +26,8 @@ interface ReactRouterAppwardenConfig {
23
26
  appwardenApiHostname?: string;
24
27
  /** Enable debug logging */
25
28
  debug?: boolean;
29
+ /** Optional Content Security Policy configuration */
30
+ contentSecurityPolicy?: UseCSPInput;
26
31
  }
27
32
  /**
28
33
  * Configuration function that receives the Cloudflare context and returns the config.
@@ -1,20 +1,26 @@
1
+ import {
2
+ useContentSecurityPolicy
3
+ } from "../chunk-BYRGGUK7.js";
1
4
  import {
2
5
  validateConfig
3
- } from "../chunk-COV6SHCD.js";
6
+ } from "../chunk-7AVYENM2.js";
4
7
  import {
5
8
  checkLockStatus
6
- } from "../chunk-MDODCAA3.js";
9
+ } from "../chunk-PH77FI6C.js";
7
10
  import {
8
- AppwardenApiTokenSchema,
9
- BooleanSchema,
10
11
  buildLockPageUrl,
11
12
  createRedirect,
12
13
  isOnLockPage
13
- } from "../chunk-ZX5QO4Y2.js";
14
+ } from "../chunk-SUZPTFWY.js";
14
15
  import {
15
- isHTMLRequest,
16
+ UseCSPInputSchema,
17
+ isHTMLRequest
18
+ } from "../chunk-HCGLR3Z3.js";
19
+ import {
20
+ AppwardenApiTokenSchema,
21
+ BooleanSchema,
16
22
  printMessage
17
- } from "../chunk-L5EQIJZB.js";
23
+ } from "../chunk-ZBYVJ3HA.js";
18
24
 
19
25
  // src/schemas/react-router-cloudflare.ts
20
26
  import { z } from "zod";
@@ -26,7 +32,9 @@ var ReactRouterCloudflareConfigSchema = z.object({
26
32
  /** Optional custom API hostname (defaults to https://api.appwarden.io) */
27
33
  appwardenApiHostname: z.string().optional(),
28
34
  /** Enable debug logging */
29
- debug: BooleanSchema.default(false)
35
+ debug: BooleanSchema.default(false),
36
+ /** Optional Content Security Policy configuration */
37
+ contentSecurityPolicy: z.lazy(() => UseCSPInputSchema).optional()
30
38
  });
31
39
 
32
40
  // src/adapters/react-router-cloudflare.ts
@@ -84,7 +92,23 @@ function createAppwardenMiddleware(configFn) {
84
92
  const lockPageUrl = buildLockPageUrl(config.lockPageSlug, request.url);
85
93
  throw createRedirect(lockPageUrl);
86
94
  }
87
- return next();
95
+ const response = await next();
96
+ if (config.contentSecurityPolicy && response instanceof Response) {
97
+ const cspContext = {
98
+ request,
99
+ response,
100
+ hostname: new URL(request.url).hostname,
101
+ waitUntil: (fn) => cloudflare.ctx.waitUntil(fn)
102
+ };
103
+ await useContentSecurityPolicy(config.contentSecurityPolicy)(
104
+ cspContext,
105
+ async () => {
106
+ }
107
+ // no-op next
108
+ );
109
+ return cspContext.response;
110
+ }
111
+ return response;
88
112
  } catch (error) {
89
113
  if (error instanceof Response) {
90
114
  throw error;
@@ -1,3 +1,6 @@
1
+ import { U as UseCSPInput } from '../use-content-security-policy-DUYpyUPy.js';
2
+ import 'zod';
3
+
1
4
  /**
2
5
  * Cloudflare context provided by TanStack Start on Cloudflare Workers.
3
6
  * This is the shape of the cloudflare context available in middleware.
@@ -18,6 +21,8 @@ interface TanStackStartAppwardenConfig {
18
21
  appwardenApiHostname?: string;
19
22
  /** Enable debug logging */
20
23
  debug?: boolean;
24
+ /** Optional Content Security Policy configuration */
25
+ contentSecurityPolicy?: UseCSPInput;
21
26
  }
22
27
  /**
23
28
  * Configuration function that receives the Cloudflare context and returns the config.