@appwarden/middleware 3.11.5 → 3.12.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-5HCAAVK5.js → chunk-6YCNCR22.js} +3 -5
- package/{chunk-R7TXTHSG.js → chunk-EXGUJ5XK.js} +75 -143
- package/chunk-G6BMPIYD.js +11 -0
- package/chunk-HTSD4WPC.js +378 -0
- package/chunk-HUWGPM4M.js +9 -0
- package/{chunk-WBWF3PPX.js → chunk-ILIYP3TG.js} +4 -6
- package/{chunk-XFG6SUSV.js → chunk-M2YVPCTG.js} +15 -0
- package/chunk-NV7K5PRA.js +36 -0
- package/{chunk-ZTVJBORU.js → chunk-Z7P4QVEY.js} +74 -8
- package/cloudflare/astro.d.ts +3 -3
- package/cloudflare/astro.js +109 -62
- package/cloudflare/nextjs.d.ts +2 -2
- package/cloudflare/nextjs.js +65 -21
- package/cloudflare/react-router.d.ts +3 -3
- package/cloudflare/react-router.js +92 -57
- package/cloudflare/tanstack-start.d.ts +2 -2
- package/cloudflare/tanstack-start.js +92 -80
- package/cloudflare.d.ts +1 -1
- package/cloudflare.js +80 -11
- package/index.d.ts +1 -1
- package/index.js +2 -3
- 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-X7WZVYQS.js +0 -6
- package/cloudflare-MAHYENA6.js +0 -29
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://github.com/appwarden/middleware)
|
|
5
5
|
[](https://www.npmjs.com/package/@appwarden/middleware)
|
|
6
6
|
[](https://docs.npmjs.com/generating-provenance-statements)
|
|
7
|
-

|
|
8
8
|
[](https://opensource.org/licenses/MIT)
|
|
9
9
|
|
|
10
10
|
## Core Features
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
UseCSPInputSchema
|
|
3
|
-
} from "./chunk-ZTVJBORU.js";
|
|
4
1
|
import {
|
|
5
2
|
AppwardenApiHostnameSchema,
|
|
6
3
|
AppwardenApiTokenSchema,
|
|
7
|
-
BooleanSchema
|
|
8
|
-
|
|
4
|
+
BooleanSchema,
|
|
5
|
+
UseCSPInputSchema
|
|
6
|
+
} from "./chunk-Z7P4QVEY.js";
|
|
9
7
|
|
|
10
8
|
// src/schemas/use-appwarden.ts
|
|
11
9
|
import { z } from "zod";
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
+
MemoryCache,
|
|
3
|
+
debug,
|
|
4
|
+
printMessage
|
|
5
|
+
} from "./chunk-HTSD4WPC.js";
|
|
6
|
+
import {
|
|
7
|
+
APPWARDEN_CACHE_KEY,
|
|
8
|
+
APPWARDEN_TEST_ROUTE,
|
|
2
9
|
LockValue
|
|
3
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-Z7P4QVEY.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
|
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// src/utils/get-now.ts
|
|
2
|
+
var getNowMs = () => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
|
|
3
|
+
var getElapsedMs = (startTime) => Math.round(getNowMs() - startTime);
|
|
4
|
+
var logElapsed = (debug, startTime) => {
|
|
5
|
+
debug(`Middleware executed in ${getElapsedMs(startTime)}ms`);
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
getNowMs,
|
|
10
|
+
logElapsed
|
|
11
|
+
};
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import {
|
|
2
|
+
APPWARDEN_HEARTBEAT_ROUTE,
|
|
3
|
+
HEARTBEAT_CONFIG_ERROR_MAX_CODE_LENGTH,
|
|
4
|
+
HEARTBEAT_CONFIG_ERROR_MAX_COUNT,
|
|
5
|
+
HEARTBEAT_CONFIG_ERROR_MAX_MESSAGE_LENGTH,
|
|
6
|
+
HEARTBEAT_CONFIG_ERROR_MAX_PATH_DEPTH,
|
|
7
|
+
HEARTBEAT_CONFIG_ERROR_MAX_PATH_SEGMENT_LENGTH,
|
|
8
|
+
HEARTBEAT_CONTRACT_VERSION,
|
|
9
|
+
LOCKDOWN_TEST_EXPIRY_MS
|
|
10
|
+
} from "./chunk-Z7P4QVEY.js";
|
|
11
|
+
|
|
12
|
+
// src/utils/build-lock-page-url.ts
|
|
13
|
+
function normalizeLockPageSlug(lockPageSlug) {
|
|
14
|
+
return lockPageSlug.startsWith("/") ? lockPageSlug : `/${lockPageSlug}`;
|
|
15
|
+
}
|
|
16
|
+
function buildLockPageUrl(lockPageSlug, requestUrl) {
|
|
17
|
+
const normalizedSlug = normalizeLockPageSlug(lockPageSlug);
|
|
18
|
+
return new URL(normalizedSlug, requestUrl);
|
|
19
|
+
}
|
|
20
|
+
function normalizeTrailingSlash(path) {
|
|
21
|
+
if (path === "/") return path;
|
|
22
|
+
return path.endsWith("/") ? path.slice(0, -1) : path;
|
|
23
|
+
}
|
|
24
|
+
function isOnLockPage(lockPageSlug, requestUrl) {
|
|
25
|
+
const normalizedSlug = normalizeTrailingSlash(
|
|
26
|
+
normalizeLockPageSlug(lockPageSlug)
|
|
27
|
+
);
|
|
28
|
+
const url = typeof requestUrl === "string" ? new URL(requestUrl) : requestUrl;
|
|
29
|
+
const normalizedPathname = normalizeTrailingSlash(url.pathname);
|
|
30
|
+
return normalizedPathname === normalizedSlug;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/utils/create-redirect.ts
|
|
34
|
+
var TEMPORARY_REDIRECT_STATUS = 302;
|
|
35
|
+
var createRedirect = (url) => {
|
|
36
|
+
return new Response(null, {
|
|
37
|
+
status: TEMPORARY_REDIRECT_STATUS,
|
|
38
|
+
headers: {
|
|
39
|
+
Location: url.toString()
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// src/utils/print-message.ts
|
|
45
|
+
var addSlashes = (str) => str.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$").replace(/"/g, '\\"').replace(/'/g, "\\'").replace(/\u0000/g, "\\0").replace(/<\/script>/gi, "<\\/script>");
|
|
46
|
+
var printMessage = (message) => `[@appwarden/middleware] ${addSlashes(message)}`;
|
|
47
|
+
|
|
48
|
+
// src/utils/debug.ts
|
|
49
|
+
var debug = (isDebug) => (...msg) => {
|
|
50
|
+
if (!isDebug) return;
|
|
51
|
+
const parts = msg.map((m) => {
|
|
52
|
+
let content;
|
|
53
|
+
if (m instanceof Error) {
|
|
54
|
+
content = m.stack ?? m.message;
|
|
55
|
+
} else if (typeof m === "object" && m !== null) {
|
|
56
|
+
try {
|
|
57
|
+
content = JSON.stringify(m);
|
|
58
|
+
} catch {
|
|
59
|
+
try {
|
|
60
|
+
content = String(m);
|
|
61
|
+
} catch {
|
|
62
|
+
content = "[Unserializable value]";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
content = String(m);
|
|
67
|
+
}
|
|
68
|
+
return content;
|
|
69
|
+
});
|
|
70
|
+
const message = parts.join(" ");
|
|
71
|
+
console.log(printMessage(message));
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// src/utils/memory-cache.ts
|
|
75
|
+
var MemoryCache = class {
|
|
76
|
+
cache = /* @__PURE__ */ new Map();
|
|
77
|
+
maxSize;
|
|
78
|
+
constructor(options) {
|
|
79
|
+
this.maxSize = options.maxSize;
|
|
80
|
+
}
|
|
81
|
+
get(key) {
|
|
82
|
+
let item;
|
|
83
|
+
if (this.cache.has(key)) {
|
|
84
|
+
item = this.cache.get(key);
|
|
85
|
+
this.cache.delete(key);
|
|
86
|
+
if (item !== void 0) {
|
|
87
|
+
this.cache.set(key, item);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return item;
|
|
91
|
+
}
|
|
92
|
+
put(key, value) {
|
|
93
|
+
if (this.cache.has(key)) {
|
|
94
|
+
this.cache.delete(key);
|
|
95
|
+
} else if (this.cache.size >= this.maxSize) {
|
|
96
|
+
const firstKey = this.cache.keys().next().value;
|
|
97
|
+
if (firstKey !== void 0) {
|
|
98
|
+
this.cache.delete(firstKey);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
this.cache.set(key, value);
|
|
102
|
+
}
|
|
103
|
+
getValues() {
|
|
104
|
+
return this.cache;
|
|
105
|
+
}
|
|
106
|
+
// the default value will be expired here
|
|
107
|
+
static isExpired = (lockValue) => {
|
|
108
|
+
if (!lockValue) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
return Date.now() > lockValue.lastCheck + 3e4;
|
|
112
|
+
};
|
|
113
|
+
static isTestExpired = (lockValue) => {
|
|
114
|
+
if (!lockValue) {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
return Date.now() > lockValue.isLockedTest + LOCKDOWN_TEST_EXPIRY_MS;
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// src/version.ts
|
|
122
|
+
var MIDDLEWARE_VERSION = "3.11.6";
|
|
123
|
+
|
|
124
|
+
// src/utils/heartbeat.ts
|
|
125
|
+
function createSanitizedMessage(code, path) {
|
|
126
|
+
const fieldName = path.length > 0 ? path[path.length - 1] : "field";
|
|
127
|
+
switch (code) {
|
|
128
|
+
case "invalid_type":
|
|
129
|
+
return `Invalid type for ${fieldName}`;
|
|
130
|
+
case "invalid_literal":
|
|
131
|
+
return `Invalid value for ${fieldName}`;
|
|
132
|
+
case "unrecognized_keys":
|
|
133
|
+
return `Unrecognized keys in ${fieldName}`;
|
|
134
|
+
case "invalid_union":
|
|
135
|
+
return `Invalid union value for ${fieldName}`;
|
|
136
|
+
case "invalid_enum_value":
|
|
137
|
+
return `Invalid enum value for ${fieldName}`;
|
|
138
|
+
case "invalid_arguments":
|
|
139
|
+
return `Invalid arguments for ${fieldName}`;
|
|
140
|
+
case "invalid_return_type":
|
|
141
|
+
return `Invalid return type for ${fieldName}`;
|
|
142
|
+
case "invalid_date":
|
|
143
|
+
return `Invalid date for ${fieldName}`;
|
|
144
|
+
case "invalid_string":
|
|
145
|
+
return `Invalid string format for ${fieldName}`;
|
|
146
|
+
case "too_small":
|
|
147
|
+
return `Value too small for ${fieldName}`;
|
|
148
|
+
case "too_big":
|
|
149
|
+
return `Value too large for ${fieldName}`;
|
|
150
|
+
case "invalid_intersection_types":
|
|
151
|
+
return `Invalid intersection types for ${fieldName}`;
|
|
152
|
+
case "not_multiple_of":
|
|
153
|
+
return `Value not a multiple of required value for ${fieldName}`;
|
|
154
|
+
case "not_finite":
|
|
155
|
+
return `Value must be finite for ${fieldName}`;
|
|
156
|
+
case "custom":
|
|
157
|
+
return `Validation failed for ${fieldName}`;
|
|
158
|
+
default:
|
|
159
|
+
return `Validation error for ${fieldName}`;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function sanitizePath(path) {
|
|
163
|
+
const truncatedPath = path.slice(0, HEARTBEAT_CONFIG_ERROR_MAX_PATH_DEPTH);
|
|
164
|
+
return truncatedPath.map((segment) => {
|
|
165
|
+
if (typeof segment === "string" && segment.length > HEARTBEAT_CONFIG_ERROR_MAX_PATH_SEGMENT_LENGTH) {
|
|
166
|
+
return segment.substring(
|
|
167
|
+
0,
|
|
168
|
+
HEARTBEAT_CONFIG_ERROR_MAX_PATH_SEGMENT_LENGTH - 3
|
|
169
|
+
) + "...";
|
|
170
|
+
}
|
|
171
|
+
return segment;
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
function truncateMessage(message) {
|
|
175
|
+
if (message.length <= HEARTBEAT_CONFIG_ERROR_MAX_MESSAGE_LENGTH) {
|
|
176
|
+
return message;
|
|
177
|
+
}
|
|
178
|
+
return message.substring(0, HEARTBEAT_CONFIG_ERROR_MAX_MESSAGE_LENGTH - 3) + "...";
|
|
179
|
+
}
|
|
180
|
+
function createHeartbeatConfigError(path, code, message) {
|
|
181
|
+
return {
|
|
182
|
+
path: sanitizePath(path),
|
|
183
|
+
code: code.substring(0, HEARTBEAT_CONFIG_ERROR_MAX_CODE_LENGTH),
|
|
184
|
+
message: truncateMessage(message)
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function normalizeHeartbeatConfigErrors(configErrors) {
|
|
188
|
+
return configErrors.slice(0, HEARTBEAT_CONFIG_ERROR_MAX_COUNT).map(
|
|
189
|
+
(configError) => createHeartbeatConfigError(
|
|
190
|
+
configError.path,
|
|
191
|
+
configError.code,
|
|
192
|
+
configError.message
|
|
193
|
+
)
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
function sanitizeConfigErrors(error) {
|
|
197
|
+
if (!error) {
|
|
198
|
+
return [];
|
|
199
|
+
}
|
|
200
|
+
const errors = [];
|
|
201
|
+
const issues = error.issues.slice(0, HEARTBEAT_CONFIG_ERROR_MAX_COUNT);
|
|
202
|
+
for (const issue of issues) {
|
|
203
|
+
const sanitizedPath = sanitizePath(issue.path);
|
|
204
|
+
const message = truncateMessage(
|
|
205
|
+
createSanitizedMessage(issue.code, sanitizedPath)
|
|
206
|
+
);
|
|
207
|
+
errors.push({
|
|
208
|
+
path: sanitizedPath,
|
|
209
|
+
code: issue.code,
|
|
210
|
+
message
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
return errors;
|
|
214
|
+
}
|
|
215
|
+
function createHeartbeatResponseBody(service, configErrors = []) {
|
|
216
|
+
return {
|
|
217
|
+
app: "appwarden",
|
|
218
|
+
kind: "heartbeat",
|
|
219
|
+
status: "ok",
|
|
220
|
+
contractVersion: HEARTBEAT_CONTRACT_VERSION,
|
|
221
|
+
service,
|
|
222
|
+
version: MIDDLEWARE_VERSION,
|
|
223
|
+
configErrors: normalizeHeartbeatConfigErrors(configErrors)
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function createHeartbeatResponse(service, configErrors = []) {
|
|
227
|
+
const body = createHeartbeatResponseBody(service, configErrors);
|
|
228
|
+
return new Response(JSON.stringify(body), {
|
|
229
|
+
status: 200,
|
|
230
|
+
headers: {
|
|
231
|
+
"content-type": "application/json",
|
|
232
|
+
"cache-control": "no-store",
|
|
233
|
+
"x-appwarden-heartbeat": "1",
|
|
234
|
+
"x-appwarden-contract-version": String(HEARTBEAT_CONTRACT_VERSION),
|
|
235
|
+
"x-appwarden-service": service,
|
|
236
|
+
"x-appwarden-version": MIDDLEWARE_VERSION
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
function isHeartbeatRoute(url) {
|
|
241
|
+
return url.pathname === APPWARDEN_HEARTBEAT_ROUTE;
|
|
242
|
+
}
|
|
243
|
+
function isHeartbeatRequest(request, url) {
|
|
244
|
+
return request.method === "GET" && isHeartbeatRoute(url);
|
|
245
|
+
}
|
|
246
|
+
function handleHeartbeatRequest(request, service, configErrors = []) {
|
|
247
|
+
return createHeartbeatResponse(service, configErrors);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// src/utils/request-checks.ts
|
|
251
|
+
function isHTMLResponse(response) {
|
|
252
|
+
return response.headers.get("Content-Type")?.includes("text/html") ?? false;
|
|
253
|
+
}
|
|
254
|
+
function isHTMLRequest(request) {
|
|
255
|
+
const accept = request.headers.get("accept");
|
|
256
|
+
if (!accept) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
const normalizedAccept = accept.toLowerCase();
|
|
260
|
+
const isWildcardOnlyAccept = (value) => {
|
|
261
|
+
const mediaRanges2 = value.split(",");
|
|
262
|
+
let hasNonEmptyRange = false;
|
|
263
|
+
for (const range of mediaRanges2) {
|
|
264
|
+
const [typeSubtype] = range.split(";");
|
|
265
|
+
const trimmed = typeSubtype.trim();
|
|
266
|
+
if (!trimmed) {
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
hasNonEmptyRange = true;
|
|
270
|
+
if (trimmed !== "*/*" && trimmed !== "*") {
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return hasNonEmptyRange;
|
|
275
|
+
};
|
|
276
|
+
if (isWildcardOnlyAccept(normalizedAccept)) {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
const mediaRanges = normalizedAccept.split(",");
|
|
280
|
+
for (const range of mediaRanges) {
|
|
281
|
+
const [typeSubtype] = range.split(";");
|
|
282
|
+
const token = typeSubtype.trim();
|
|
283
|
+
if (token === "text/html") {
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/utils/cloudflare/csp-keywords.ts
|
|
291
|
+
var CSP_KEYWORDS = [
|
|
292
|
+
"self",
|
|
293
|
+
"none",
|
|
294
|
+
"unsafe-inline",
|
|
295
|
+
"unsafe-eval",
|
|
296
|
+
"unsafe-hashes",
|
|
297
|
+
"strict-dynamic",
|
|
298
|
+
"report-sample",
|
|
299
|
+
"unsafe-allow-redirects",
|
|
300
|
+
"wasm-unsafe-eval",
|
|
301
|
+
"trusted-types-eval",
|
|
302
|
+
"report-sha256",
|
|
303
|
+
"report-sha384",
|
|
304
|
+
"report-sha512",
|
|
305
|
+
"unsafe-webtransport-hashes"
|
|
306
|
+
];
|
|
307
|
+
var CSP_KEYWORDS_SET = new Set(CSP_KEYWORDS);
|
|
308
|
+
var isCSPKeyword = (value) => {
|
|
309
|
+
return CSP_KEYWORDS_SET.has(value.toLowerCase());
|
|
310
|
+
};
|
|
311
|
+
var isQuoted = (value) => {
|
|
312
|
+
return value.startsWith("'") && value.endsWith("'");
|
|
313
|
+
};
|
|
314
|
+
var autoQuoteCSPKeyword = (value) => {
|
|
315
|
+
const trimmed = value.trim();
|
|
316
|
+
if (isQuoted(trimmed)) {
|
|
317
|
+
return trimmed;
|
|
318
|
+
}
|
|
319
|
+
if (isCSPKeyword(trimmed)) {
|
|
320
|
+
return `'${trimmed}'`;
|
|
321
|
+
}
|
|
322
|
+
return trimmed;
|
|
323
|
+
};
|
|
324
|
+
var autoQuoteCSPDirectiveValue = (value) => {
|
|
325
|
+
return value.trim().split(/\s+/).filter(Boolean).map(autoQuoteCSPKeyword).join(" ");
|
|
326
|
+
};
|
|
327
|
+
var autoQuoteCSPDirectiveArray = (values) => {
|
|
328
|
+
return values.map(autoQuoteCSPKeyword);
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
// src/utils/cloudflare/make-csp-header.ts
|
|
332
|
+
var addNonce = (value, cspNonce) => value.replace("{{nonce}}", `'nonce-${cspNonce}'`);
|
|
333
|
+
var makeCSPHeader = (cspNonce, directives, mode) => {
|
|
334
|
+
const namesSeen = /* @__PURE__ */ new Set(), result = [];
|
|
335
|
+
Object.entries(directives ?? {}).forEach(([originalName, value]) => {
|
|
336
|
+
const name = originalName.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
337
|
+
if (namesSeen.has(name)) {
|
|
338
|
+
throw new Error(`${originalName} is specified more than once`);
|
|
339
|
+
}
|
|
340
|
+
namesSeen.add(name);
|
|
341
|
+
let directiveValue;
|
|
342
|
+
if (Array.isArray(value)) {
|
|
343
|
+
directiveValue = autoQuoteCSPDirectiveArray(value).join(" ");
|
|
344
|
+
} else if (value === true) {
|
|
345
|
+
directiveValue = "";
|
|
346
|
+
} else if (typeof value === "string") {
|
|
347
|
+
directiveValue = autoQuoteCSPDirectiveValue(value);
|
|
348
|
+
} else {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (directiveValue) {
|
|
352
|
+
result.push(`${name} ${addNonce(directiveValue, cspNonce)}`);
|
|
353
|
+
} else {
|
|
354
|
+
result.push(name);
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
return [
|
|
358
|
+
mode === "enforced" ? "Content-Security-Policy" : "Content-Security-Policy-Report-Only",
|
|
359
|
+
result.join("; ")
|
|
360
|
+
];
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
export {
|
|
364
|
+
buildLockPageUrl,
|
|
365
|
+
isOnLockPage,
|
|
366
|
+
TEMPORARY_REDIRECT_STATUS,
|
|
367
|
+
createRedirect,
|
|
368
|
+
printMessage,
|
|
369
|
+
debug,
|
|
370
|
+
MemoryCache,
|
|
371
|
+
createHeartbeatConfigError,
|
|
372
|
+
sanitizeConfigErrors,
|
|
373
|
+
isHeartbeatRequest,
|
|
374
|
+
handleHeartbeatRequest,
|
|
375
|
+
isHTMLResponse,
|
|
376
|
+
isHTMLRequest,
|
|
377
|
+
makeCSPHeader
|
|
378
|
+
};
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
-
isHTMLResponse
|
|
3
|
-
|
|
2
|
+
isHTMLResponse,
|
|
3
|
+
makeCSPHeader
|
|
4
|
+
} from "./chunk-HTSD4WPC.js";
|
|
4
5
|
import {
|
|
5
6
|
UseCSPInputSchema
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import {
|
|
8
|
-
makeCSPHeader
|
|
9
|
-
} from "./chunk-R7TXTHSG.js";
|
|
7
|
+
} from "./chunk-Z7P4QVEY.js";
|
|
10
8
|
|
|
11
9
|
// src/middlewares/use-content-security-policy.ts
|
|
12
10
|
var AppendAttribute = (attribute, nonce) => ({
|