@appwarden/middleware 3.2.1 → 3.4.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.
@@ -1,21 +1,28 @@
1
+ import {
2
+ useContentSecurityPolicy
3
+ } from "../chunk-YBWFEBZC.js";
1
4
  import {
2
5
  validateConfig
3
- } from "../chunk-COV6SHCD.js";
6
+ } from "../chunk-U3T4R5KZ.js";
4
7
  import {
5
8
  checkLockStatus
6
- } from "../chunk-MDODCAA3.js";
9
+ } from "../chunk-3MKVGKH7.js";
7
10
  import {
8
- AppwardenApiTokenSchema,
9
- BooleanSchema,
10
11
  TEMPORARY_REDIRECT_STATUS,
11
12
  buildLockPageUrl,
12
13
  createRedirect,
14
+ debug,
13
15
  isOnLockPage
14
- } from "../chunk-ZX5QO4Y2.js";
16
+ } from "../chunk-QVRWMYGL.js";
15
17
  import {
16
- isHTMLRequest,
18
+ UseCSPInputSchema,
19
+ isHTMLRequest
20
+ } from "../chunk-HCGLR3Z3.js";
21
+ import {
22
+ AppwardenApiTokenSchema,
23
+ BooleanSchema,
17
24
  printMessage
18
- } from "../chunk-L5EQIJZB.js";
25
+ } from "../chunk-G4RRFNHY.js";
19
26
 
20
27
  // src/schemas/astro-cloudflare.ts
21
28
  import { z } from "zod";
@@ -27,12 +34,16 @@ var AstroCloudflareConfigSchema = z.object({
27
34
  /** Optional custom API hostname (defaults to https://api.appwarden.io) */
28
35
  appwardenApiHostname: z.string().optional(),
29
36
  /** Enable debug logging */
30
- debug: BooleanSchema.default(false)
37
+ debug: BooleanSchema.default(false),
38
+ /** Optional Content Security Policy configuration */
39
+ contentSecurityPolicy: z.lazy(() => UseCSPInputSchema).optional()
31
40
  });
32
41
 
33
42
  // src/adapters/astro-cloudflare.ts
