@appwarden/middleware 1.0.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/LICENSE +21 -0
- package/README.md +110 -0
- package/chunk-47MLTBFC.js +108 -0
- package/chunk-JWUAFJ2E.js +29 -0
- package/chunk-NZNMFDZ7.js +145 -0
- package/chunk-QEFORWCW.js +84 -0
- package/cloudflare-2PkEr25r.d.ts +99 -0
- package/cloudflare.d.ts +45 -0
- package/cloudflare.js +389 -0
- package/index.d.ts +68 -0
- package/index.js +26 -0
- package/package.json +70 -0
- package/use-content-security-policy-C89AROtC.d.ts +414 -0
- package/vercel.d.ts +26 -0
- package/vercel.js +341 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Appwarden
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# @appwarden/middleware
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
[](https://www.npmjs.com/package/@appwarden/middleware)
|
|
5
|
+
[](https://docs.npmjs.com/generating-provenance-statements)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
> Read the docs [to learn more](https://appwarden.io/docs)
|
|
9
|
+
|
|
10
|
+
## Stop in progress attacks in their tracks
|
|
11
|
+
|
|
12
|
+
### Core Features
|
|
13
|
+
|
|
14
|
+
- **Instant Quarantine**: Immediately redirects all visitors to a maintenance page when activated
|
|
15
|
+
- **Discord Integration**: Trigger lockdowns via Discord commands (`/quarantine lock your.app.io`)
|
|
16
|
+
- **Nonce-based Content Security Policy**: On Cloudflare, deploy a nonce-based Content Security Policy to supercharge your website security
|
|
17
|
+
|
|
18
|
+
### Performance Optimizations
|
|
19
|
+
|
|
20
|
+
- **Background Synchronization**: Uses `waitUntil()` to update cache state without blocking responses
|
|
21
|
+
- **Minimal Runtime Overhead**: Lightweight implementation with negligible performance impact
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
Compatible with websites powered by [Cloudflare](https://developers.cloudflare.com/pages/) or [Vercel](https://vercel.com).
|
|
26
|
+
|
|
27
|
+
For detailed usage instructions, please refer to our [documentation](https://appwarden.io/docs).
|
|
28
|
+
|
|
29
|
+
### Cloudflare
|
|
30
|
+
|
|
31
|
+
We recommend using the [`@appwarden/build-cloudflare-action`](https://github.com/appwarden/build-cloudflare-action) Github Action to deploy automatically on Cloudflare.
|
|
32
|
+
|
|
33
|
+
> Read the docs [to get started](https://appwarden.io/docs/guides/cloudflare-integration)
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import {
|
|
37
|
+
withAppwarden,
|
|
38
|
+
useContentSecurityPolicy,
|
|
39
|
+
} from "@appwarden/middleware/cloudflare"
|
|
40
|
+
|
|
41
|
+
export default {
|
|
42
|
+
fetch: withAppwarden((context) => ({
|
|
43
|
+
debug: context.env.DEBUG,
|
|
44
|
+
lockPageSlug: context.env.LOCK_PAGE_SLUG,
|
|
45
|
+
appwardenApiToken: context.env.APPWARDEN_API_TOKEN,
|
|
46
|
+
middleware: {
|
|
47
|
+
before: [
|
|
48
|
+
useContentSecurityPolicy({
|
|
49
|
+
mode: "enforced",
|
|
50
|
+
directives: {
|
|
51
|
+
"script-src": ["self", "{{nonce}}"],
|
|
52
|
+
"style-src": ["self", "{{nonce}}"],
|
|
53
|
+
},
|
|
54
|
+
}),
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
})),
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Vercel
|
|
62
|
+
|
|
63
|
+
> Read the docs [to get started](https://appwarden.io/docs/guides/vercel-integration)
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { withAppwarden } from "@appwarden/middleware/vercel"
|
|
67
|
+
|
|
68
|
+
export default withAppwarden({
|
|
69
|
+
cacheUrl: process.env.EDGE_CONFIG_URL || process.env.UPSTASH_URL,
|
|
70
|
+
appwardenApiToken: process.env.APPWARDEN_API_TOKEN,
|
|
71
|
+
vercelApiToken: process.env.VERCEL_API_TOKEN,
|
|
72
|
+
lockPageSlug: "/maintenance",
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
// Configures middleware to match all routes
|
|
76
|
+
export const config = {
|
|
77
|
+
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Contributing
|
|
82
|
+
|
|
83
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
84
|
+
|
|
85
|
+
1. Fork the repository
|
|
86
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
87
|
+
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
88
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
89
|
+
5. Open a Pull Request
|
|
90
|
+
|
|
91
|
+
## Development
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Install dependencies
|
|
95
|
+
pnpm install
|
|
96
|
+
|
|
97
|
+
# Build the package
|
|
98
|
+
pnpm build
|
|
99
|
+
|
|
100
|
+
# Run tests
|
|
101
|
+
pnpm test
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Security
|
|
105
|
+
|
|
106
|
+
This package is published with npm 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).
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LOCKDOWN_TEST_EXPIRY_MS
|
|
3
|
+
} from "./chunk-JWUAFJ2E.js";
|
|
4
|
+
|
|
5
|
+
// src/utils/errors.ts
|
|
6
|
+
var errorsMap = {
|
|
7
|
+
mode: '`CSP_MODE` must be one of "disabled", "report-only", or "enforced"',
|
|
8
|
+
directives: {
|
|
9
|
+
["DirectivesRequired" /* DirectivesRequired */]: '`CSP_DIRECTIVES` must be provided when `CSP_MODE` is "report-only" or "enforced"',
|
|
10
|
+
["DirectivesBadParse" /* DirectivesBadParse */]: "Failed to parse `CSP_DIRECTIVES`. Is it a valid JSON string?"
|
|
11
|
+
},
|
|
12
|
+
appwardenApiToken: "Please provide a valid `appwardenApiToken`. Learn more at https://appwarden.com/docs/guides/api-token-management."
|
|
13
|
+
};
|
|
14
|
+
var getErrors = (error) => {
|
|
15
|
+
const matches = [];
|
|
16
|
+
const errors = [...Object.entries(error.flatten().fieldErrors)];
|
|
17
|
+
for (const issue of error.issues) {
|
|
18
|
+
errors.push(
|
|
19
|
+
...Object.entries(
|
|
20
|
+
"returnTypeError" in issue ? issue.returnTypeError.flatten().fieldErrors : {}
|
|
21
|
+
)
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
for (const [field, maybeSchemaErrorKey] of errors) {
|
|
25
|
+
let match = errorsMap[field];
|
|
26
|
+
if (match) {
|
|
27
|
+
if (match instanceof Object) {
|
|
28
|
+
if (maybeSchemaErrorKey) {
|
|
29
|
+
match = match[maybeSchemaErrorKey[0]];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
matches.push(match);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return matches;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// src/utils/memory-cache.ts
|
|
39
|
+
var MemoryCache = class {
|
|
40
|
+
cache = /* @__PURE__ */ new Map();
|
|
41
|
+
maxSize;
|
|
42
|
+
constructor(options) {
|
|
43
|
+
this.maxSize = options.maxSize;
|
|
44
|
+
}
|
|
45
|
+
get(key) {
|
|
46
|
+
let item;
|
|
47
|
+
if (this.cache.has(key)) {
|
|
48
|
+
item = this.cache.get(key);
|
|
49
|
+
this.cache.delete(key);
|
|
50
|
+
if (item !== void 0) {
|
|
51
|
+
this.cache.set(key, item);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return item;
|
|
55
|
+
}
|
|
56
|
+
put(key, value) {
|
|
57
|
+
if (this.cache.has(key)) {
|
|
58
|
+
this.cache.delete(key);
|
|
59
|
+
} else if (this.cache.size >= this.maxSize) {
|
|
60
|
+
const firstKey = this.cache.keys().next().value;
|
|
61
|
+
if (firstKey !== void 0) {
|
|
62
|
+
this.cache.delete(firstKey);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
this.cache.set(key, value);
|
|
66
|
+
}
|
|
67
|
+
getValues() {
|
|
68
|
+
return this.cache;
|
|
69
|
+
}
|
|
70
|
+
// the default value will be expired here
|
|
71
|
+
static isExpired = (lockValue) => {
|
|
72
|
+
if (!lockValue) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
return Date.now() > lockValue.lastCheck + 3e4;
|
|
76
|
+
};
|
|
77
|
+
static isTestExpired = (lockValue) => {
|
|
78
|
+
if (!lockValue) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
return Date.now() > lockValue.isLockedTest + LOCKDOWN_TEST_EXPIRY_MS;
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// src/schemas/helpers.ts
|
|
86
|
+
import { z } from "zod";
|
|
87
|
+
var BoolOrStringSchema = z.union([z.string(), z.boolean()]).optional();
|
|
88
|
+
var BooleanSchema = BoolOrStringSchema.transform((val) => {
|
|
89
|
+
if (val === "true" || val === true) {
|
|
90
|
+
return true;
|
|
91
|
+
} else if (val === "false" || val === false) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
throw new Error("Invalid value");
|
|
95
|
+
});
|
|
96
|
+
var LockValue = z.object({
|
|
97
|
+
isLocked: z.number(),
|
|
98
|
+
isLockedTest: z.number(),
|
|
99
|
+
lastCheck: z.number(),
|
|
100
|
+
code: z.string()
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
export {
|
|
104
|
+
getErrors,
|
|
105
|
+
MemoryCache,
|
|
106
|
+
BooleanSchema,
|
|
107
|
+
LockValue
|
|
108
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
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_USER_AGENT = "Appwarden-Monitor";
|
|
7
|
+
var APPWARDEN_CACHE_KEY = "appwarden-lock";
|
|
8
|
+
|
|
9
|
+
// src/utils/debug.ts
|
|
10
|
+
var debug = (...msg) => {
|
|
11
|
+
if (true) {
|
|
12
|
+
console.log(...msg);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// src/utils/print-message.ts
|
|
17
|
+
var addSlashes = (str) => str.replace(/[\\"'`]/g, "\\$&").replace(/\u0000/g, "\\0");
|
|
18
|
+
var printMessage = (message) => `[@appwarden/middleware] ${addSlashes(message)}`;
|
|
19
|
+
|
|
20
|
+
export {
|
|
21
|
+
LOCKDOWN_TEST_EXPIRY_MS,
|
|
22
|
+
errors,
|
|
23
|
+
globalErrors,
|
|
24
|
+
APPWARDEN_TEST_ROUTE,
|
|
25
|
+
APPWARDEN_USER_AGENT,
|
|
26
|
+
APPWARDEN_CACHE_KEY,
|
|
27
|
+
debug,
|
|
28
|
+
printMessage
|
|
29
|
+
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import {
|
|
2
|
+
debug,
|
|
3
|
+
printMessage
|
|
4
|
+
} from "./chunk-JWUAFJ2E.js";
|
|
5
|
+
|
|
6
|
+
// src/schemas/use-content-security-policy.ts
|
|
7
|
+
import { z as z2 } from "zod";
|
|
8
|
+
|
|
9
|
+
// src/types/csp.ts
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
var stringySchema = z.union([z.array(z.string()), z.string(), z.boolean()]);
|
|
12
|
+
var ContentSecurityPolicySchema = z.object({
|
|
13
|
+
"default-src": stringySchema.optional(),
|
|
14
|
+
"script-src": stringySchema.optional(),
|
|
15
|
+
"style-src": stringySchema.optional(),
|
|
16
|
+
"img-src": stringySchema.optional(),
|
|
17
|
+
"connect-src": stringySchema.optional(),
|
|
18
|
+
"font-src": stringySchema.optional(),
|
|
19
|
+
"object-src": stringySchema.optional(),
|
|
20
|
+
"media-src": stringySchema.optional(),
|
|
21
|
+
"frame-src": stringySchema.optional(),
|
|
22
|
+
sandbox: stringySchema.optional(),
|
|
23
|
+
"report-uri": stringySchema.optional(),
|
|
24
|
+
"child-src": stringySchema.optional(),
|
|
25
|
+
"form-action": stringySchema.optional(),
|
|
26
|
+
"frame-ancestors": stringySchema.optional(),
|
|
27
|
+
"plugin-types": stringySchema.optional(),
|
|
28
|
+
"base-uri": stringySchema.optional(),
|
|
29
|
+
"report-to": stringySchema.optional(),
|
|
30
|
+
"worker-src": stringySchema.optional(),
|
|
31
|
+
"manifest-src": stringySchema.optional(),
|
|
32
|
+
"prefetch-src": stringySchema.optional(),
|
|
33
|
+
"navigate-to": stringySchema.optional(),
|
|
34
|
+
"require-sri-for": stringySchema.optional(),
|
|
35
|
+
"block-all-mixed-content": stringySchema.optional(),
|
|
36
|
+
"upgrade-insecure-requests": stringySchema.optional(),
|
|
37
|
+
"trusted-types": stringySchema.optional(),
|
|
38
|
+
"require-trusted-types-for": stringySchema.optional()
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// src/schemas/use-content-security-policy.ts
|
|
42
|
+
var CSPDirectivesSchema = z2.union([
|
|
43
|
+
z2.string(),
|
|
44
|
+
ContentSecurityPolicySchema
|
|
45
|
+
]);
|
|
46
|
+
var CSPModeSchema = z2.union([
|
|
47
|
+
z2.literal("disabled"),
|
|
48
|
+
z2.literal("report-only"),
|
|
49
|
+
z2.literal("enforced")
|
|
50
|
+
]).optional().default("disabled");
|
|
51
|
+
var UseCSPInputSchema = z2.object({
|
|
52
|
+
mode: CSPModeSchema,
|
|
53
|
+
directives: CSPDirectivesSchema.optional().refine(
|
|
54
|
+
(val) => {
|
|
55
|
+
try {
|
|
56
|
+
if (typeof val === "string") {
|
|
57
|
+
JSON.parse(val);
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
{ message: "DirectivesBadParse" /* DirectivesBadParse */ }
|
|
65
|
+
).transform(
|
|
66
|
+
(val) => typeof val === "string" ? JSON.parse(val) : val
|
|
67
|
+
)
|
|
68
|
+
}).refine(
|
|
69
|
+
(values) => (
|
|
70
|
+
// validate that directives are provided when the mode is "report-only" or "enforced"
|
|
71
|
+
["report-only", "enforced"].includes(values.mode) ? !!values.directives : true
|
|
72
|
+
),
|
|
73
|
+
{ path: ["directives"], message: "DirectivesRequired" /* DirectivesRequired */ }
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
// src/utils/cloudflare/make-csp-header.ts
|
|
77
|
+
var addNonce = (value, cspNonce) => value.replace("{{nonce}}", `'nonce-${cspNonce}'`);
|
|
78
|
+
var makeCSPHeader = (cspNonce, directives, mode) => {
|
|
79
|
+
const namesSeen = /* @__PURE__ */ new Set(), result = [];
|
|
80
|
+
Object.entries(directives ?? {}).forEach(([originalName, value]) => {
|
|
81
|
+
const name = originalName.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
82
|
+
if (namesSeen.has(name)) {
|
|
83
|
+
throw new Error(`${originalName} is specified more than once`);
|
|
84
|
+
}
|
|
85
|
+
namesSeen.add(name);
|
|
86
|
+
if (Array.isArray(value)) {
|
|
87
|
+
value = addNonce(value.join(" "), cspNonce);
|
|
88
|
+
} else if (value === true) {
|
|
89
|
+
value = "";
|
|
90
|
+
}
|
|
91
|
+
if (value) {
|
|
92
|
+
result.push(`${name} ${addNonce(value, cspNonce)}`);
|
|
93
|
+
} else if (value !== false) {
|
|
94
|
+
result.push(name);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
return [
|
|
98
|
+
mode === "enforced" ? "Content-Security-Policy" : "Content-Security-Policy-Report-Only",
|
|
99
|
+
result.join("; ")
|
|
100
|
+
];
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// src/middlewares/use-content-security-policy.ts
|
|
104
|
+
var AppendAttribute = (attribute, nonce) => ({
|
|
105
|
+
element: function(element) {
|
|
106
|
+
element.setAttribute(attribute, nonce);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
var useContentSecurityPolicy = (input) => {
|
|
110
|
+
const parsedInput = UseCSPInputSchema.safeParse(input);
|
|
111
|
+
if (!parsedInput.success) {
|
|
112
|
+
throw parsedInput.error;
|
|
113
|
+
}
|
|
114
|
+
const config = parsedInput.data;
|
|
115
|
+
return async (context, next) => {
|
|
116
|
+
await next();
|
|
117
|
+
const { response } = context;
|
|
118
|
+
if (
|
|
119
|
+
// if the csp is disabled
|
|
120
|
+
!["enforced", "report-only"].includes(config.mode)
|
|
121
|
+
) {
|
|
122
|
+
debug(printMessage("csp is disabled"));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (response.headers.has("Content-Type") && !response.headers.get("Content-Type")?.includes("text/html")) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const cspNonce = btoa(crypto.getRandomValues(new Uint32Array(2)).toString());
|
|
129
|
+
const [cspHeaderName, cspHeaderValue] = makeCSPHeader(
|
|
130
|
+
cspNonce,
|
|
131
|
+
config.directives,
|
|
132
|
+
config.mode
|
|
133
|
+
);
|
|
134
|
+
const nextResponse = new Response(response.body, response);
|
|
135
|
+
nextResponse.headers.set(cspHeaderName, cspHeaderValue);
|
|
136
|
+
nextResponse.headers.set("content-type", "text/html; charset=utf-8");
|
|
137
|
+
context.response = new HTMLRewriter().on("style", AppendAttribute("nonce", cspNonce)).on("script", AppendAttribute("nonce", cspNonce)).transform(nextResponse);
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export {
|
|
142
|
+
CSPDirectivesSchema,
|
|
143
|
+
CSPModeSchema,
|
|
144
|
+
useContentSecurityPolicy
|
|
145
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// src/utils/is-cache-url.ts
|
|
2
|
+
var getEdgeConfigId = (value = "") => {
|
|
3
|
+
if (isValidCacheUrl.edgeConfig(value)) {
|
|
4
|
+
const url = new URL(value);
|
|
5
|
+
return url.pathname.replace("/", "");
|
|
6
|
+
}
|
|
7
|
+
return void 0;
|
|
8
|
+
};
|
|
9
|
+
var isCacheUrl = {
|
|
10
|
+
/**
|
|
11
|
+
* Checks if a URL is a valid Edge Config URL (hostname check only)
|
|
12
|
+
* @param value The URL to check
|
|
13
|
+
* @returns True if the URL is a valid Edge Config URL, false otherwise
|
|
14
|
+
*/
|
|
15
|
+
edgeConfig: (value = "") => {
|
|
16
|
+
try {
|
|
17
|
+
const url = new URL(value);
|
|
18
|
+
return url.hostname === "edge-config.vercel.com";
|
|
19
|
+
} catch {
|
|
20
|
+
return /^https:\/\/edge-config\.vercel\.com\//.test(value);
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
/**
|
|
24
|
+
* Checks if a URL is a valid Upstash URL (hostname check only)
|
|
25
|
+
* @param value The URL to check
|
|
26
|
+
* @returns True if the URL is a valid Upstash URL, false otherwise
|
|
27
|
+
*/
|
|
28
|
+
upstash: (value = "") => {
|
|
29
|
+
try {
|
|
30
|
+
const url = new URL(value);
|
|
31
|
+
return url.hostname.endsWith(".upstash.io");
|
|
32
|
+
} catch {
|
|
33
|
+
return /^.*\.upstash\.io$/.test(value || "");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var isValidCacheUrl = {
|
|
38
|
+
/**
|
|
39
|
+
* Strictly validates an Edge Config URL
|
|
40
|
+
* @param value The URL to validate
|
|
41
|
+
* @returns True if the URL is a valid Edge Config URL, false otherwise
|
|
42
|
+
*/
|
|
43
|
+
edgeConfig: (value = "") => {
|
|
44
|
+
try {
|
|
45
|
+
const url = new URL(value);
|
|
46
|
+
return (
|
|
47
|
+
// Only allow HTTPS for security
|
|
48
|
+
url.protocol === "https:" && // Exact hostname match
|
|
49
|
+
url.hostname === "edge-config.vercel.com" && // Path must start with /ecfg_
|
|
50
|
+
url.pathname.startsWith("/ecfg_") && // Must have a token parameter
|
|
51
|
+
url.searchParams.has("token") && // Token should not be empty
|
|
52
|
+
url.searchParams.get("token") !== ""
|
|
53
|
+
);
|
|
54
|
+
} catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
/**
|
|
59
|
+
* Strictly validates an Upstash URL
|
|
60
|
+
* @param value The URL to validate
|
|
61
|
+
* @returns The password if the URL is valid, false otherwise
|
|
62
|
+
*/
|
|
63
|
+
upstash: (value = "") => {
|
|
64
|
+
try {
|
|
65
|
+
const url = new URL(value);
|
|
66
|
+
if (
|
|
67
|
+
// Only allow redis: or rediss: protocols
|
|
68
|
+
["redis:", "rediss:"].includes(url.protocol) && // Hostname must end with .upstash.io
|
|
69
|
+
url.hostname.endsWith(".upstash.io")
|
|
70
|
+
) {
|
|
71
|
+
return url.password || "";
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
} catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export {
|
|
81
|
+
getEdgeConfigId,
|
|
82
|
+
isCacheUrl,
|
|
83
|
+
isValidCacheUrl
|
|
84
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
declare const ContentSecurityPolicySchema: z.ZodObject<{
|
|
4
|
+
"default-src": z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodString, z.ZodBoolean]>>;
|
|
5
|
+
"script-src": z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodString, z.ZodBoolean]>>;
|
|
6
|
+
"style-src": z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodString, z.ZodBoolean]>>;
|
|
7
|
+
"img-src": z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodString, z.ZodBoolean]>>;
|
|
8
|
+
"connect-src": z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodString, z.ZodBoolean]>>;
|
|
9
|
+
"font-src": z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodString, z.ZodBoolean]>>;
|
|
10
|
+
"object-src": z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodString, z.ZodBoolean]>>;
|
|
11
|
+
"media-src": z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodString, z.ZodBoolean]>>;
|
|
12
|
+
"frame-src": z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodString, z.ZodBoolean]>>;
|
|
13
|
+
sandbox: z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodString, z.ZodBoolean]>>;
|
|
14
|
+
"report-uri": z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodString, z.ZodBoolean]>>;
|
|
15
|
+
"child-src": z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodString, z.ZodBoolean]>>;
|
|
16
|
+
"form-action": z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodString, z.ZodBoolean]>>;
|
|
17
|
+
"frame-ancestors": z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodString, z.ZodBoolean]>>;
|
|
18
|
+
"plugin-types": z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodString, z.ZodBoolean]>>;
|
|
19
|
+
"base-uri": z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodString, z.ZodBoolean]>>;
|
|
20
|
+
"report-to": z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodString, z.ZodBoolean]>>;
|
|
21
|
+
"worker-src": z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodString, z.ZodBoolean]>>;
|
|
22
|
+
"manifest-src": z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodString, z.ZodBoolean]>>;
|
|
23
|
+
"prefetch-src": z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodString, z.ZodBoolean]>>;
|
|
24
|
+
"navigate-to": z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodString, z.ZodBoolean]>>;
|
|
25
|
+
"require-sri-for": z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodString, z.ZodBoolean]>>;
|
|
26
|
+
"block-all-mixed-content": z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodString, z.ZodBoolean]>>;
|
|
27
|
+
"upgrade-insecure-requests": z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodString, z.ZodBoolean]>>;
|
|
28
|
+
"trusted-types": z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodString, z.ZodBoolean]>>;
|
|
29
|
+
"require-trusted-types-for": z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodString, z.ZodBoolean]>>;
|
|
30
|
+
}, "strip", z.ZodTypeAny, {
|
|
31
|
+
"default-src"?: string | boolean | string[] | undefined;
|
|
32
|
+
"script-src"?: string | boolean | string[] | undefined;
|
|
33
|
+
"style-src"?: string | boolean | string[] | undefined;
|
|
34
|
+
"img-src"?: string | boolean | string[] | undefined;
|
|
35
|
+
"connect-src"?: string | boolean | string[] | undefined;
|
|
36
|
+
"font-src"?: string | boolean | string[] | undefined;
|
|
37
|
+
"object-src"?: string | boolean | string[] | undefined;
|
|
38
|
+
"media-src"?: string | boolean | string[] | undefined;
|
|
39
|
+
"frame-src"?: string | boolean | string[] | undefined;
|
|
40
|
+
sandbox?: string | boolean | string[] | undefined;
|
|
41
|
+
"report-uri"?: string | boolean | string[] | undefined;
|
|
42
|
+
"child-src"?: string | boolean | string[] | undefined;
|
|
43
|
+
"form-action"?: string | boolean | string[] | undefined;
|
|
44
|
+
"frame-ancestors"?: string | boolean | string[] | undefined;
|
|
45
|
+
"plugin-types"?: string | boolean | string[] | undefined;
|
|
46
|
+
"base-uri"?: string | boolean | string[] | undefined;
|
|
47
|
+
"report-to"?: string | boolean | string[] | undefined;
|
|
48
|
+
"worker-src"?: string | boolean | string[] | undefined;
|
|
49
|
+
"manifest-src"?: string | boolean | string[] | undefined;
|
|
50
|
+
"prefetch-src"?: string | boolean | string[] | undefined;
|
|
51
|
+
"navigate-to"?: string | boolean | string[] | undefined;
|
|
52
|
+
"require-sri-for"?: string | boolean | string[] | undefined;
|
|
53
|
+
"block-all-mixed-content"?: string | boolean | string[] | undefined;
|
|
54
|
+
"upgrade-insecure-requests"?: string | boolean | string[] | undefined;
|
|
55
|
+
"trusted-types"?: string | boolean | string[] | undefined;
|
|
56
|
+
"require-trusted-types-for"?: string | boolean | string[] | undefined;
|
|
57
|
+
}, {
|
|
58
|
+
"default-src"?: string | boolean | string[] | undefined;
|
|
59
|
+
"script-src"?: string | boolean | string[] | undefined;
|
|
60
|
+
"style-src"?: string | boolean | string[] | undefined;
|
|
61
|
+
"img-src"?: string | boolean | string[] | undefined;
|
|
62
|
+
"connect-src"?: string | boolean | string[] | undefined;
|
|
63
|
+
"font-src"?: string | boolean | string[] | undefined;
|
|
64
|
+
"object-src"?: string | boolean | string[] | undefined;
|
|
65
|
+
"media-src"?: string | boolean | string[] | undefined;
|
|
66
|
+
"frame-src"?: string | boolean | string[] | undefined;
|
|
67
|
+
sandbox?: string | boolean | string[] | undefined;
|
|
68
|
+
"report-uri"?: string | boolean | string[] | undefined;
|
|
69
|
+
"child-src"?: string | boolean | string[] | undefined;
|
|
70
|
+
"form-action"?: string | boolean | string[] | undefined;
|
|
71
|
+
"frame-ancestors"?: string | boolean | string[] | undefined;
|
|
72
|
+
"plugin-types"?: string | boolean | string[] | undefined;
|
|
73
|
+
"base-uri"?: string | boolean | string[] | undefined;
|
|
74
|
+
"report-to"?: string | boolean | string[] | undefined;
|
|
75
|
+
"worker-src"?: string | boolean | string[] | undefined;
|
|
76
|
+
"manifest-src"?: string | boolean | string[] | undefined;
|
|
77
|
+
"prefetch-src"?: string | boolean | string[] | undefined;
|
|
78
|
+
"navigate-to"?: string | boolean | string[] | undefined;
|
|
79
|
+
"require-sri-for"?: string | boolean | string[] | undefined;
|
|
80
|
+
"block-all-mixed-content"?: string | boolean | string[] | undefined;
|
|
81
|
+
"upgrade-insecure-requests"?: string | boolean | string[] | undefined;
|
|
82
|
+
"trusted-types"?: string | boolean | string[] | undefined;
|
|
83
|
+
"require-trusted-types-for"?: string | boolean | string[] | undefined;
|
|
84
|
+
}>;
|
|
85
|
+
type ContentSecurityPolicyType = z.infer<typeof ContentSecurityPolicySchema>;
|
|
86
|
+
|
|
87
|
+
declare global {
|
|
88
|
+
interface CloudflareEnv extends Bindings {
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
type Bindings = {
|
|
92
|
+
DEBUG: string | boolean;
|
|
93
|
+
LOCK_PAGE_SLUG: string;
|
|
94
|
+
CSP_MODE: "disabled" | "report-only" | "enforced";
|
|
95
|
+
CSP_DIRECTIVES: string | ContentSecurityPolicyType;
|
|
96
|
+
APPWARDEN_API_TOKEN: string;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export type { Bindings as B };
|
package/cloudflare.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { B as Bindings } from './cloudflare-2PkEr25r.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { M as Middleware } from './use-content-security-policy-C89AROtC.js';
|
|
4
|
+
export { u as useContentSecurityPolicy } from './use-content-security-policy-C89AROtC.js';
|
|
5
|
+
|
|
6
|
+
declare const ConfigFnInputSchema: z.ZodFunction<z.ZodTuple<[z.ZodType<{
|
|
7
|
+
env: CloudflareEnv;
|
|
8
|
+
cf: Record<string, unknown>;
|
|
9
|
+
ctx: unknown;
|
|
10
|
+
}, z.ZodTypeDef, {
|
|
11
|
+
env: CloudflareEnv;
|
|
12
|
+
cf: Record<string, unknown>;
|
|
13
|
+
ctx: unknown;
|
|
14
|
+
}>], z.ZodUnknown>, z.ZodObject<{
|
|
15
|
+
debug: z.ZodDefault<z.ZodEffects<z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodBoolean]>>, boolean, string | boolean | undefined>>;
|
|
16
|
+
lockPageSlug: z.ZodString;
|
|
17
|
+
appwardenApiToken: z.ZodEffects<z.ZodString, string, string>;
|
|
18
|
+
} & {
|
|
19
|
+
middleware: z.ZodDefault<z.ZodObject<{
|
|
20
|
+
before: z.ZodDefault<z.ZodArray<z.ZodType<Middleware, z.ZodTypeDef, Middleware>, "many">>;
|
|
21
|
+
}, "strip", z.ZodTypeAny, {
|
|
22
|
+
before: Middleware[];
|
|
23
|
+
}, {
|
|
24
|
+
before?: Middleware[] | undefined;
|
|
25
|
+
}>>;
|
|
26
|
+
}, "strip", z.ZodTypeAny, {
|
|
27
|
+
debug: boolean;
|
|
28
|
+
lockPageSlug: string;
|
|
29
|
+
appwardenApiToken: string;
|
|
30
|
+
middleware: {
|
|
31
|
+
before: Middleware[];
|
|
32
|
+
};
|
|
33
|
+
}, {
|
|
34
|
+
lockPageSlug: string;
|
|
35
|
+
appwardenApiToken: string;
|
|
36
|
+
debug?: string | boolean | undefined;
|
|
37
|
+
middleware?: {
|
|
38
|
+
before?: Middleware[] | undefined;
|
|
39
|
+
} | undefined;
|
|
40
|
+
}>>;
|
|
41
|
+
type CloudflareConfigType = ReturnType<z.infer<typeof ConfigFnInputSchema>>;
|
|
42
|
+
|
|
43
|
+
declare const withAppwarden: (inputFn: CloudflareConfigType) => ExportedHandlerFetchHandler<Bindings>;
|
|
44
|
+
|
|
45
|
+
export { withAppwarden };
|