@appwarden/middleware 3.11.6 → 3.13.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 +1 -1
- package/chunk-2ZSJUEHK.js +445 -0
- package/{chunk-5HCAAVK5.js → chunk-GNDWHKJ5.js} +3 -5
- package/{chunk-R7TXTHSG.js → chunk-HP5GMFH7.js} +75 -143
- package/chunk-HUWGPM4M.js +9 -0
- package/{chunk-WBWF3PPX.js → chunk-J2TA6BEU.js} +4 -6
- package/chunk-NV7K5PRA.js +36 -0
- package/chunk-SREQAAZC.js +220 -0
- package/{chunk-UFWJYCX6.js → chunk-TBSMAMWC.js} +1 -1
- package/cloudflare/astro.d.ts +2 -2
- package/cloudflare/astro.js +55 -15
- package/cloudflare/nextjs.d.ts +2 -2
- package/cloudflare/nextjs.js +60 -15
- package/cloudflare/react-router.d.ts +2 -2
- package/cloudflare/react-router.js +43 -15
- package/cloudflare/tanstack-start.d.ts +2 -2
- package/cloudflare/tanstack-start.js +40 -15
- package/cloudflare.d.ts +1 -1
- package/cloudflare.js +80 -11
- package/index.d.ts +109 -2
- package/index.js +10 -5
- package/package.json +1 -1
- package/{use-content-security-policy-CvdzUPYF.d.ts → use-content-security-policy-Dwdcwp33.d.ts} +0 -1
- package/vercel.js +38 -16
- package/chunk-AY4ZKZTF.js +0 -162
- package/chunk-QC2ZUZWY.js +0 -84
- package/chunk-WEM7GS4M.js +0 -29
- package/chunk-ZTVJBORU.js +0 -81
- package/cloudflare-MAHYENA6.js +0 -29
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
+
MemoryCache,
|
|
3
|
+
debug,
|
|
4
|
+
printMessage
|
|
5
|
+
} from "./chunk-2ZSJUEHK.js";
|
|
6
|
+
import {
|
|
7
|
+
APPWARDEN_CACHE_KEY,
|
|
8
|
+
APPWARDEN_TEST_ROUTE,
|
|
2
9
|
LockValue
|
|
3
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-SREQAAZC.js";
|
|
4
11
|
|
|
5
12
|
// src/utils/cloudflare/cloudflare-cache.ts
|
|
6
13
|
var store = {
|
|
@@ -38,51 +45,6 @@ var updateCacheValue = async (context, cacheKey, value, ttl) => {
|
|
|
38
45
|
};
|
|
39
46
|
var clearCache = (context, cacheKey) => context.cache.delete(cacheKey);
|
|
40
47
|
|
|
41
|
-
// src/utils/cloudflare/csp-keywords.ts
|
|
42
|
-
var CSP_KEYWORDS = [
|
|
43
|
-
"self",
|
|
44
|
-
"none",
|
|
45
|
-
"unsafe-inline",
|
|
46
|
-
"unsafe-eval",
|
|
47
|
-
"unsafe-hashes",
|
|
48
|
-
"strict-dynamic",
|
|
49
|
-
"report-sample",
|
|
50
|
-
"unsafe-allow-redirects",
|
|
51
|
-
"wasm-unsafe-eval",
|
|
52
|
-
"trusted-types-eval",
|
|
53
|
-
"report-sha256",
|
|
54
|
-
"report-sha384",
|
|
55
|
-
"report-sha512",
|
|
56
|
-
"unsafe-webtransport-hashes"
|
|
57
|
-
];
|
|
58
|
-
var CSP_KEYWORDS_SET = new Set(CSP_KEYWORDS);
|
|
59
|
-
var isCSPKeyword = (value) => {
|
|
60
|
-
return CSP_KEYWORDS_SET.has(value.toLowerCase());
|
|
61
|
-
};
|
|
62
|
-
var isQuoted = (value) => {
|
|
63
|
-
return value.startsWith("'") && value.endsWith("'");
|
|
64
|
-
};
|
|
65
|
-
var autoQuoteCSPKeyword = (value) => {
|
|
66
|
-
const trimmed = value.trim();
|
|
67
|
-
if (isQuoted(trimmed)) {
|
|
68
|
-
return trimmed;
|
|
69
|
-
}
|
|
70
|
-
if (isCSPKeyword(trimmed)) {
|
|
71
|
-
return `'${trimmed}'`;
|
|
72
|
-
}
|
|
73
|
-
return trimmed;
|
|
74
|
-
};
|
|
75
|
-
var autoQuoteCSPDirectiveValue = (value) => {
|
|
76
|
-
return value.trim().split(/\s+/).filter(Boolean).map(autoQuoteCSPKeyword).join(" ");
|
|
77
|
-
};
|
|
78
|
-
var autoQuoteCSPDirectiveArray = (values) => {
|
|
79
|
-
return values.map(autoQuoteCSPKeyword);
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
// src/utils/print-message.ts
|
|
83
|
-
var addSlashes = (str) => str.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$").replace(/"/g, '\\"').replace(/'/g, "\\'").replace(/\u0000/g, "\\0").replace(/<\/script>/gi, "<\\/script>");
|
|
84
|
-
var printMessage = (message) => `[@appwarden/middleware] ${addSlashes(message)}`;
|
|
85
|
-
|
|
86
48
|
// src/utils/cloudflare/delete-edge-value.ts
|
|
87
49
|
var deleteEdgeValue = async (context) => {
|
|
88
50
|
try {
|
|
@@ -105,39 +67,6 @@ var deleteEdgeValue = async (context) => {
|
|
|
105
67
|
}
|
|
106
68
|
};
|
|
107
69
|
|
|
108
|
-
// src/utils/errors.ts
|
|
109
|
-
var errorsMap = {
|
|
110
|
-
mode: '`CSP_MODE` must be one of "disabled", "report-only", or "enforced"',
|
|
111
|
-
directives: {
|
|
112
|
-
["DirectivesRequired" /* DirectivesRequired */]: '`CSP_DIRECTIVES` must be provided when `CSP_MODE` is "report-only" or "enforced"',
|
|
113
|
-
["DirectivesBadParse" /* DirectivesBadParse */]: "Failed to parse `CSP_DIRECTIVES`. Is it a valid JSON string?"
|
|
114
|
-
},
|
|
115
|
-
appwardenApiToken: "Please provide a valid `appwardenApiToken`. Learn more at https://appwarden.com/docs/guides/api-token-management."
|
|
116
|
-
};
|
|
117
|
-
var getErrors = (error) => {
|
|
118
|
-
const matches = [];
|
|
119
|
-
const errors = [...Object.entries(error.flatten().fieldErrors)];
|
|
120
|
-
for (const issue of error.issues) {
|
|
121
|
-
errors.push(
|
|
122
|
-
...Object.entries(
|
|
123
|
-
"returnTypeError" in issue ? issue.returnTypeError.flatten().fieldErrors : {}
|
|
124
|
-
)
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
for (const [field, maybeSchemaErrorKey] of errors) {
|
|
128
|
-
let match = errorsMap[field];
|
|
129
|
-
if (match) {
|
|
130
|
-
if (match instanceof Object) {
|
|
131
|
-
if (maybeSchemaErrorKey) {
|
|
132
|
-
match = match[maybeSchemaErrorKey[0]];
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
matches.push(match);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
return matches;
|
|
139
|
-
};
|
|
140
|
-
|
|
141
70
|
// src/utils/cloudflare/get-lock-value.ts
|
|
142
71
|
var getLockValue = async (context) => {
|
|
143
72
|
try {
|
|
@@ -181,56 +110,6 @@ var getLockValue = async (context) => {
|
|
|
181
110
|
}
|
|
182
111
|
};
|
|
183
112
|
|
|
184
|
-
// src/utils/cloudflare/insert-errors-logs.ts
|
|
185
|
-
var insertErrorLogs = async (context, error) => {
|
|
186
|
-
const errors = getErrors(error);
|
|
187
|
-
for (const err of errors) {
|
|
188
|
-
console.log(printMessage(err));
|
|
189
|
-
}
|
|
190
|
-
return new HTMLRewriter().on("body", {
|
|
191
|
-
element: (elem) => {
|
|
192
|
-
elem.append(
|
|
193
|
-
`<script>
|
|
194
|
-
${errors.map((err) => `console.error(\`${printMessage(err)}\`)`).join("\n")}
|
|
195
|
-
</script>`,
|
|
196
|
-
{ html: true }
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
}).transform(await fetch(context.request.clone()));
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
// src/utils/cloudflare/make-csp-header.ts
|
|
203
|
-
var addNonce = (value, cspNonce) => value.replace("{{nonce}}", `'nonce-${cspNonce}'`);
|
|
204
|
-
var makeCSPHeader = (cspNonce, directives, mode) => {
|
|
205
|
-
const namesSeen = /* @__PURE__ */ new Set(), result = [];
|
|
206
|
-
Object.entries(directives ?? {}).forEach(([originalName, value]) => {
|
|
207
|
-
const name = originalName.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
208
|
-
if (namesSeen.has(name)) {
|
|
209
|
-
throw new Error(`${originalName} is specified more than once`);
|
|
210
|
-
}
|
|
211
|
-
namesSeen.add(name);
|
|
212
|
-
let directiveValue;
|
|
213
|
-
if (Array.isArray(value)) {
|
|
214
|
-
directiveValue = autoQuoteCSPDirectiveArray(value).join(" ");
|
|
215
|
-
} else if (value === true) {
|
|
216
|
-
directiveValue = "";
|
|
217
|
-
} else if (typeof value === "string") {
|
|
218
|
-
directiveValue = autoQuoteCSPDirectiveValue(value);
|
|
219
|
-
} else {
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
if (directiveValue) {
|
|
223
|
-
result.push(`${name} ${addNonce(directiveValue, cspNonce)}`);
|
|
224
|
-
} else {
|
|
225
|
-
result.push(name);
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
return [
|
|
229
|
-
mode === "enforced" ? "Content-Security-Policy" : "Content-Security-Policy-Report-Only",
|
|
230
|
-
result.join("; ")
|
|
231
|
-
];
|
|
232
|
-
};
|
|
233
|
-
|
|
234
113
|
// src/utils/cloudflare/sync-edge-value.ts
|
|
235
114
|
var APIError = class extends Error {
|
|
236
115
|
constructor(message) {
|
|
@@ -287,19 +166,72 @@ var syncEdgeValue = async (context) => {
|
|
|
287
166
|
}
|
|
288
167
|
};
|
|
289
168
|
|
|
169
|
+
// src/core/check-lock-status.ts
|
|
170
|
+
var createContext = async (config) => {
|
|
171
|
+
const requestUrl = new URL(config.request.url);
|
|
172
|
+
const keyName = APPWARDEN_CACHE_KEY;
|
|
173
|
+
const provider = "cloudflare-cache";
|
|
174
|
+
const debugFn = debug(config.debug ?? false);
|
|
175
|
+
const edgeCache = store.json(
|
|
176
|
+
{
|
|
177
|
+
serviceOrigin: requestUrl.origin,
|
|
178
|
+
cache: await caches.open("appwarden:lock"),
|
|
179
|
+
debug: debugFn
|
|
180
|
+
},
|
|
181
|
+
keyName
|
|
182
|
+
);
|
|
183
|
+
return {
|
|
184
|
+
keyName,
|
|
185
|
+
request: config.request,
|
|
186
|
+
edgeCache,
|
|
187
|
+
requestUrl,
|
|
188
|
+
provider,
|
|
189
|
+
debug: debugFn,
|
|
190
|
+
lockPageSlug: config.lockPageSlug,
|
|
191
|
+
appwardenApiToken: config.appwardenApiToken,
|
|
192
|
+
appwardenApiHostname: config.appwardenApiHostname,
|
|
193
|
+
waitUntil: config.waitUntil
|
|
194
|
+
};
|
|
195
|
+
};
|
|
196
|
+
var resolveLockStatus = async (context) => {
|
|
197
|
+
const { lockValue, shouldDeleteEdgeValue } = await getLockValue(context);
|
|
198
|
+
if (shouldDeleteEdgeValue) {
|
|
199
|
+
context.debug("Deleting corrupted cache value");
|
|
200
|
+
await deleteEdgeValue(context);
|
|
201
|
+
}
|
|
202
|
+
const isTestRoute = context.requestUrl.pathname === APPWARDEN_TEST_ROUTE;
|
|
203
|
+
const isTestLock = isTestRoute && !MemoryCache.isTestExpired(lockValue) && !!lockValue;
|
|
204
|
+
return {
|
|
205
|
+
isLocked: !!lockValue?.isLocked || isTestLock,
|
|
206
|
+
isTestLock,
|
|
207
|
+
lockValue,
|
|
208
|
+
wasDeleted: shouldDeleteEdgeValue ?? false
|
|
209
|
+
};
|
|
210
|
+
};
|
|
211
|
+
var checkLockStatus = async (config) => {
|
|
212
|
+
const context = await createContext(config);
|
|
213
|
+
let { isLocked, isTestLock, lockValue, wasDeleted } = await resolveLockStatus(context);
|
|
214
|
+
const isExpired = MemoryCache.isExpired(lockValue);
|
|
215
|
+
if (!isExpired && !wasDeleted && lockValue) {
|
|
216
|
+
context.debug("Lock value resolved from cache");
|
|
217
|
+
}
|
|
218
|
+
if (isExpired || wasDeleted) {
|
|
219
|
+
if (!lockValue || wasDeleted || lockValue.isLocked) {
|
|
220
|
+
context.debug(
|
|
221
|
+
"No fresh cached lock status available - syncing with API synchronously"
|
|
222
|
+
);
|
|
223
|
+
await syncEdgeValue(context);
|
|
224
|
+
({ isLocked, isTestLock } = await resolveLockStatus(context));
|
|
225
|
+
} else {
|
|
226
|
+
context.debug(
|
|
227
|
+
"Cached lock status expired but last known state unlocked - syncing with API in background"
|
|
228
|
+
);
|
|
229
|
+
config.waitUntil(syncEdgeValue(context));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return { isLocked, isTestLock };
|
|
233
|
+
};
|
|
234
|
+
|
|
290
235
|
export {
|
|
291
|
-
|
|
292
|
-
getErrors,
|
|
293
|
-
store,
|
|
294
|
-
CSP_KEYWORDS,
|
|
295
|
-
isCSPKeyword,
|
|
296
|
-
isQuoted,
|
|
297
|
-
autoQuoteCSPKeyword,
|
|
298
|
-
autoQuoteCSPDirectiveValue,
|
|
299
|
-
autoQuoteCSPDirectiveArray,
|
|
300
|
-
deleteEdgeValue,
|
|
301
|
-
getLockValue,
|
|
302
|
-
insertErrorLogs,
|
|
303
|
-
makeCSPHeader,
|
|
304
|
-
syncEdgeValue
|
|
236
|
+
checkLockStatus
|
|
305
237
|
};
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
-
isHTMLResponse
|
|
3
|
-
|
|
2
|
+
isHTMLResponse,
|
|
3
|
+
makeCSPHeader
|
|
4
|
+
} from "./chunk-2ZSJUEHK.js";
|
|
4
5
|
import {
|
|
5
6
|
UseCSPInputSchema
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import {
|
|
8
|
-
makeCSPHeader
|
|
9
|
-
} from "./chunk-R7TXTHSG.js";
|
|
7
|
+
} from "./chunk-SREQAAZC.js";
|
|
10
8
|
|
|
11
9
|
// src/middlewares/use-content-security-policy.ts
|
|
12
10
|
var AppendAttribute = (attribute, nonce) => ({
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// src/utils/errors.ts
|
|
2
|
+
var errorsMap = {
|
|
3
|
+
mode: '`CSP_MODE` must be one of "disabled", "report-only", or "enforced"',
|
|
4
|
+
directives: {
|
|
5
|
+
["DirectivesRequired" /* DirectivesRequired */]: '`CSP_DIRECTIVES` must be provided when `CSP_MODE` is "report-only" or "enforced"',
|
|
6
|
+
["DirectivesBadParse" /* DirectivesBadParse */]: "Failed to parse `CSP_DIRECTIVES`. Is it a valid JSON string?"
|
|
7
|
+
},
|
|
8
|
+
appwardenApiToken: "Please provide a valid `appwardenApiToken`. Learn more at https://appwarden.com/docs/guides/api-token-management."
|
|
9
|
+
};
|
|
10
|
+
var getErrors = (error) => {
|
|
11
|
+
const matches = [];
|
|
12
|
+
const errors = [...Object.entries(error.flatten().fieldErrors)];
|
|
13
|
+
for (const issue of error.issues) {
|
|
14
|
+
errors.push(
|
|
15
|
+
...Object.entries(
|
|
16
|
+
"returnTypeError" in issue ? issue.returnTypeError.flatten().fieldErrors : {}
|
|
17
|
+
)
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
for (const [field, maybeSchemaErrorKey] of errors) {
|
|
21
|
+
let match = errorsMap[field];
|
|
22
|
+
if (match) {
|
|
23
|
+
if (match instanceof Object) {
|
|
24
|
+
if (maybeSchemaErrorKey) {
|
|
25
|
+
match = match[maybeSchemaErrorKey[0]];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
matches.push(match);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return matches;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export {
|
|
35
|
+
getErrors
|
|
36
|
+
};
|
|
@@ -0,0 +1,220 @@
|
|
|
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_HEARTBEAT_ROUTE = "/_appwarden/heartbeat";
|
|
7
|
+
var HEARTBEAT_CONTRACT_VERSION = 1;
|
|
8
|
+
var HEARTBEAT_VERSION_MAX_LENGTH = 128;
|
|
9
|
+
var HEARTBEAT_CONFIG_ERROR_MAX_COUNT = 10;
|
|
10
|
+
var HEARTBEAT_CONFIG_ERROR_MAX_PATH_DEPTH = 10;
|
|
11
|
+
var HEARTBEAT_CONFIG_ERROR_MAX_CODE_LENGTH = 100;
|
|
12
|
+
var HEARTBEAT_CONFIG_ERROR_MAX_MESSAGE_LENGTH = 500;
|
|
13
|
+
var HEARTBEAT_CONFIG_ERRORS_MAX_SERIALIZED_BYTES = 12 * 1024;
|
|
14
|
+
var HEARTBEAT_RESPONSE_BODY_MAX_SERIALIZED_BYTES = 32 * 1024;
|
|
15
|
+
var HEARTBEAT_CONFIG_ERROR_MAX_PATH_SEGMENT_LENGTH = 100;
|
|
16
|
+
var APPWARDEN_CACHE_KEY = "appwarden-lock";
|
|
17
|
+
var HEARTBEAT_SERVICE_VALUES = [
|
|
18
|
+
"cloudflare",
|
|
19
|
+
"cloudflare-astro",
|
|
20
|
+
"cloudflare-react-router",
|
|
21
|
+
"cloudflare-tanstack-start",
|
|
22
|
+
"cloudflare-nextjs",
|
|
23
|
+
"vercel"
|
|
24
|
+
];
|
|
25
|
+
var [
|
|
26
|
+
CLOUDFLARE,
|
|
27
|
+
CLOUDFLARE_ASTRO,
|
|
28
|
+
CLOUDFLARE_REACT_ROUTER,
|
|
29
|
+
CLOUDFLARE_TANSTACK_START,
|
|
30
|
+
CLOUDFLARE_NEXTJS,
|
|
31
|
+
VERCEL
|
|
32
|
+
] = HEARTBEAT_SERVICE_VALUES;
|
|
33
|
+
var HEARTBEAT_SERVICES = {
|
|
34
|
+
CLOUDFLARE,
|
|
35
|
+
CLOUDFLARE_ASTRO,
|
|
36
|
+
CLOUDFLARE_REACT_ROUTER,
|
|
37
|
+
CLOUDFLARE_TANSTACK_START,
|
|
38
|
+
CLOUDFLARE_NEXTJS,
|
|
39
|
+
VERCEL
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// src/types/heartbeat.ts
|
|
43
|
+
import { z } from "zod";
|
|
44
|
+
function getSerializedJsonByteLength(value) {
|
|
45
|
+
return new TextEncoder().encode(JSON.stringify(value)).length;
|
|
46
|
+
}
|
|
47
|
+
function getHeartbeatResponseBodyByteBudgetIssue() {
|
|
48
|
+
return {
|
|
49
|
+
code: z.ZodIssueCode.custom,
|
|
50
|
+
message: `Serialized heartbeat response body must be at most ${HEARTBEAT_RESPONSE_BODY_MAX_SERIALIZED_BYTES} bytes`,
|
|
51
|
+
path: []
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function getHeartbeatResponseBodySerializationIssue() {
|
|
55
|
+
return {
|
|
56
|
+
code: z.ZodIssueCode.custom,
|
|
57
|
+
message: "Heartbeat response body must be JSON-serializable",
|
|
58
|
+
path: []
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
var HeartbeatConfigErrorPathSegmentSchema = z.union([
|
|
62
|
+
z.string().max(HEARTBEAT_CONFIG_ERROR_MAX_PATH_SEGMENT_LENGTH),
|
|
63
|
+
z.number().int().nonnegative()
|
|
64
|
+
]);
|
|
65
|
+
var HeartbeatConfigErrorSchema = z.object({
|
|
66
|
+
path: z.array(HeartbeatConfigErrorPathSegmentSchema).max(HEARTBEAT_CONFIG_ERROR_MAX_PATH_DEPTH),
|
|
67
|
+
code: z.string().min(1).max(HEARTBEAT_CONFIG_ERROR_MAX_CODE_LENGTH),
|
|
68
|
+
message: z.string().min(1).max(HEARTBEAT_CONFIG_ERROR_MAX_MESSAGE_LENGTH)
|
|
69
|
+
}).strict();
|
|
70
|
+
var HeartbeatConfigErrorsSchema = z.array(HeartbeatConfigErrorSchema).max(HEARTBEAT_CONFIG_ERROR_MAX_COUNT).superRefine((configErrors, ctx) => {
|
|
71
|
+
if (getSerializedJsonByteLength(configErrors) > HEARTBEAT_CONFIG_ERRORS_MAX_SERIALIZED_BYTES) {
|
|
72
|
+
ctx.addIssue({
|
|
73
|
+
code: z.ZodIssueCode.custom,
|
|
74
|
+
message: `Serialized configErrors payload must be at most ${HEARTBEAT_CONFIG_ERRORS_MAX_SERIALIZED_BYTES} bytes`
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
var HeartbeatResponseBodySchema = z.object({
|
|
79
|
+
app: z.literal("appwarden"),
|
|
80
|
+
kind: z.literal("heartbeat"),
|
|
81
|
+
status: z.literal("ok"),
|
|
82
|
+
contractVersion: z.literal(HEARTBEAT_CONTRACT_VERSION),
|
|
83
|
+
service: z.enum(HEARTBEAT_SERVICE_VALUES),
|
|
84
|
+
version: z.string().min(1).max(HEARTBEAT_VERSION_MAX_LENGTH),
|
|
85
|
+
configErrors: HeartbeatConfigErrorsSchema
|
|
86
|
+
}).strict().superRefine((body, ctx) => {
|
|
87
|
+
if (getSerializedJsonByteLength(body) > HEARTBEAT_RESPONSE_BODY_MAX_SERIALIZED_BYTES) {
|
|
88
|
+
ctx.addIssue({
|
|
89
|
+
code: z.ZodIssueCode.custom,
|
|
90
|
+
message: `Serialized heartbeat response body must be at most ${HEARTBEAT_RESPONSE_BODY_MAX_SERIALIZED_BYTES} bytes`
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
function validateHeartbeatResponseBody(value) {
|
|
95
|
+
let serializedJsonByteLength;
|
|
96
|
+
try {
|
|
97
|
+
serializedJsonByteLength = getSerializedJsonByteLength(value);
|
|
98
|
+
} catch {
|
|
99
|
+
throw new z.ZodError([getHeartbeatResponseBodySerializationIssue()]);
|
|
100
|
+
}
|
|
101
|
+
if (serializedJsonByteLength > HEARTBEAT_RESPONSE_BODY_MAX_SERIALIZED_BYTES) {
|
|
102
|
+
throw new z.ZodError([getHeartbeatResponseBodyByteBudgetIssue()]);
|
|
103
|
+
}
|
|
104
|
+
return HeartbeatResponseBodySchema.parse(value);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/schemas/use-content-security-policy.ts
|
|
108
|
+
import { z as z4 } from "zod";
|
|
109
|
+
|
|
110
|
+
// src/types/csp.ts
|
|
111
|
+
import { z as z2 } from "zod";
|
|
112
|
+
var stringySchema = z2.union([z2.array(z2.string()), z2.string(), z2.boolean()]);
|
|
113
|
+
var ContentSecurityPolicySchema = z2.object({
|
|
114
|
+
"default-src": stringySchema.optional(),
|
|
115
|
+
"script-src": stringySchema.optional(),
|
|
116
|
+
"style-src": stringySchema.optional(),
|
|
117
|
+
"img-src": stringySchema.optional(),
|
|
118
|
+
"connect-src": stringySchema.optional(),
|
|
119
|
+
"font-src": stringySchema.optional(),
|
|
120
|
+
"object-src": stringySchema.optional(),
|
|
121
|
+
"media-src": stringySchema.optional(),
|
|
122
|
+
"frame-src": stringySchema.optional(),
|
|
123
|
+
sandbox: stringySchema.optional(),
|
|
124
|
+
"report-uri": stringySchema.optional(),
|
|
125
|
+
"child-src": stringySchema.optional(),
|
|
126
|
+
"form-action": stringySchema.optional(),
|
|
127
|
+
"frame-ancestors": stringySchema.optional(),
|
|
128
|
+
"plugin-types": stringySchema.optional(),
|
|
129
|
+
"base-uri": stringySchema.optional(),
|
|
130
|
+
"report-to": stringySchema.optional(),
|
|
131
|
+
"worker-src": stringySchema.optional(),
|
|
132
|
+
"manifest-src": stringySchema.optional(),
|
|
133
|
+
"prefetch-src": stringySchema.optional(),
|
|
134
|
+
"navigate-to": stringySchema.optional(),
|
|
135
|
+
"require-sri-for": stringySchema.optional(),
|
|
136
|
+
"block-all-mixed-content": stringySchema.optional(),
|
|
137
|
+
"upgrade-insecure-requests": stringySchema.optional(),
|
|
138
|
+
"trusted-types": stringySchema.optional(),
|
|
139
|
+
"require-trusted-types-for": stringySchema.optional()
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// src/schemas/helpers.ts
|
|
143
|
+
import { z as z3 } from "zod";
|
|
144
|
+
var BoolOrStringSchema = z3.union([z3.string(), z3.boolean()]).optional();
|
|
145
|
+
var BooleanSchema = BoolOrStringSchema.transform((val) => {
|
|
146
|
+
if (val === "true" || val === true) {
|
|
147
|
+
return true;
|
|
148
|
+
} else if (val === "false" || val === false) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
throw new Error("Invalid value");
|
|
152
|
+
});
|
|
153
|
+
var AppwardenApiTokenSchema = z3.string().refine((val) => !!val, { message: "appwardenApiToken is required" });
|
|
154
|
+
var AppwardenApiHostnameSchema = z3.string().url({
|
|
155
|
+
message: "Invalid `appwardenApiHostname`. Please provide an absolute URL (e.g. https://api.appwarden.io)."
|
|
156
|
+
}).refine((value) => value.startsWith("https://"), {
|
|
157
|
+
message: "`appwardenApiHostname` must use the https:// scheme (e.g. https://api.appwarden.io)."
|
|
158
|
+
});
|
|
159
|
+
var LockValue = z3.object({
|
|
160
|
+
isLocked: z3.number(),
|
|
161
|
+
isLockedTest: z3.number(),
|
|
162
|
+
lastCheck: z3.number()
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// src/schemas/use-content-security-policy.ts
|
|
166
|
+
var CSPDirectivesSchema = z4.union([
|
|
167
|
+
z4.string(),
|
|
168
|
+
ContentSecurityPolicySchema
|
|
169
|
+
]);
|
|
170
|
+
var CSPModeSchema = z4.union([
|
|
171
|
+
z4.literal("disabled"),
|
|
172
|
+
z4.literal("report-only"),
|
|
173
|
+
z4.literal("enforced")
|
|
174
|
+
]);
|
|
175
|
+
var UseCSPInputSchema = z4.object({
|
|
176
|
+
mode: CSPModeSchema,
|
|
177
|
+
directives: CSPDirectivesSchema.refine(
|
|
178
|
+
(val) => {
|
|
179
|
+
try {
|
|
180
|
+
if (typeof val === "string") {
|
|
181
|
+
JSON.parse(val);
|
|
182
|
+
}
|
|
183
|
+
return true;
|
|
184
|
+
} catch (error) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
{ message: "DirectivesBadParse" /* DirectivesBadParse */ }
|
|
189
|
+
).transform(
|
|
190
|
+
(val) => typeof val === "string" ? JSON.parse(val) : val
|
|
191
|
+
)
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
export {
|
|
195
|
+
LOCKDOWN_TEST_EXPIRY_MS,
|
|
196
|
+
errors,
|
|
197
|
+
globalErrors,
|
|
198
|
+
APPWARDEN_TEST_ROUTE,
|
|
199
|
+
APPWARDEN_HEARTBEAT_ROUTE,
|
|
200
|
+
HEARTBEAT_CONTRACT_VERSION,
|
|
201
|
+
HEARTBEAT_CONFIG_ERROR_MAX_COUNT,
|
|
202
|
+
HEARTBEAT_CONFIG_ERROR_MAX_PATH_DEPTH,
|
|
203
|
+
HEARTBEAT_CONFIG_ERROR_MAX_CODE_LENGTH,
|
|
204
|
+
HEARTBEAT_CONFIG_ERROR_MAX_MESSAGE_LENGTH,
|
|
205
|
+
HEARTBEAT_CONFIG_ERRORS_MAX_SERIALIZED_BYTES,
|
|
206
|
+
HEARTBEAT_RESPONSE_BODY_MAX_SERIALIZED_BYTES,
|
|
207
|
+
HEARTBEAT_CONFIG_ERROR_MAX_PATH_SEGMENT_LENGTH,
|
|
208
|
+
APPWARDEN_CACHE_KEY,
|
|
209
|
+
HEARTBEAT_SERVICES,
|
|
210
|
+
BooleanSchema,
|
|
211
|
+
AppwardenApiTokenSchema,
|
|
212
|
+
AppwardenApiHostnameSchema,
|
|
213
|
+
LockValue,
|
|
214
|
+
HeartbeatConfigErrorSchema,
|
|
215
|
+
HeartbeatResponseBodySchema,
|
|
216
|
+
validateHeartbeatResponseBody,
|
|
217
|
+
CSPDirectivesSchema,
|
|
218
|
+
CSPModeSchema,
|
|
219
|
+
UseCSPInputSchema
|
|
220
|
+
};
|
package/cloudflare/astro.d.ts
CHANGED
|
@@ -270,8 +270,8 @@ declare const AstroCloudflareConfigSchema: z.ZodObject<{
|
|
|
270
270
|
};
|
|
271
271
|
}>>>;
|
|
272
272
|
}, "strip", z.ZodTypeAny, {
|
|
273
|
-
debug: boolean;
|
|
274
273
|
lockPageSlug: string;
|
|
274
|
+
debug: boolean;
|
|
275
275
|
appwardenApiToken: string;
|
|
276
276
|
contentSecurityPolicy?: {
|
|
277
277
|
mode: "disabled" | "report-only" | "enforced";
|
|
@@ -308,7 +308,6 @@ declare const AstroCloudflareConfigSchema: z.ZodObject<{
|
|
|
308
308
|
}, {
|
|
309
309
|
lockPageSlug: string;
|
|
310
310
|
appwardenApiToken: string;
|
|
311
|
-
debug?: string | boolean | undefined;
|
|
312
311
|
contentSecurityPolicy?: {
|
|
313
312
|
mode: "disabled" | "report-only" | "enforced";
|
|
314
313
|
directives: string | {
|
|
@@ -340,6 +339,7 @@ declare const AstroCloudflareConfigSchema: z.ZodObject<{
|
|
|
340
339
|
"require-trusted-types-for"?: string | boolean | string[] | undefined;
|
|
341
340
|
};
|
|
342
341
|
} | undefined;
|
|
342
|
+
debug?: string | boolean | undefined;
|
|
343
343
|
appwardenApiHostname?: string | undefined;
|
|
344
344
|
}>;
|
|
345
345
|
type AstroCloudflareConfig = z.infer<typeof AstroCloudflareConfigSchema>;
|
package/cloudflare/astro.js
CHANGED
|
@@ -1,34 +1,35 @@
|
|
|
1
1
|
import {
|
|
2
2
|
applyContentSecurityPolicyToResponse,
|
|
3
3
|
isResponseLike
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import "../chunk-
|
|
4
|
+
} from "../chunk-TBSMAMWC.js";
|
|
5
|
+
import "../chunk-J2TA6BEU.js";
|
|
6
6
|
import {
|
|
7
7
|
getNowMs,
|
|
8
8
|
logElapsed
|
|
9
9
|
} from "../chunk-G6BMPIYD.js";
|
|
10
10
|
import {
|
|
11
11
|
checkLockStatus
|
|
12
|
-
} from "../chunk-
|
|
12
|
+
} from "../chunk-HP5GMFH7.js";
|
|
13
13
|
import {
|
|
14
14
|
TEMPORARY_REDIRECT_STATUS,
|
|
15
15
|
buildLockPageUrl,
|
|
16
|
+
createHeartbeatConfigError,
|
|
16
17
|
createRedirect,
|
|
17
18
|
debug,
|
|
19
|
+
handleHeartbeatRequest,
|
|
18
20
|
isHTMLRequest,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
} from "../chunk-
|
|
24
|
-
import {
|
|
25
|
-
printMessage
|
|
26
|
-
} from "../chunk-R7TXTHSG.js";
|
|
21
|
+
isHeartbeatRequest,
|
|
22
|
+
isOnLockPage,
|
|
23
|
+
printMessage,
|
|
24
|
+
sanitizeConfigErrors
|
|
25
|
+
} from "../chunk-2ZSJUEHK.js";
|
|
27
26
|
import {
|
|
28
27
|
AppwardenApiHostnameSchema,
|
|
29
28
|
AppwardenApiTokenSchema,
|
|
30
|
-
BooleanSchema
|
|
31
|
-
|
|
29
|
+
BooleanSchema,
|
|
30
|
+
HEARTBEAT_SERVICES,
|
|
31
|
+
UseCSPInputSchema
|
|
32
|
+
} from "../chunk-SREQAAZC.js";
|
|
32
33
|
|
|
33
34
|
// src/adapters/astro-cloudflare.ts
|
|
34
35
|
import { waitUntil } from "cloudflare:workers";
|
|
@@ -49,13 +50,50 @@ var AstroCloudflareConfigSchema = z.object({
|
|
|
49
50
|
});
|
|
50
51
|
|
|
51
52
|
// src/adapters/astro-cloudflare.ts
|
|
53
|
+
var createAstroHeartbeatResponse = (request, runtime, configFn) => {
|
|
54
|
+
if (!runtime) {
|
|
55
|
+
return handleHeartbeatRequest(
|
|
56
|
+
request,
|
|
57
|
+
HEARTBEAT_SERVICES.CLOUDFLARE_ASTRO,
|
|
58
|
+
[
|
|
59
|
+
createHeartbeatConfigError(
|
|
60
|
+
["runtime"],
|
|
61
|
+
"custom",
|
|
62
|
+
"Cloudflare runtime unavailable"
|
|
63
|
+
)
|
|
64
|
+
]
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const validationResult = AstroCloudflareConfigSchema.safeParse(
|
|
69
|
+
configFn(runtime)
|
|
70
|
+
);
|
|
71
|
+
return handleHeartbeatRequest(
|
|
72
|
+
request,
|
|
73
|
+
HEARTBEAT_SERVICES.CLOUDFLARE_ASTRO,
|
|
74
|
+
validationResult.success ? [] : sanitizeConfigErrors(validationResult.error)
|
|
75
|
+
);
|
|
76
|
+
} catch {
|
|
77
|
+
return handleHeartbeatRequest(
|
|
78
|
+
request,
|
|
79
|
+
HEARTBEAT_SERVICES.CLOUDFLARE_ASTRO,
|
|
80
|
+
[
|
|
81
|
+
createHeartbeatConfigError(
|
|
82
|
+
["config"],
|
|
83
|
+
"custom",
|
|
84
|
+
"Appwarden config evaluation failed"
|
|
85
|
+
)
|
|
86
|
+
]
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
52
90
|
function createAppwardenMiddleware(configFn) {
|
|
53
91
|
return async (context, next) => {
|
|
54
92
|
const startTime = getNowMs();
|
|
55
93
|
const { request } = context;
|
|
56
94
|
let config;
|
|
57
95
|
let debugFn;
|
|
58
|
-
|
|
96
|
+
const requestUrl = new URL(request.url);
|
|
59
97
|
const applyCspToResponse = async (response2) => {
|
|
60
98
|
if (!config.contentSecurityPolicy || !isResponseLike(response2)) {
|
|
61
99
|
return response2;
|
|
@@ -79,6 +117,9 @@ function createAppwardenMiddleware(configFn) {
|
|
|
79
117
|
}
|
|
80
118
|
};
|
|
81
119
|
const locals = context.locals;
|
|
120
|
+
if (isHeartbeatRequest(request, requestUrl)) {
|
|
121
|
+
return createAstroHeartbeatResponse(request, locals.runtime, configFn);
|
|
122
|
+
}
|
|
82
123
|
try {
|
|
83
124
|
const runtime = locals.runtime;
|
|
84
125
|
if (!runtime) {
|
|
@@ -101,7 +142,6 @@ function createAppwardenMiddleware(configFn) {
|
|
|
101
142
|
}
|
|
102
143
|
config = validationResult.data;
|
|
103
144
|
debugFn = debug(config.debug);
|
|
104
|
-
requestUrl = new URL(request.url);
|
|
105
145
|
const isHTML = isHTMLRequest(request);
|
|
106
146
|
debugFn(
|
|
107
147
|
`Appwarden middleware invoked for ${requestUrl.pathname}`,
|