43
+ var getNowMs = () => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
34
44
  function createAppwardenMiddleware(configFn) {
35
45
  return async (context, next) => {
46
+ const startTime = getNowMs();
36
47
  const { request } = context;
37
48
  const locals = context.locals;
38
49
  try {
@@ -45,15 +56,23 @@ function createAppwardenMiddleware(configFn) {
45
56
  );
46
57
  return next();
47
58
  }
48
- if (!isHTMLRequest(request)) {
59
+ const config = configFn(runtime);
60
+ const debugFn = debug(config.debug ?? false);
61
+ const requestUrl = new URL(request.url);
62
+ const isHTML = isHTMLRequest(request);
63
+ debugFn(
64
+ `Appwarden middleware invoked for ${requestUrl.pathname}`,
65
+ `isHTML: ${isHTML}`
66
+ );
67
+ if (!isHTML) {
49
68
  return next();
50
69
  }
51
- const config = configFn(runtime);
52
70
  const hasError = validateConfig(config, AstroCloudflareConfigSchema);
53
71
  if (hasError) {
54
72
  return next();
55
73
  }
56
74
  if (isOnLockPage(config.lockPageSlug, request.url)) {
75
+ debugFn("Already on lock page - skipping");
57
76
  return next();
58
77
  }
59
78
  const result = await checkLockStatus({
@@ -66,6 +85,7 @@ function createAppwardenMiddleware(configFn) {
66
85
  });
67
86
  if (result.isLocked) {
68
87
  const lockPageUrl = buildLockPageUrl(config.lockPageSlug, request.url);
88
+ debugFn(`Site is locked - redirecting to ${lockPageUrl.pathname}`);
69
89
  if (context.redirect) {
70
90
  return context.redirect(
71
91
  lockPageUrl.toString(),
@@ -74,7 +94,30 @@ function createAppwardenMiddleware(configFn) {
74
94
  }
75
95
  return createRedirect(lockPageUrl);
76
96
  }
77
- return next();
97
+ debugFn("Site is unlocked to origin");
98
+ const response = await next();
99
+ if (config.contentSecurityPolicy) {
100
+ debugFn("Applying CSP middleware");
101
+ const cspContext = {
102
+ request,
103
+ response,
104
+ hostname: requestUrl.hostname,
105
+ waitUntil: (fn) => runtime.ctx.waitUntil(fn),
106
+ debug: debugFn
107
+ };
108
+ await useContentSecurityPolicy(config.contentSecurityPolicy)(
109
+ cspContext,
110
+ async () => {
111
+ }
112
+ // no-op next
113
+ );
114
+ const elapsed2 = Math.round(getNowMs() - startTime);
115
+ debugFn(`Middleware executed in ${elapsed2}ms`);
116
+ return cspContext.response;
117
+ }
118
+ const elapsed = Math.round(getNowMs() - startTime);
119
+ debugFn(`Middleware executed in ${elapsed}ms`);
120
+ return response;
78
121
  } catch (error) {
79
122
  if (error instanceof Response) {
80
123
  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,24 @@
1
1
  import {
2
2
  validateConfig
3
- } from "../chunk-COV6SHCD.js";
3
+ } from "../chunk-U3T4R5KZ.js";
4
4
  import {
5
5
  checkLockStatus
6
- } from "../chunk-MDODCAA3.js";
6
+ } from "../chunk-3MKVGKH7.js";
7
7
  import {
8
- AppwardenApiTokenSchema,
9
- BooleanSchema,
10
8
  TEMPORARY_REDIRECT_STATUS,
11
9
  buildLockPageUrl,
10
+ debug,
12
11
  isOnLockPage
13
- } from "../chunk-ZX5QO4Y2.js";
12
+ } from "../chunk-QVRWMYGL.js";
13
+ import {
14
+ UseCSPInputSchema,
15
+ isHTMLRequest
16
+ } from "../chunk-HCGLR3Z3.js";
14
17
  import {
15
- isHTMLRequest,
18
+ AppwardenApiTokenSchema,
19
+ BooleanSchema,
16
20
  printMessage
17
- } from "../chunk-L5EQIJZB.js";
21
+ } from "../chunk-G4RRFNHY.js";
18
22
 
19
23
  // src/adapters/nextjs-cloudflare.ts
20
24
  import {
@@ -23,6 +27,17 @@ import {
23
27
 
24
28
  // src/schemas/nextjs-cloudflare.ts
25
29
  import { z } from "zod";
30
+ var NextJsCloudflareCSPInputSchema = UseCSPInputSchema.refine(
31
+ (values) => {
32
+ if (!values.directives) return true;
33
+ const serialized = JSON.stringify(values.directives);
34
+ return !serialized.includes("{{nonce}}");
35
+ },
36
+ {
37
+ path: ["directives"],
38
+ 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."
39
+ }
40
+ );
26
41
  var NextJsCloudflareConfigSchema = z.object({
27
42
  /** The slug/path of the lock page to redirect to when the site is locked */
28
43
  lockPageSlug: z.string(),
@@ -31,24 +46,36 @@ var NextJsCloudflareConfigSchema = z.object({
31
46
  /** Optional custom API hostname (defaults to https://api.appwarden.io) */
32
47
  appwardenApiHostname: z.string().optional(),
33
48
  /** Enable debug logging */
34
- debug: BooleanSchema.default(false)
49
+ debug: BooleanSchema.default(false),
50
+ /** Optional Content Security Policy configuration (headers only, no HTML rewriting; '{{nonce}}' is not supported) */
51
+ contentSecurityPolicy: z.lazy(() => NextJsCloudflareCSPInputSchema).optional()
35
52
  });
36
53
 
37
54
  // src/adapters/nextjs-cloudflare.ts
55
+ var getNowMs = () => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
38
56
  function createAppwardenMiddleware(configFn) {
39
57
  return async (request, _event) => {
58
+ const startTime = getNowMs();
40
59
  try {
41
60
  const { getCloudflareContext } = await import("@opennextjs/cloudflare");
42
61
  const { env, ctx } = await getCloudflareContext();
43
- if (!isHTMLRequest(request)) {
62
+ const config = configFn({ env, ctx });
63
+ const debugFn = debug(config.debug ?? false);
64
+ const requestUrl = new URL(request.url);
65
+ const isHTML = isHTMLRequest(request);
66
+ debugFn(
67
+ `Appwarden middleware invoked for ${requestUrl.pathname}`,
68
+ `isHTML: ${isHTML}`
69
+ );
70
+ if (!isHTML) {
44
71
  return NextResponse.next();
45
72
  }
46
- const config = configFn({ env, ctx });
47
73
  const hasError = validateConfig(config, NextJsCloudflareConfigSchema);
48
74
  if (hasError) {
49
75
  return NextResponse.next();
50
76
  }
51
77
  if (isOnLockPage(config.lockPageSlug, request.url)) {
78
+ debugFn("Already on lock page - skipping");
52
79
  return NextResponse.next();
53
80
  }
54
81
  const result = await checkLockStatus({
@@ -61,8 +88,28 @@ function createAppwardenMiddleware(configFn) {
61
88
  });
62
89
  if (result.isLocked) {
63
90
  const lockPageUrl = buildLockPageUrl(config.lockPageSlug, request.url);
91
+ debugFn(`Site is locked - redirecting to ${lockPageUrl.pathname}`);
64
92
  return NextResponse.redirect(lockPageUrl, TEMPORARY_REDIRECT_STATUS);
65
93
  }
94
+ debugFn("Site is unlocked");
95
+ if (config.contentSecurityPolicy && config.contentSecurityPolicy.mode !== "disabled") {
96
+ debugFn(
97
+ `Applying CSP headers in ${config.contentSecurityPolicy.mode} mode`
98
+ );
99
+ const { makeCSPHeader } = await import("../cloudflare-36BOGAYU.js");
100
+ const [headerName, headerValue] = makeCSPHeader(
101
+ "",
102
+ config.contentSecurityPolicy.directives,
103
+ config.contentSecurityPolicy.mode
104
+ );
105
+ const response = NextResponse.next();
106
+ response.headers.set(headerName, headerValue);
107
+ const elapsed2 = Math.round(getNowMs() - startTime);
108
+ debugFn(`Middleware executed in ${elapsed2}ms`);
109
+ return response;
110
+ }
111
+ const elapsed = Math.round(getNowMs() - startTime);
112
+ debugFn(`Middleware executed in ${elapsed}ms`);
66
113
  return NextResponse.next();
67
114
  } catch (error) {
68
115
  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,27 @@
1
+ import {
2
+ useContentSecurityPolicy
3
+ } from "../chunk-YBWFEBZC.js";
1
4
  import {
2
5
  validateConfig
3
- } from "../chunk-COV6SHCD.js";
6
+ } from "../chunk-U3T4R5KZ.js";
4
7
  import {
5
8
  checkLockStatus
6
- } from "../chunk-MDODCAA3.js";
9
+ } from "../chunk-3MKVGKH7.js";
7
10
  import {
8
- AppwardenApiTokenSchema,
9
- BooleanSchema,
10
11
  buildLockPageUrl,
11
12
  createRedirect,
13
+ debug,
12
14
  isOnLockPage
13
- } from "../chunk-ZX5QO4Y2.js";
15
+ } from "../chunk-QVRWMYGL.js";
14
16
  import {
15
- isHTMLRequest,
17
+ UseCSPInputSchema,
18
+ isHTMLRequest
19
+ } from "../chunk-HCGLR3Z3.js";
20
+ import {
21
+ AppwardenApiTokenSchema,
22
+ BooleanSchema,
16
23
  printMessage
17
- } from "../chunk-L5EQIJZB.js";
24
+ } from "../chunk-G4RRFNHY.js";
18
25
 
19
26
  // src/schemas/react-router-cloudflare.ts
20
27
  import { z } from "zod";
@@ -26,13 +33,16 @@ var ReactRouterCloudflareConfigSchema = z.object({
26
33
  /** Optional custom API hostname (defaults to https://api.appwarden.io) */
27
34
  appwardenApiHostname: z.string().optional(),
28
35
  /** Enable debug logging */
29
- debug: BooleanSchema.default(false)
36
+ debug: BooleanSchema.default(false),
37
+ /** Optional Content Security Policy configuration */
38
+ contentSecurityPolicy: z.lazy(() => UseCSPInputSchema).optional()
30
39
  });
31
40
 
32
41
  // src/adapters/react-router-cloudflare.ts
33
42
  var cloudflareContextSymbol = /* @__PURE__ */ Symbol.for(
34
43
  "@appwarden/middleware:cloudflare"
35
44
  );
45
+ var getNowMs = () => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
36
46
  function getCloudflareContext(context) {
37
47
  if (context?.cloudflare) {
38
48
  return context.cloudflare;
@@ -50,6 +60,7 @@ function getCloudflareContext(context) {
50
60
  }
51
61
  function createAppwardenMiddleware(configFn) {
52
62
  return async (args, next) => {
63
+ const startTime = getNowMs();
53
64
  const { request, context } = args;
54
65
  try {
55
66
  const cloudflare = getCloudflareContext(context);
@@ -61,15 +72,23 @@ function createAppwardenMiddleware(configFn) {
61
72
  );
62
73
  return next();
63
74
  }
64
- if (!isHTMLRequest(request)) {
75
+ const config = configFn(cloudflare);
76
+ const debugFn = debug(config.debug ?? false);
77
+ const requestUrl = new URL(request.url);
78
+ const isHTML = isHTMLRequest(request);
79
+ debugFn(
80
+ `Appwarden middleware invoked for ${requestUrl.pathname}`,
81
+ `isHTML: ${isHTML}`
82
+ );
83
+ if (!isHTML) {
65
84
  return next();
66
85
  }
67
- const config = configFn(cloudflare);
68
86
  const hasError = validateConfig(config, ReactRouterCloudflareConfigSchema);
69
87
  if (hasError) {
70
88
  return next();
71
89
  }
72
90
  if (isOnLockPage(config.lockPageSlug, request.url)) {
91
+ debugFn("Already on lock page - skipping");
73
92
  return next();
74
93
  }
75
94
  const result = await checkLockStatus({
@@ -82,9 +101,33 @@ function createAppwardenMiddleware(configFn) {
82
101
  });
83
102
  if (result.isLocked) {
84
103
  const lockPageUrl = buildLockPageUrl(config.lockPageSlug, request.url);
104
+ debugFn(`Site is locked - redirecting to ${lockPageUrl.pathname}`);
85
105
  throw createRedirect(lockPageUrl);
86
106
  }
87
- return next();
107
+ debugFn("Site is unlocked to origin");
108
+ const response = await next();
109
+ if (config.contentSecurityPolicy && response instanceof Response) {
110
+ debugFn("Applying CSP middleware");
111
+ const cspContext = {
112
+ request,
113
+ response,
114
+ hostname: requestUrl.hostname,
115
+ waitUntil: (fn) => cloudflare.ctx.waitUntil(fn),
116
+ debug: debugFn
117
+ };
118
+ await useContentSecurityPolicy(config.contentSecurityPolicy)(
119
+ cspContext,
120
+ async () => {
121
+ }
122
+ // no-op next
123
+ );
124
+ const elapsed2 = Math.round(getNowMs() - startTime);
125
+ debugFn(`Middleware executed in ${elapsed2}ms`);
126
+ return cspContext.response;
127
+ }
128
+ const elapsed = Math.round(getNowMs() - startTime);
129
+ debugFn(`Middleware executed in ${elapsed}ms`);
130
+ return response;
88
131
  } catch (error) {
89
132
  if (error instanceof Response) {
90
133
  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.
@@ -1,20 +1,27 @@
1
+ import {
2
+ useContentSecurityPolicy
3
+ } from "../chunk-YBWFEBZC.js";
1
4
  import {
2
5
  validateConfig
3
- } from "../chunk-COV6SHCD.js";
6
+ } from "../chunk-U3T4R5KZ.js";
4
7
  import {
5
8
  checkLockStatus
6
- } from "../chunk-MDODCAA3.js";
9
+ } from "../chunk-3MKVGKH7.js";
7
10
  import {
8
- AppwardenApiTokenSchema,
9
- BooleanSchema,
10
11
  buildLockPageUrl,
11
12
  createRedirect,
13
+ debug,
12
14
  isOnLockPage
13
- } from "../chunk-ZX5QO4Y2.js";
15
+ } from "../chunk-QVRWMYGL.js";
14
16
  import {
15
- isHTMLRequest,
17
+ UseCSPInputSchema,
18
+ isHTMLRequest
19
+ } from "../chunk-HCGLR3Z3.js";
20
+ import {
21
+ AppwardenApiTokenSchema,
22
+ BooleanSchema,
16
23
  printMessage
17
- } from "../chunk-L5EQIJZB.js";
24
+ } from "../chunk-G4RRFNHY.js";
18
25
 
19
26
  // src/schemas/tanstack-start-cloudflare.ts
20
27
  import { z } from "zod";
@@ -26,12 +33,16 @@ var TanStackStartCloudflareConfigSchema = z.object({
26
33
  /** Optional custom API hostname (defaults to https://api.appwarden.io) */
27
34
  appwardenApiHostname: z.string().optional(),
28
35
  /** Enable debug logging */
29
- debug: BooleanSchema.default(false)
36
+ debug: BooleanSchema.default(false),
37
+ /** Optional Content Security Policy configuration */
38
+ contentSecurityPolicy: z.lazy(() => UseCSPInputSchema).optional()
30
39
  });
31
40
 
32
41
  // src/adapters/tanstack-start-cloudflare.ts
42
+ var getNowMs = () => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
33
43
  function createAppwardenMiddleware(configFn) {
34
44
  return async (args) => {
45
+ const startTime = getNowMs();
35
46
  const { request, next, context } = args;
36
47
  try {
37
48
  const cloudflare = context.cloudflare;
@@ -43,10 +54,17 @@ function createAppwardenMiddleware(configFn) {
43
54
  );
44
55
  return next();
45
56
  }
46
- if (!isHTMLRequest(request)) {
57
+ const config = configFn(cloudflare);
58
+ const debugFn = debug(config.debug ?? false);
59
+ const requestUrl = new URL(request.url);
60
+ const isHTML = isHTMLRequest(request);
61
+ debugFn(
62
+ `Appwarden middleware invoked for ${requestUrl.pathname}`,
63
+ `isHTML: ${isHTML}`
64
+ );
65
+ if (!isHTML) {
47
66
  return next();
48
67
  }
49
- const config = configFn(cloudflare);
50
68
  const hasError = validateConfig(
51
69
  config,
52
70
  TanStackStartCloudflareConfigSchema
@@ -55,6 +73,7 @@ function createAppwardenMiddleware(configFn) {
55
73
  return next();
56
74
  }
57
75
  if (isOnLockPage(config.lockPageSlug, request.url)) {
76
+ debugFn("Already on lock page - skipping");
58
77
  return next();
59
78
  }
60
79
  const result = await checkLockStatus({
@@ -67,9 +86,33 @@ function createAppwardenMiddleware(configFn) {
67
86
  });
68
87
  if (result.isLocked) {
69
88
  const lockPageUrl = buildLockPageUrl(config.lockPageSlug, request.url);
89
+ debugFn(`Site is locked - redirecting to ${lockPageUrl.pathname}`);
70
90
  throw createRedirect(lockPageUrl);
71
91
  }
72
- return next();
92
+ debugFn("Site is unlocked to origin");
93
+ const response = await next();
94
+ if (config.contentSecurityPolicy && response instanceof Response) {
95
+ debugFn("Applying CSP middleware");
96
+ const cspContext = {
97
+ request,
98
+ response,
99
+ hostname: requestUrl.hostname,
100
+ waitUntil: (fn) => cloudflare.ctx.waitUntil(fn),
101
+ debug: debugFn
102
+ };
103
+ await useContentSecurityPolicy(config.contentSecurityPolicy)(
104
+ cspContext,
105
+ async () => {
106
+ }
107
+ // no-op next
108
+ );
109
+ const elapsed2 = Math.round(getNowMs() - startTime);
110
+ debugFn(`Middleware executed in ${elapsed2}ms`);
111
+ return cspContext.response;
112
+ }
113
+ const elapsed = Math.round(getNowMs() - startTime);
114
+ debugFn(`Middleware executed in ${elapsed}ms`);
115
+ return response;
73
116
  } catch (error) {
74
117
  if (error instanceof Response) {
75
118
  throw error;
@@ -0,0 +1,28 @@
1
+ import {
2
+ CSP_KEYWORDS,
3
+ autoQuoteCSPDirectiveArray,
4
+ autoQuoteCSPDirectiveValue,
5
+ autoQuoteCSPKeyword,
6
+ deleteEdgeValue,
7
+ getLockValue,
8
+ insertErrorLogs,
9
+ isCSPKeyword,
10
+ isQuoted,
11
+ makeCSPHeader,
12
+ store,
13
+ syncEdgeValue
14
+ } from "./chunk-G4RRFNHY.js";
15
+ export {
16
+ CSP_KEYWORDS,
17
+ autoQuoteCSPDirectiveArray,
18
+ autoQuoteCSPDirectiveValue,
19
+ autoQuoteCSPKeyword,
20
+ deleteEdgeValue,
21
+ getLockValue,
22
+ insertErrorLogs,
23
+ isCSPKeyword,
24
+ isQuoted,
25
+ makeCSPHeader,
26
+ store,
27
+ syncEdgeValue
28
+ };