@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.
- package/README.md +257 -22
- package/{chunk-COV6SHCD.js → chunk-7AVYENM2.js} +2 -4
- package/chunk-BYRGGUK7.js +51 -0
- package/chunk-HCGLR3Z3.js +97 -0
- package/chunk-PH77FI6C.js +70 -0
- package/{chunk-ZX5QO4Y2.js → chunk-SUZPTFWY.js} +2 -58
- package/chunk-ZBYVJ3HA.js +354 -0
- package/cloudflare/astro.d.ts +4 -0
- package/cloudflare/astro.js +33 -9
- package/cloudflare/nextjs.d.ts +4 -0
- package/cloudflare/nextjs.js +35 -8
- package/cloudflare/react-router.d.ts +5 -0
- package/cloudflare/react-router.js +33 -9
- package/cloudflare/tanstack-start.d.ts +5 -0
- package/cloudflare/tanstack-start.js +33 -9
- package/cloudflare-LUT5TVEV.js +28 -0
- package/cloudflare.d.ts +440 -8
- package/cloudflare.js +25 -47
- package/index.d.ts +2 -1
- package/index.js +5 -4
- package/package.json +8 -3
- package/use-content-security-policy-CjlLe4yU.d.ts +16 -0
- package/{use-content-security-policy-DjRTjIpm.d.ts → use-content-security-policy-DUYpyUPy.d.ts} +1 -18
- package/vercel.d.ts +677 -0
- package/vercel.js +59 -9
- package/chunk-A5XGYLYS.js +0 -196
- package/chunk-L5EQIJZB.js +0 -54
- package/chunk-MDODCAA3.js +0 -232
package/README.md
CHANGED
|
@@ -1,50 +1,283 @@
|
|
|
1
1
|
# @appwarden/middleware
|
|
2
2
|
|
|
3
|
-
](https://appwarden.io/docs/reference/appwarden-middleware)
|
|
4
|
+
[](https://github.com/appwarden/middleware)
|
|
4
5
|
[](https://www.npmjs.com/package/@appwarden/middleware)
|
|
5
6
|
[](https://docs.npmjs.com/generating-provenance-statements)
|
|
7
|
+

|
|
6
8
|
[](https://opensource.org/licenses/MIT)
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
## Core Features
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
- **Discord Integration**: Quarantine your website via Discord commands (`/quarantine [un]lock`)
|
|
13
|
+
- **Instant Quarantine**: Immediately redirects all visitors to a lock page when activated to stop in progress attacks.
|
|
14
|
+
- **Nonce-based Content Security Policy (See [Feature Compatibility](#feature-compatibility))**: Deploy a nonce-based Content Security Policy (CSP) using HTML rewriting on Cloudflare where supported.
|
|
15
|
+
- **Minimal Runtime Overhead**: Negligible performance impact by using `event.waitUntil` for status checks
|
|
11
16
|
|
|
12
|
-
###
|
|
17
|
+
### Feature Compatibility
|
|
13
18
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
The table below summarizes which Appwarden features are available on each platform, including quarantine enforcement and Content Security Policy (CSP) support (with or without nonces).
|
|
20
|
+
|
|
21
|
+
| Platform / Adapter | Package / entrypoint | Quarantine | CSP | CSP Nonce |
|
|
22
|
+
| --------------------------------------- | ------------------------------------------------- | ---------- | --- | --------- |
|
|
23
|
+
| Cloudflare – Universal middleware | `@appwarden/middleware/cloudflare` | ✅ | ✅ | ✅ |
|
|
24
|
+
| Cloudflare – Astro | `@appwarden/middleware/cloudflare/astro` | ✅ | ✅ | ✅ |
|
|
25
|
+
| Cloudflare – React Router | `@appwarden/middleware/cloudflare/react-router` | ✅ | ✅ | ✅ |
|
|
26
|
+
| Cloudflare – TanStack Start | `@appwarden/middleware/cloudflare/tanstack-start` | ✅ | ✅ | ✅ |
|
|
27
|
+
| Cloudflare – Next.js (OpenNext adapter) | `@appwarden/middleware/cloudflare/nextjs` | ✅ | ✅ | ❌ |
|
|
28
|
+
| Vercel - Universal middleware | `@appwarden/middleware/vercel` | ✅ | ✅ | ❌ |
|
|
29
|
+
|
|
30
|
+
Nonce-based CSP requires HTML rewriting and is only available on Cloudflare. Next.js on Cloudflare (OpenNext) and Vercel Edge Middleware apply CSP headers only and do **not** support nonces. If you are using Next.js on Cloudflare, please use the Cloudflare Universal middleware for CSP nonce support.
|
|
31
|
+
|
|
32
|
+
## Configuration
|
|
33
|
+
|
|
34
|
+
The following options are shared across the Cloudflare and Vercel middleware bundles.
|
|
35
|
+
|
|
36
|
+
### `lockPageSlug`
|
|
37
|
+
|
|
38
|
+
The path or route (for example, `/maintenance`) to redirect users to when the domain is quarantined.
|
|
39
|
+
This should be a working page on your site, such as a maintenance or status page, that
|
|
40
|
+
explains why the website is temporarily unavailable.
|
|
41
|
+
|
|
42
|
+
### `contentSecurityPolicy`
|
|
43
|
+
|
|
44
|
+
Controls the [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) headers that Appwarden adds.
|
|
45
|
+
|
|
46
|
+
- `mode` (optional) controls how the CSP is applied:
|
|
47
|
+
- `"disabled"` – no CSP header is sent.
|
|
48
|
+
- `"report-only"` – sends the `Content-Security-Policy-Report-Only` header so violations are
|
|
49
|
+
reported (for example in the browser console) but not blocked.
|
|
50
|
+
- `"enforced"` – sends the `Content-Security-Policy` header so violations are actively blocked.
|
|
51
|
+
|
|
52
|
+
When developing or iterating on your CSP, we recommend starting with `"report-only"` so you can
|
|
53
|
+
identify and fix violations before switching to `"enforced"`.
|
|
54
|
+
|
|
55
|
+
- `directives` (optional) is an object whose keys are CSP directive names and whose values are
|
|
56
|
+
arrays of allowed sources. For example:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
contentSecurityPolicy: {
|
|
60
|
+
mode: "enforced",
|
|
61
|
+
directives: {
|
|
62
|
+
"script-src": ["'self'", "{{nonce}}"],
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
To add a nonce to a directive (See [Feature Compatibility](#feature-compatibility)), include the `"{{nonce}}"` placeholder in the list of sources.
|
|
68
|
+
|
|
69
|
+
### `appwardenApiToken`
|
|
70
|
+
|
|
71
|
+
The Appwarden API token used to authenticate requests to the Appwarden API. See the
|
|
72
|
+
[API token management guide](https://appwarden.io/docs/guides/api-token-management) for details on creating and
|
|
73
|
+
managing your token.
|
|
74
|
+
|
|
75
|
+
Treat this token as a secret (similar to a password): do not commit it to source control and store
|
|
76
|
+
it in environment variables or secret management where possible. Appwarden stores API tokens using
|
|
77
|
+
AES-GCM encryption and does not display them after creation.
|
|
78
|
+
|
|
79
|
+
### `cacheUrl` (Vercel only)
|
|
80
|
+
|
|
81
|
+
The URL or connection string of the cache provider (for example, Upstash or Vercel Edge Config)
|
|
82
|
+
that stores the quarantine status for your domain. See the
|
|
83
|
+
[Vercel integration guide](https://appwarden.io/docs/guides/vercel-middleware-integration#3-configure-a-cache-provider)
|
|
84
|
+
for cache provider configuration details.
|
|
85
|
+
|
|
86
|
+
### `vercelApiToken` (Vercel only)
|
|
87
|
+
|
|
88
|
+
A Vercel API token that Appwarden uses to manage the Vercel Edge Config cache provider that synchronizes the quarantine status of your domain.
|
|
89
|
+
See the [Vercel integration guide](https://appwarden.io/docs/guides/vercel-middleware-integration#3-configure-a-cache-provider)
|
|
90
|
+
for cache provider configuration details.
|
|
91
|
+
|
|
92
|
+
Appwarden never stores or logs your Vercel API token; it is used only to manage the quarantine status cache for your domain.
|
|
18
93
|
|
|
19
94
|
## Installation
|
|
20
95
|
|
|
21
96
|
Compatible with websites powered by [Cloudflare](https://developers.cloudflare.com/workers/static-assets/) or [Vercel](https://vercel.com).
|
|
22
97
|
|
|
23
|
-
For
|
|
98
|
+
For more background and advanced configuration, see the [Appwarden documentation](https://appwarden.io/docs).
|
|
99
|
+
|
|
100
|
+
### 1. Cloudflare
|
|
101
|
+
|
|
102
|
+
#### 1.1 Universal Middleware (direct Cloudflare Worker usage)
|
|
103
|
+
|
|
104
|
+
The **Universal Middleware** (`@appwarden/middleware/cloudflare`) is the recommended way to install Appwarden on Cloudflare. The easiest way to deploy this universal middleware is via our [build-cloudflare-action](https://github.com/appwarden/build-cloudflare-action); see the [Cloudflare integration guide](https://appwarden.io/docs/guides/cloudflare-middleware-integration#1-set-up-the-github-actions-workflow) for workflow details. If you prefer to manage your own Cloudflare Worker instead of using the GitHub Action, you can mount the universal Cloudflare middleware directly using the `@appwarden/middleware/cloudflare` bundle:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
// src/worker.ts
|
|
108
|
+
import { createAppwardenMiddleware } from "@appwarden/middleware/cloudflare"
|
|
109
|
+
|
|
110
|
+
const appwardenHandler = createAppwardenMiddleware((cloudflare) => ({
|
|
111
|
+
debug: cloudflare.env.DEBUG === "true",
|
|
112
|
+
lockPageSlug: cloudflare.env.LOCK_PAGE_SLUG,
|
|
113
|
+
appwardenApiToken: cloudflare.env.APPWARDEN_API_TOKEN,
|
|
114
|
+
contentSecurityPolicy: {
|
|
115
|
+
mode: cloudflare.env.CSP_MODE,
|
|
116
|
+
directives: cloudflare.env.CSP_DIRECTIVES,
|
|
117
|
+
},
|
|
118
|
+
}))
|
|
24
119
|
|
|
25
|
-
|
|
120
|
+
export default {
|
|
121
|
+
fetch(request: Request, env: CloudflareEnv, ctx: ExecutionContext) {
|
|
122
|
+
return appwardenHandler(request, env, ctx)
|
|
123
|
+
},
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
See the Cloudflare integration docs on [appwarden.io](https://appwarden.io/docs) for environment variable setup and deployment details.
|
|
128
|
+
|
|
129
|
+
#### 1.2 Cloudflare framework adapters
|
|
130
|
+
|
|
131
|
+
If you cannot use `build-cloudflare-action`, you can mount Appwarden inside your application using framework-specific adapters.
|
|
132
|
+
|
|
133
|
+
> Currently, framework adapters do not automatically reflect your Appwarden domain configuration. You must manually provide the `lockPageSlug` and `contentSecurityPolicy` configuration in your code.
|
|
134
|
+
|
|
135
|
+
##### Astro on Cloudflare
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
// src/middleware.ts
|
|
139
|
+
import { sequence } from "astro:middleware"
|
|
140
|
+
import { createAppwardenMiddleware } from "@appwarden/middleware/cloudflare/astro"
|
|
141
|
+
|
|
142
|
+
const appwarden = createAppwardenMiddleware((cloudflare) => ({
|
|
143
|
+
lockPageSlug: cloudflare.env.APPWARDEN_LOCK_PAGE_SLUG,
|
|
144
|
+
appwardenApiToken: cloudflare.env.APPWARDEN_API_TOKEN,
|
|
145
|
+
debug: cloudflare.env.APPWARDEN_DEBUG === "true",
|
|
146
|
+
contentSecurityPolicy: {
|
|
147
|
+
mode: "enforced",
|
|
148
|
+
directives: {
|
|
149
|
+
"script-src": ["'self'", "{{nonce}}"],
|
|
150
|
+
"style-src": ["'self'", "{{nonce}}"],
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
}))
|
|
154
|
+
|
|
155
|
+
export const onRequest = sequence(appwarden)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
See the [Astro + Cloudflare guide](https://appwarden.io/docs/guides/astro-cloudflare) for more details.
|
|
159
|
+
|
|
160
|
+
##### React Router on Cloudflare
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
// app/root.tsx
|
|
164
|
+
import { createAppwardenMiddleware } from "@appwarden/middleware/cloudflare/react-router"
|
|
165
|
+
import type { CloudflareContext } from "@appwarden/middleware/cloudflare/react-router"
|
|
166
|
+
|
|
167
|
+
export const unstable_middleware = [
|
|
168
|
+
createAppwardenMiddleware((cloudflare: CloudflareContext) => ({
|
|
169
|
+
lockPageSlug: cloudflare.env.APPWARDEN_LOCK_PAGE_SLUG,
|
|
170
|
+
appwardenApiToken: cloudflare.env.APPWARDEN_API_TOKEN,
|
|
171
|
+
debug: cloudflare.env.APPWARDEN_DEBUG === "true",
|
|
172
|
+
contentSecurityPolicy: {
|
|
173
|
+
mode: "enforced",
|
|
174
|
+
directives: {
|
|
175
|
+
"script-src": ["'self'", "{{nonce}}"],
|
|
176
|
+
"style-src": ["'self'", "{{nonce}}"],
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
})),
|
|
180
|
+
]
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
See the [React Router + Cloudflare guide](https://appwarden.io/docs/guides/react-router-cloudflare) for full usage and context setup.
|
|
184
|
+
|
|
185
|
+
##### TanStack Start on Cloudflare
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
// src/start.ts
|
|
189
|
+
import { createStart } from "@tanstack/react-start"
|
|
190
|
+
import { createAppwardenMiddleware } from "@appwarden/middleware/cloudflare/tanstack-start"
|
|
191
|
+
import type { TanStackStartCloudflareContext } from "@appwarden/middleware/cloudflare/tanstack-start"
|
|
192
|
+
|
|
193
|
+
const appwardenMiddleware = createAppwardenMiddleware(
|
|
194
|
+
(cloudflare: TanStackStartCloudflareContext) => ({
|
|
195
|
+
lockPageSlug: cloudflare.env.APPWARDEN_LOCK_PAGE_SLUG,
|
|
196
|
+
appwardenApiToken: cloudflare.env.APPWARDEN_API_TOKEN,
|
|
197
|
+
debug: cloudflare.env.APPWARDEN_DEBUG === "true",
|
|
198
|
+
contentSecurityPolicy: {
|
|
199
|
+
mode: "enforced",
|
|
200
|
+
directives: {
|
|
201
|
+
"script-src": ["'self'", "{{nonce}}"],
|
|
202
|
+
"style-src": ["'self'", "{{nonce}}"],
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
}),
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
export const start = createStart(() => ({
|
|
209
|
+
requestMiddleware: [appwardenMiddleware],
|
|
210
|
+
}))
|
|
211
|
+
```
|
|
26
212
|
|
|
27
|
-
|
|
213
|
+
See the [TanStack Start + Cloudflare guide](https://appwarden.io/docs/guides/tanstack-start-cloudflare) for more details.
|
|
28
214
|
|
|
29
|
-
|
|
215
|
+
##### Next.js on Cloudflare (OpenNext)
|
|
30
216
|
|
|
31
|
-
|
|
217
|
+
```ts
|
|
218
|
+
// middleware.ts or proxy.ts
|
|
219
|
+
import { createAppwardenMiddleware } from "@appwarden/middleware/cloudflare/nextjs"
|
|
32
220
|
|
|
33
|
-
|
|
221
|
+
export const config = {
|
|
222
|
+
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
|
|
223
|
+
}
|
|
34
224
|
|
|
35
|
-
|
|
225
|
+
export default createAppwardenMiddleware((cloudflare) => ({
|
|
226
|
+
lockPageSlug: cloudflare.env.APPWARDEN_LOCK_PAGE_SLUG,
|
|
227
|
+
appwardenApiToken: cloudflare.env.APPWARDEN_API_TOKEN,
|
|
228
|
+
debug: cloudflare.env.APPWARDEN_DEBUG === "true",
|
|
229
|
+
// Headers-only CSP (no HTML rewriting, no nonce support; do not use `{{nonce}}` here)
|
|
230
|
+
contentSecurityPolicy: {
|
|
231
|
+
mode: "report-only",
|
|
232
|
+
directives: {
|
|
233
|
+
"script-src": ["'self'"],
|
|
234
|
+
"style-src": ["'self'", "'unsafe-inline'"],
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
}))
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
This adapter applies CSP **headers only** before origin (no HTML rewriting, no nonce injection). Nonce-based CSP (`{{nonce}}`) is **not supported** in this adapter; CSP directives must not include `{{nonce}}`.
|
|
36
241
|
|
|
37
|
-
|
|
242
|
+
### 2. Vercel
|
|
38
243
|
|
|
39
|
-
|
|
244
|
+
To use Appwarden as Vercel Edge Middleware, use the `@appwarden/middleware/vercel` bundle:
|
|
40
245
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
246
|
+
```ts
|
|
247
|
+
// middleware.ts (Next.js app on Vercel)
|
|
248
|
+
import { createAppwardenMiddleware } from "@appwarden/middleware/vercel"
|
|
249
|
+
import type { VercelMiddlewareFunction } from "@appwarden/middleware/vercel"
|
|
45
250
|
|
|
46
|
-
|
|
251
|
+
const appwardenMiddleware: VercelMiddlewareFunction = createAppwardenMiddleware(
|
|
252
|
+
{
|
|
253
|
+
// Edge Config or Upstash KV URL
|
|
254
|
+
cacheUrl: process.env.APPWARDEN_CACHE_URL!,
|
|
255
|
+
// Required when using Vercel Edge Config
|
|
256
|
+
vercelApiToken: process.env.APPWARDEN_VERCEL_API_TOKEN!,
|
|
257
|
+
appwardenApiToken: process.env.APPWARDEN_API_TOKEN!,
|
|
258
|
+
lockPageSlug: "/maintenance",
|
|
259
|
+
contentSecurityPolicy: {
|
|
260
|
+
mode: "report-only",
|
|
261
|
+
directives: {
|
|
262
|
+
"script-src": ["'self'"],
|
|
263
|
+
"style-src": ["'self'", "'unsafe-inline'"],
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
export default appwardenMiddleware
|
|
270
|
+
```
|
|
47
271
|
|
|
272
|
+
Nonce-based CSP (`{{nonce}}`) is **not supported** in Vercel Edge Middleware; CSP directives must not include `{{nonce}}`.
|
|
273
|
+
|
|
274
|
+
## Supported platforms
|
|
275
|
+
|
|
276
|
+
- [All websites on Cloudflare](https://appwarden.io/docs/guides/cloudflare-middleware-integration)
|
|
277
|
+
- [Astro on Cloudflare](https://appwarden.io/docs/guides/astro-cloudflare)
|
|
278
|
+
- [React Router on Cloudflare](https://appwarden.io/docs/guides/react-router-cloudflare)
|
|
279
|
+
- [TanStack Start on Cloudflare](https://appwarden.io/docs/guides/tanstack-start-cloudflare)
|
|
280
|
+
- [Next.js on Cloudflare (OpenNext)](https://appwarden.io/docs/guides/nextjs-cloudflare)
|
|
48
281
|
- [All websites on Vercel](https://appwarden.io/docs/guides/vercel-integration)
|
|
49
282
|
|
|
50
283
|
## Contributing
|
|
@@ -80,6 +313,8 @@ pnpm test
|
|
|
80
313
|
|
|
81
314
|
## Security
|
|
82
315
|
|
|
316
|
+
Please review our [security policy](SECURITY.md) for details on how we handle vulnerabilities and how to report a security issue.
|
|
317
|
+
|
|
83
318
|
This package is published with npm trusted publishers, to prevent npm token exfiltration, and provenance enabled, which provides a verifiable link between the published package and its source code. For more information, see [npm provenance documentation](https://docs.npmjs.com/generating-provenance-statements).
|
|
84
319
|
|
|
85
320
|
## License
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {
|
|
2
|
+
UseCSPInputSchema,
|
|
3
|
+
isHTMLResponse
|
|
4
|
+
} from "./chunk-HCGLR3Z3.js";
|
|
5
|
+
import {
|
|
6
|
+
debug,
|
|
7
|
+
makeCSPHeader,
|
|
8
|
+
printMessage
|
|
9
|
+
} from "./chunk-ZBYVJ3HA.js";
|
|
10
|
+
|
|
11
|
+
// src/middlewares/use-content-security-policy.ts
|
|
12
|
+
var AppendAttribute = (attribute, nonce) => ({
|
|
13
|
+
element: function(element) {
|
|
14
|
+
element.setAttribute(attribute, nonce);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
var useContentSecurityPolicy = (input) => {
|
|
18
|
+
const parsedInput = UseCSPInputSchema.safeParse(input);
|
|
19
|
+
if (!parsedInput.success) {
|
|
20
|
+
throw parsedInput.error;
|
|
21
|
+
}
|
|
22
|
+
const config = parsedInput.data;
|
|
23
|
+
return async (context, next) => {
|
|
24
|
+
await next();
|
|
25
|
+
const { response } = context;
|
|
26
|
+
if (
|
|
27
|
+
// if the csp is disabled
|
|
28
|
+
!["enforced", "report-only"].includes(config.mode)
|
|
29
|
+
) {
|
|
30
|
+
debug(printMessage("csp is disabled"));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (response.headers.has("Content-Type") && !isHTMLResponse(response)) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const cspNonce = crypto.randomUUID();
|
|
37
|
+
const [cspHeaderName, cspHeaderValue] = makeCSPHeader(
|
|
38
|
+
cspNonce,
|
|
39
|
+
config.directives,
|
|
40
|
+
config.mode
|
|
41
|
+
);
|
|
42
|
+
const nextResponse = new Response(response.body, response);
|
|
43
|
+
nextResponse.headers.set(cspHeaderName, cspHeaderValue);
|
|
44
|
+
nextResponse.headers.set("content-type", "text/html; charset=utf-8");
|
|
45
|
+
context.response = new HTMLRewriter().on("style", AppendAttribute("nonce", cspNonce)).on("script", AppendAttribute("nonce", cspNonce)).transform(nextResponse);
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export {
|
|
50
|
+
useContentSecurityPolicy
|
|
51
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// src/constants.ts
|
|
2
|
+
var LOCKDOWN_TEST_EXPIRY_MS = 5 * 60 * 1e3;
|
|
3
|
+
var errors = { badCacheConnection: "BAD_CACHE_CONNECTION" };
|
|
4
|
+
var globalErrors = [errors.badCacheConnection];
|
|
5
|
+
var APPWARDEN_TEST_ROUTE = "/_appwarden/test";
|
|
6
|
+
var APPWARDEN_CACHE_KEY = "appwarden-lock";
|
|
7
|
+
|
|
8
|
+
// src/schemas/use-content-security-policy.ts
|
|
9
|
+
import { z as z2 } from "zod";
|
|
10
|
+
|
|
11
|
+
// src/types/csp.ts
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
var stringySchema = z.union([z.array(z.string()), z.string(), z.boolean()]);
|
|
14
|
+
var ContentSecurityPolicySchema = z.object({
|
|
15
|
+
"default-src": stringySchema.optional(),
|
|
16
|
+
"script-src": stringySchema.optional(),
|
|
17
|
+
"style-src": stringySchema.optional(),
|
|
18
|
+
"img-src": stringySchema.optional(),
|
|
19
|
+
"connect-src": stringySchema.optional(),
|
|
20
|
+
"font-src": stringySchema.optional(),
|
|
21
|
+
"object-src": stringySchema.optional(),
|
|
22
|
+
"media-src": stringySchema.optional(),
|
|
23
|
+
"frame-src": stringySchema.optional(),
|
|
24
|
+
sandbox: stringySchema.optional(),
|
|
25
|
+
"report-uri": stringySchema.optional(),
|
|
26
|
+
"child-src": stringySchema.optional(),
|
|
27
|
+
"form-action": stringySchema.optional(),
|
|
28
|
+
"frame-ancestors": stringySchema.optional(),
|
|
29
|
+
"plugin-types": stringySchema.optional(),
|
|
30
|
+
"base-uri": stringySchema.optional(),
|
|
31
|
+
"report-to": stringySchema.optional(),
|
|
32
|
+
"worker-src": stringySchema.optional(),
|
|
33
|
+
"manifest-src": stringySchema.optional(),
|
|
34
|
+
"prefetch-src": stringySchema.optional(),
|
|
35
|
+
"navigate-to": stringySchema.optional(),
|
|
36
|
+
"require-sri-for": stringySchema.optional(),
|
|
37
|
+
"block-all-mixed-content": stringySchema.optional(),
|
|
38
|
+
"upgrade-insecure-requests": stringySchema.optional(),
|
|
39
|
+
"trusted-types": stringySchema.optional(),
|
|
40
|
+
"require-trusted-types-for": stringySchema.optional()
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// src/utils/request-checks.ts
|
|
44
|
+
function isHTMLResponse(response) {
|
|
45
|
+
return response.headers.get("Content-Type")?.includes("text/html") ?? false;
|
|
46
|
+
}
|
|
47
|
+
function isHTMLRequest(request) {
|
|
48
|
+
return request.headers.get("accept")?.includes("text/html") ?? false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/schemas/use-content-security-policy.ts
|
|
52
|
+
var CSPDirectivesSchema = z2.union([
|
|
53
|
+
z2.string(),
|
|
54
|
+
ContentSecurityPolicySchema
|
|
55
|
+
]);
|
|
56
|
+
var CSPModeSchema = z2.union([
|
|
57
|
+
z2.literal("disabled"),
|
|
58
|
+
z2.literal("report-only"),
|
|
59
|
+
z2.literal("enforced")
|
|
60
|
+
]).optional().default("disabled");
|
|
61
|
+
var UseCSPInputSchema = z2.object({
|
|
62
|
+
mode: CSPModeSchema,
|
|
63
|
+
directives: CSPDirectivesSchema.optional().refine(
|
|
64
|
+
(val) => {
|
|
65
|
+
try {
|
|
66
|
+
if (typeof val === "string") {
|
|
67
|
+
JSON.parse(val);
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
{ message: "DirectivesBadParse" /* DirectivesBadParse */ }
|
|
75
|
+
).transform(
|
|
76
|
+
(val) => typeof val === "string" ? JSON.parse(val) : val
|
|
77
|
+
)
|
|
78
|
+
}).refine(
|
|
79
|
+
(values) => (
|
|
80
|
+
// validate that directives are provided when the mode is "report-only" or "enforced"
|
|
81
|
+
["report-only", "enforced"].includes(values.mode) ? !!values.directives : true
|
|
82
|
+
),
|
|
83
|
+
{ path: ["directives"], message: "DirectivesRequired" /* DirectivesRequired */ }
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
export {
|
|
87
|
+
LOCKDOWN_TEST_EXPIRY_MS,
|
|
88
|
+
errors,
|
|
89
|
+
globalErrors,
|
|
90
|
+
APPWARDEN_TEST_ROUTE,
|
|
91
|
+
APPWARDEN_CACHE_KEY,
|
|
92
|
+
CSPDirectivesSchema,
|
|
93
|
+
CSPModeSchema,
|
|
94
|
+
UseCSPInputSchema,
|
|
95
|
+
isHTMLResponse,
|
|
96
|
+
isHTMLRequest
|
|
97
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MemoryCache
|
|
3
|
+
} from "./chunk-SUZPTFWY.js";
|
|
4
|
+
import {
|
|
5
|
+
APPWARDEN_CACHE_KEY,
|
|
6
|
+
APPWARDEN_TEST_ROUTE
|
|
7
|
+
} from "./chunk-HCGLR3Z3.js";
|
|
8
|
+
import {
|
|
9
|
+
deleteEdgeValue,
|
|
10
|
+
getLockValue,
|
|
11
|
+
store,
|
|
12
|
+
syncEdgeValue
|
|
13
|
+
} from "./chunk-ZBYVJ3HA.js";
|
|
14
|
+
|
|
15
|
+
// src/core/check-lock-status.ts
|
|
16
|
+
var createContext = async (config) => {
|
|
17
|
+
const requestUrl = new URL(config.request.url);
|
|
18
|
+
const keyName = APPWARDEN_CACHE_KEY;
|
|
19
|
+
const provider = "cloudflare-cache";
|
|
20
|
+
const edgeCache = store.json(
|
|
21
|
+
{
|
|
22
|
+
serviceOrigin: requestUrl.origin,
|
|
23
|
+
cache: await caches.open("appwarden:lock")
|
|
24
|
+
},
|
|
25
|
+
keyName
|
|
26
|
+
);
|
|
27
|
+
return {
|
|
28
|
+
keyName,
|
|
29
|
+
request: config.request,
|
|
30
|
+
edgeCache,
|
|
31
|
+
requestUrl,
|
|
32
|
+
provider,
|
|
33
|
+
debug: config.debug ?? false,
|
|
34
|
+
lockPageSlug: config.lockPageSlug,
|
|
35
|
+
appwardenApiToken: config.appwardenApiToken,
|
|
36
|
+
appwardenApiHostname: config.appwardenApiHostname,
|
|
37
|
+
waitUntil: config.waitUntil
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
var resolveLockStatus = async (context) => {
|
|
41
|
+
const { lockValue, shouldDeleteEdgeValue } = await getLockValue(context);
|
|
42
|
+
if (shouldDeleteEdgeValue) {
|
|
43
|
+
await deleteEdgeValue(context);
|
|
44
|
+
}
|
|
45
|
+
const isTestRoute = context.requestUrl.pathname === APPWARDEN_TEST_ROUTE;
|
|
46
|
+
const isTestLock = isTestRoute && !MemoryCache.isTestExpired(lockValue) && !!lockValue;
|
|
47
|
+
return {
|
|
48
|
+
isLocked: !!lockValue?.isLocked || isTestLock,
|
|
49
|
+
isTestLock,
|
|
50
|
+
lockValue,
|
|
51
|
+
wasDeleted: shouldDeleteEdgeValue ?? false
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
var checkLockStatus = async (config) => {
|
|
55
|
+
const context = await createContext(config);
|
|
56
|
+
let { isLocked, isTestLock, lockValue, wasDeleted } = await resolveLockStatus(context);
|
|
57
|
+
if (MemoryCache.isExpired(lockValue) || wasDeleted) {
|
|
58
|
+
if (!lockValue || wasDeleted || lockValue.isLocked) {
|
|
59
|
+
await syncEdgeValue(context);
|
|
60
|
+
({ isLocked, isTestLock } = await resolveLockStatus(context));
|
|
61
|
+
} else {
|
|
62
|
+
config.waitUntil(syncEdgeValue(context));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { isLocked, isTestLock };
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export {
|
|
69
|
+
checkLockStatus
|
|
70
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
LOCKDOWN_TEST_EXPIRY_MS
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-HCGLR3Z3.js";
|
|
4
4
|
|
|
5
5
|
// src/utils/build-lock-page-url.ts
|
|
6
6
|
function normalizeLockPageSlug(lockPageSlug) {
|
|
@@ -81,66 +81,10 @@ var MemoryCache = class {
|
|
|
81
81
|
};
|
|
82
82
|
};
|
|
83
83
|
|
|
84
|
-
// src/utils/errors.ts
|
|
85
|
-
var errorsMap = {
|
|
86
|
-
mode: '`CSP_MODE` must be one of "disabled", "report-only", or "enforced"',
|
|
87
|
-
directives: {
|
|
88
|
-
["DirectivesRequired" /* DirectivesRequired */]: '`CSP_DIRECTIVES` must be provided when `CSP_MODE` is "report-only" or "enforced"',
|
|
89
|
-
["DirectivesBadParse" /* DirectivesBadParse */]: "Failed to parse `CSP_DIRECTIVES`. Is it a valid JSON string?"
|
|
90
|
-
},
|
|
91
|
-
appwardenApiToken: "Please provide a valid `appwardenApiToken`. Learn more at https://appwarden.com/docs/guides/api-token-management."
|
|
92
|
-
};
|
|
93
|
-
var getErrors = (error) => {
|
|
94
|
-
const matches = [];
|
|
95
|
-
const errors = [...Object.entries(error.flatten().fieldErrors)];
|
|
96
|
-
for (const issue of error.issues) {
|
|
97
|
-
errors.push(
|
|
98
|
-
...Object.entries(
|
|
99
|
-
"returnTypeError" in issue ? issue.returnTypeError.flatten().fieldErrors : {}
|
|
100
|
-
)
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
for (const [field, maybeSchemaErrorKey] of errors) {
|
|
104
|
-
let match = errorsMap[field];
|
|
105
|
-
if (match) {
|
|
106
|
-
if (match instanceof Object) {
|
|
107
|
-
if (maybeSchemaErrorKey) {
|
|
108
|
-
match = match[maybeSchemaErrorKey[0]];
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
matches.push(match);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return matches;
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
// src/schemas/helpers.ts
|
|
118
|
-
import { z } from "zod";
|
|
119
|
-
var BoolOrStringSchema = z.union([z.string(), z.boolean()]).optional();
|
|
120
|
-
var BooleanSchema = BoolOrStringSchema.transform((val) => {
|
|
121
|
-
if (val === "true" || val === true) {
|
|
122
|
-
return true;
|
|
123
|
-
} else if (val === "false" || val === false) {
|
|
124
|
-
return false;
|
|
125
|
-
}
|
|
126
|
-
throw new Error("Invalid value");
|
|
127
|
-
});
|
|
128
|
-
var AppwardenApiTokenSchema = z.string().refine((val) => !!val, { message: "appwardenApiToken is required" });
|
|
129
|
-
var LockValue = z.object({
|
|
130
|
-
isLocked: z.number(),
|
|
131
|
-
isLockedTest: z.number(),
|
|
132
|
-
lastCheck: z.number(),
|
|
133
|
-
code: z.string()
|
|
134
|
-
});
|
|
135
|
-
|
|
136
84
|
export {
|
|
137
85
|
buildLockPageUrl,
|
|
138
86
|
isOnLockPage,
|
|
139
87
|
TEMPORARY_REDIRECT_STATUS,
|
|
140
88
|
createRedirect,
|
|
141
|
-
|
|
142
|
-
MemoryCache,
|
|
143
|
-
BooleanSchema,
|
|
144
|
-
AppwardenApiTokenSchema,
|
|
145
|
-
LockValue
|
|
89
|
+
MemoryCache
|
|
146
90
|
};
|