@appwarden/middleware 1.0.16
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/chunk-C7APN7T6.js +821 -0
- package/chunk-JXIVUR6E.js +186 -0
- package/cloudflare-hVS30fDq.d.ts +99 -0
- package/cloudflare.d.ts +47 -0
- package/cloudflare.js +13 -0
- package/index.d.ts +25 -0
- package/index.js +12 -0
- package/nextjs-on-pages.d.ts +30 -0
- package/nextjs-on-pages.js +10 -0
- package/package.json +36 -0
- package/use-content-security-policy-BtEGGIeu.d.ts +245 -0
- package/vercel.d.ts +31 -0
- package/vercel.js +13 -0
|
@@ -0,0 +1,821 @@
|
|
|
1
|
+
// src/constants.ts
|
|
2
|
+
var LOCKDOWN_TEST_EXPIRY_MS = 5 * 60 * 1e3;
|
|
3
|
+
var removedHeaders = ["X-Powered-By", "Server"];
|
|
4
|
+
var securityHeaders = [
|
|
5
|
+
["Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload"],
|
|
6
|
+
["X-Frame-Options", "DENY"],
|
|
7
|
+
["X-XSS-Protection", "1; mode=block"],
|
|
8
|
+
["X-Content-Type-Options", "nosniff"],
|
|
9
|
+
["Referrer-Policy", "no-referrer, strict-origin-when-cross-origin"],
|
|
10
|
+
["X-DNS-Prefetch-Control", "on"]
|
|
11
|
+
];
|
|
12
|
+
var errors = { badCacheConnection: "BAD_CACHE_CONNECTION" };
|
|
13
|
+
var globalErrors = [errors.badCacheConnection];
|
|
14
|
+
var APPWARDEN_TEST_ROUTE = "_appwarden/test";
|
|
15
|
+
|
|
16
|
+
// src/schemas/vercel.ts
|
|
17
|
+
import { z as z4 } from "zod";
|
|
18
|
+
|
|
19
|
+
// src/utils/debug.ts
|
|
20
|
+
var debug = (...msg) => {
|
|
21
|
+
if (true) {
|
|
22
|
+
console.log(...msg);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// src/utils/memory-cache.ts
|
|
27
|
+
var MemoryCache = class {
|
|
28
|
+
cache = /* @__PURE__ */ new Map();
|
|
29
|
+
maxSize;
|
|
30
|
+
constructor(options) {
|
|
31
|
+
this.maxSize = options.maxSize;
|
|
32
|
+
}
|
|
33
|
+
get(key) {
|
|
34
|
+
let item;
|
|
35
|
+
if (this.cache.has(key)) {
|
|
36
|
+
item = this.cache.get(key);
|
|
37
|
+
this.cache.delete(key);
|
|
38
|
+
this.cache.set(key, item);
|
|
39
|
+
}
|
|
40
|
+
return item;
|
|
41
|
+
}
|
|
42
|
+
put(key, value) {
|
|
43
|
+
if (this.cache.has(key)) {
|
|
44
|
+
this.cache.delete(key);
|
|
45
|
+
} else if (this.cache.size >= this.maxSize) {
|
|
46
|
+
const firstKey = this.cache.keys().next().value;
|
|
47
|
+
this.cache.delete(firstKey);
|
|
48
|
+
}
|
|
49
|
+
this.cache.set(key, value);
|
|
50
|
+
}
|
|
51
|
+
getValues() {
|
|
52
|
+
return this.cache;
|
|
53
|
+
}
|
|
54
|
+
// the default value will be expired here
|
|
55
|
+
static isExpired = (lockValue) => {
|
|
56
|
+
if (!lockValue) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
return Date.now() > lockValue.lastCheck + 3e4;
|
|
60
|
+
};
|
|
61
|
+
static isTestExpired = (lockValue) => {
|
|
62
|
+
if (!lockValue) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
return Date.now() > lockValue.isLockedTest + LOCKDOWN_TEST_EXPIRY_MS;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// src/utils/print-message.ts
|
|
70
|
+
var printMessage = (message) => `[@appwarden/middleware] ${message}`;
|
|
71
|
+
|
|
72
|
+
// src/utils/vercel/delete-edge-value.ts
|
|
73
|
+
var deleteEdgeValue = async ({
|
|
74
|
+
keyName,
|
|
75
|
+
provider,
|
|
76
|
+
connectionString,
|
|
77
|
+
edgeConfigId,
|
|
78
|
+
vercelApiToken
|
|
79
|
+
}) => {
|
|
80
|
+
try {
|
|
81
|
+
switch (provider) {
|
|
82
|
+
case "edge-config": {
|
|
83
|
+
const res = await fetch(
|
|
84
|
+
`https://api.vercel.com/v1/edge-config/${edgeConfigId}/items`,
|
|
85
|
+
{
|
|
86
|
+
method: "PATCH",
|
|
87
|
+
headers: {
|
|
88
|
+
Authorization: `Bearer ${vercelApiToken}`,
|
|
89
|
+
"Content-Type": "application/json"
|
|
90
|
+
},
|
|
91
|
+
body: JSON.stringify({
|
|
92
|
+
items: [
|
|
93
|
+
{
|
|
94
|
+
key: keyName,
|
|
95
|
+
operation: "delete"
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
if (res.status !== 200) {
|
|
102
|
+
let response = void 0;
|
|
103
|
+
try {
|
|
104
|
+
response = await res.json();
|
|
105
|
+
} catch (error) {
|
|
106
|
+
}
|
|
107
|
+
throw new Error(
|
|
108
|
+
`api.vercel.com/v1/edge-config responded with ${res.status} - ${res.statusText}${response?.error?.message ? ` - ${response?.error?.message}` : ""}`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
case "upstash": {
|
|
114
|
+
const [url, token] = connectionString.split("@");
|
|
115
|
+
const { Redis } = await import("@upstash/redis");
|
|
116
|
+
const redis = new Redis({ url, token });
|
|
117
|
+
await redis.del(keyName);
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
default:
|
|
121
|
+
throw new Error(`Unsupported provider: ${provider}`);
|
|
122
|
+
}
|
|
123
|
+
} catch (e) {
|
|
124
|
+
const message = "Failed to delete edge value";
|
|
125
|
+
console.error(
|
|
126
|
+
printMessage(e instanceof Error ? `${message} - ${e.message}` : message)
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// src/schemas/cloudflare.ts
|
|
132
|
+
import { z as z2 } from "zod";
|
|
133
|
+
|
|
134
|
+
// src/schemas/helpers.ts
|
|
135
|
+
import { z } from "zod";
|
|
136
|
+
var BoolOrStringSchema = z.union([z.string(), z.boolean()]).optional();
|
|
137
|
+
var BooleanSchema = BoolOrStringSchema.transform((val) => {
|
|
138
|
+
if (val === "true" || val === true) {
|
|
139
|
+
return true;
|
|
140
|
+
} else if (val === "false" || val === false) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
throw new Error("Invalid value");
|
|
144
|
+
});
|
|
145
|
+
var BaseOutputConfigSchema = z.object({
|
|
146
|
+
debug: BooleanSchema.default(false),
|
|
147
|
+
appwardenApiToken: z.string().refine((appwardenApiToken) => !!appwardenApiToken, {
|
|
148
|
+
message: printMessage(
|
|
149
|
+
"Please provide a valid `appwardenApiToken`. Learn more at https://appwarden.com/docs/api-tokens."
|
|
150
|
+
),
|
|
151
|
+
path: ["appwardenApiToken"]
|
|
152
|
+
}),
|
|
153
|
+
lockPageSlug: z.string()
|
|
154
|
+
});
|
|
155
|
+
var LockValue = z.object({
|
|
156
|
+
isLocked: z.number(),
|
|
157
|
+
isLockedTest: z.number(),
|
|
158
|
+
lastCheck: z.number(),
|
|
159
|
+
code: z.string()
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// src/schemas/cloudflare.ts
|
|
163
|
+
var ConfigOutputSchema = BaseOutputConfigSchema.extend({
|
|
164
|
+
middleware: z2.object({ before: z2.custom().array().default([]) }).default({})
|
|
165
|
+
});
|
|
166
|
+
var ConfigFnInputSchema = z2.function().args(z2.custom()).returns(
|
|
167
|
+
ConfigOutputSchema.extend({
|
|
168
|
+
debug: BoolOrStringSchema
|
|
169
|
+
})
|
|
170
|
+
);
|
|
171
|
+
var CloudflareConfigFnOutputSchema = z2.function().args(z2.custom()).returns(ConfigOutputSchema);
|
|
172
|
+
|
|
173
|
+
// src/schemas/nextjs.ts
|
|
174
|
+
import { z as z3 } from "zod";
|
|
175
|
+
var ConfigOutputSchema2 = BaseOutputConfigSchema;
|
|
176
|
+
var NextJsConfigFnOutputSchema = z3.function().args(z3.custom()).returns(ConfigOutputSchema2);
|
|
177
|
+
|
|
178
|
+
// src/utils/vercel/get-lock-value.ts
|
|
179
|
+
var getLockValue = async (context) => {
|
|
180
|
+
try {
|
|
181
|
+
let shouldDeleteEdgeValue = false;
|
|
182
|
+
let serializedValue, lockValue = {
|
|
183
|
+
isLocked: 0,
|
|
184
|
+
isLockedTest: 0,
|
|
185
|
+
lastCheck: 0,
|
|
186
|
+
code: ""
|
|
187
|
+
};
|
|
188
|
+
switch (context.provider) {
|
|
189
|
+
case "edge-config": {
|
|
190
|
+
const { createClient } = await import("@vercel/edge-config");
|
|
191
|
+
const edgeConfig = createClient(context.connectionString);
|
|
192
|
+
serializedValue = await edgeConfig.get(
|
|
193
|
+
context.keyName
|
|
194
|
+
);
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
case "upstash": {
|
|
198
|
+
const [url, token] = context.connectionString.split("@");
|
|
199
|
+
const { Redis } = await import("@upstash/redis");
|
|
200
|
+
const redis = new Redis({ url, token });
|
|
201
|
+
const redisValue = await redis.get(context.keyName);
|
|
202
|
+
serializedValue = redisValue === null ? void 0 : redisValue;
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
default:
|
|
206
|
+
throw new Error(`Unsupported provider: ${context.provider}`);
|
|
207
|
+
}
|
|
208
|
+
if (!serializedValue) {
|
|
209
|
+
return { lockValue: void 0 };
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
lockValue = LockValue.parse(JSON.parse(serializedValue));
|
|
213
|
+
} catch (error) {
|
|
214
|
+
console.error(
|
|
215
|
+
printMessage(
|
|
216
|
+
`Failed to parse ${context.keyName} from edge cache - ${error}`
|
|
217
|
+
)
|
|
218
|
+
);
|
|
219
|
+
shouldDeleteEdgeValue = true;
|
|
220
|
+
}
|
|
221
|
+
return { lockValue, shouldDeleteEdgeValue };
|
|
222
|
+
} catch (e) {
|
|
223
|
+
const message = "Failed to retrieve edge value";
|
|
224
|
+
console.error(
|
|
225
|
+
printMessage(e instanceof Error ? `${message} - ${e.message}` : message)
|
|
226
|
+
);
|
|
227
|
+
if (e instanceof Error) {
|
|
228
|
+
if (e.message.includes("Invalid connection string provided")) {
|
|
229
|
+
throw new Error(errors.badCacheConnection);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return { lockValue: void 0 };
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// src/utils/vercel/sync-edge-value.ts
|
|
237
|
+
var APIError = class extends Error {
|
|
238
|
+
constructor(message) {
|
|
239
|
+
super(message);
|
|
240
|
+
this.name = "APIError";
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
var syncEdgeValue = async (context) => {
|
|
244
|
+
debug("syncing with api");
|
|
245
|
+
try {
|
|
246
|
+
const response = await fetch(new URL("/v1/status/check", "https://bot-gateway.appwarden.io"), {
|
|
247
|
+
method: "POST",
|
|
248
|
+
headers: { "content-type": "application/json" },
|
|
249
|
+
body: JSON.stringify({
|
|
250
|
+
service: "vercel",
|
|
251
|
+
provider: context.provider,
|
|
252
|
+
fqdn: context.requestUrl.hostname,
|
|
253
|
+
edgeConfigId: context.edgeConfigId ?? "",
|
|
254
|
+
vercelApiToken: context.vercelApiToken ?? "",
|
|
255
|
+
appwardenApiToken: context.appwardenApiToken,
|
|
256
|
+
connectionString: context.connectionString
|
|
257
|
+
})
|
|
258
|
+
});
|
|
259
|
+
if (response.status !== 200) {
|
|
260
|
+
throw new Error(`${response.status} ${response.statusText}`);
|
|
261
|
+
}
|
|
262
|
+
if (response.headers.get("content-type")?.includes("application/json")) {
|
|
263
|
+
const result = await response.json();
|
|
264
|
+
if (result.error) {
|
|
265
|
+
throw new APIError(result.error.message);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
} catch (e) {
|
|
269
|
+
const message = "Failed to fetch from check endpoint";
|
|
270
|
+
console.error(
|
|
271
|
+
printMessage(
|
|
272
|
+
e instanceof APIError ? e.message : e instanceof Error ? `${message} - ${e.message}` : message
|
|
273
|
+
)
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// src/utils/handle-vercel-request.ts
|
|
279
|
+
var handleVercelRequest = async (context, options) => {
|
|
280
|
+
let cachedLockValue = context.memoryCache.get(context.keyName);
|
|
281
|
+
const shouldRecheck = MemoryCache.isExpired(cachedLockValue);
|
|
282
|
+
if (shouldRecheck) {
|
|
283
|
+
const { lockValue, shouldDeleteEdgeValue } = await getLockValue(context);
|
|
284
|
+
if (lockValue) {
|
|
285
|
+
context.memoryCache.put(context.keyName, lockValue);
|
|
286
|
+
}
|
|
287
|
+
if (shouldDeleteEdgeValue) {
|
|
288
|
+
await deleteEdgeValue(context);
|
|
289
|
+
}
|
|
290
|
+
cachedLockValue = lockValue;
|
|
291
|
+
}
|
|
292
|
+
if (cachedLockValue?.isLocked || context.requestUrl.pathname === APPWARDEN_TEST_ROUTE && !MemoryCache.isTestExpired(cachedLockValue)) {
|
|
293
|
+
options.onLocked();
|
|
294
|
+
}
|
|
295
|
+
return cachedLockValue;
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
// src/utils/middleware.ts
|
|
299
|
+
var usePipeline = (...initMiddlewares) => {
|
|
300
|
+
const stack = [...initMiddlewares];
|
|
301
|
+
const execute = async (context) => {
|
|
302
|
+
const runner = async (prevIndex, index) => {
|
|
303
|
+
if (index === prevIndex) {
|
|
304
|
+
throw new Error("next() called multiple times");
|
|
305
|
+
}
|
|
306
|
+
if (index >= stack.length) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const middleware = stack[index];
|
|
310
|
+
const next = async () => runner(index, index + 1);
|
|
311
|
+
await middleware(context, next);
|
|
312
|
+
};
|
|
313
|
+
await runner(-1, 0);
|
|
314
|
+
};
|
|
315
|
+
return {
|
|
316
|
+
execute
|
|
317
|
+
};
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
// src/utils/render-lock-page.ts
|
|
321
|
+
var renderLockPage = (context) => fetch(new URL(context.lockPageSlug, context.requestUrl.origin), {
|
|
322
|
+
headers: {
|
|
323
|
+
// no browser caching, otherwise we need to hard refresh to disable lock screen
|
|
324
|
+
"Cache-Control": "no-store"
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// src/schemas/vercel.ts
|
|
329
|
+
var MiddlewareSchema = z4.custom(
|
|
330
|
+
(val) => typeof val === "function"
|
|
331
|
+
);
|
|
332
|
+
var BaseNextJsConfigSchema = z4.object({
|
|
333
|
+
provider: z4.enum(["upstash", "edge-config"]),
|
|
334
|
+
lockPageSlug: z4.string(),
|
|
335
|
+
connectionString: z4.string(),
|
|
336
|
+
edgeConfigId: z4.string().optional(),
|
|
337
|
+
vercelApiToken: z4.string().optional(),
|
|
338
|
+
appwardenApiToken: z4.string()
|
|
339
|
+
});
|
|
340
|
+
var AppwardenConfigSchema = BaseNextJsConfigSchema.refine(
|
|
341
|
+
(data) => !!data.appwardenApiToken,
|
|
342
|
+
{
|
|
343
|
+
message: printMessage(
|
|
344
|
+
"Please provide a valid `appwardenApiToken`. Learn more at https://appwarden.com/docs/api-tokens."
|
|
345
|
+
),
|
|
346
|
+
path: ["appwardenApiToken"]
|
|
347
|
+
}
|
|
348
|
+
).refine(
|
|
349
|
+
(data) => {
|
|
350
|
+
return data.provider === "upstash" && data.connectionString.includes("upstash.io") || data.provider === "edge-config" && data.connectionString.includes("edge-config.vercel.com");
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
message: printMessage(
|
|
354
|
+
"Invalid connection string for the selected `provider`"
|
|
355
|
+
),
|
|
356
|
+
path: ["connectionString"]
|
|
357
|
+
}
|
|
358
|
+
).refine(
|
|
359
|
+
(data) => {
|
|
360
|
+
return data.provider === "upstash" && data.connectionString.includes("upstash") || data.provider === "edge-config" && data.connectionString.includes("edge-config");
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
message: printMessage(
|
|
364
|
+
"Invalid connection string for the selected `provider`"
|
|
365
|
+
),
|
|
366
|
+
path: ["connectionString"]
|
|
367
|
+
}
|
|
368
|
+
).refine(
|
|
369
|
+
(data) => {
|
|
370
|
+
if (data.provider === "edge-config") {
|
|
371
|
+
return !!data.vercelApiToken;
|
|
372
|
+
}
|
|
373
|
+
return true;
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
message: printMessage("Missing vercelApiToken when provider=edge-config"),
|
|
377
|
+
path: ["vercelApiToken"]
|
|
378
|
+
}
|
|
379
|
+
).refine(
|
|
380
|
+
(data) => {
|
|
381
|
+
if (data.provider === "edge-config") {
|
|
382
|
+
return !!data.edgeConfigId;
|
|
383
|
+
}
|
|
384
|
+
return true;
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
message: printMessage(
|
|
388
|
+
"Missing `edgeConfigId` when `provider=edge-config`"
|
|
389
|
+
),
|
|
390
|
+
path: ["edgeConfigId"]
|
|
391
|
+
}
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
// src/middlewares/use-content-security-policy.ts
|
|
395
|
+
import { z as z7 } from "zod";
|
|
396
|
+
|
|
397
|
+
// src/types/csp.ts
|
|
398
|
+
import { z as z5 } from "zod";
|
|
399
|
+
var stringySchema = z5.union([z5.array(z5.string()), z5.string(), z5.boolean()]);
|
|
400
|
+
var ContentSecurityPolicySchema = z5.object({
|
|
401
|
+
"default-src": stringySchema.optional(),
|
|
402
|
+
"script-src": stringySchema.optional(),
|
|
403
|
+
"style-src": stringySchema.optional(),
|
|
404
|
+
"img-src": stringySchema.optional(),
|
|
405
|
+
"connect-src": stringySchema.optional(),
|
|
406
|
+
"font-src": stringySchema.optional(),
|
|
407
|
+
"object-src": stringySchema.optional(),
|
|
408
|
+
"media-src": stringySchema.optional(),
|
|
409
|
+
"frame-src": stringySchema.optional(),
|
|
410
|
+
sandbox: stringySchema.optional(),
|
|
411
|
+
"report-uri": stringySchema.optional(),
|
|
412
|
+
"child-src": stringySchema.optional(),
|
|
413
|
+
"form-action": stringySchema.optional(),
|
|
414
|
+
"frame-ancestors": stringySchema.optional(),
|
|
415
|
+
"plugin-types": stringySchema.optional(),
|
|
416
|
+
"base-uri": stringySchema.optional(),
|
|
417
|
+
"report-to": stringySchema.optional(),
|
|
418
|
+
"worker-src": stringySchema.optional(),
|
|
419
|
+
"manifest-src": stringySchema.optional(),
|
|
420
|
+
"prefetch-src": stringySchema.optional(),
|
|
421
|
+
"navigate-to": stringySchema.optional(),
|
|
422
|
+
"require-sri-for": stringySchema.optional(),
|
|
423
|
+
"block-all-mixed-content": stringySchema.optional(),
|
|
424
|
+
"upgrade-insecure-requests": stringySchema.optional(),
|
|
425
|
+
"trusted-types": stringySchema.optional(),
|
|
426
|
+
"require-trusted-types-for": stringySchema.optional()
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
// src/types/middleware.ts
|
|
430
|
+
import { z as z6 } from "zod";
|
|
431
|
+
var MiddlewareNextSchema = z6.union([
|
|
432
|
+
z6.void(),
|
|
433
|
+
z6.null(),
|
|
434
|
+
z6.promise(z6.union([z6.void(), z6.null()]))
|
|
435
|
+
]);
|
|
436
|
+
|
|
437
|
+
// src/middlewares/use-content-security-policy.ts
|
|
438
|
+
var CSPDirectivesSchema = z7.union([
|
|
439
|
+
z7.string(),
|
|
440
|
+
ContentSecurityPolicySchema
|
|
441
|
+
]);
|
|
442
|
+
var CSPEnforcedSchema = BoolOrStringSchema;
|
|
443
|
+
var InputConfigSchema = z7.object({
|
|
444
|
+
directives: CSPDirectivesSchema,
|
|
445
|
+
enforce: CSPEnforcedSchema
|
|
446
|
+
});
|
|
447
|
+
var OutputSchema = z7.object({
|
|
448
|
+
directives: z7.union([z7.string(), ContentSecurityPolicySchema]).transform(
|
|
449
|
+
(val) => typeof val === "string" ? JSON.parse(val) : val
|
|
450
|
+
),
|
|
451
|
+
enforce: z7.boolean().default(false)
|
|
452
|
+
});
|
|
453
|
+
var AppendAttribute = (attribute, nonce) => ({
|
|
454
|
+
element: function(element) {
|
|
455
|
+
element.setAttribute(attribute, nonce);
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
var addNonce = (value, cspNonce) => value.replace("{{nonce}}", `'nonce-${cspNonce}'`);
|
|
459
|
+
var makeCSPHeader = (cspNonce, directives, enforce) => {
|
|
460
|
+
const namesSeen = /* @__PURE__ */ new Set(), result = [];
|
|
461
|
+
Object.entries(directives).forEach(([originalName, value]) => {
|
|
462
|
+
const name = originalName.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
463
|
+
if (namesSeen.has(name)) {
|
|
464
|
+
throw new Error(`${originalName} is specified more than once`);
|
|
465
|
+
}
|
|
466
|
+
namesSeen.add(name);
|
|
467
|
+
if (Array.isArray(value)) {
|
|
468
|
+
value = addNonce(value.join(" "), cspNonce);
|
|
469
|
+
} else if (value === true) {
|
|
470
|
+
value = "";
|
|
471
|
+
}
|
|
472
|
+
if (value) {
|
|
473
|
+
result.push(`${name} ${addNonce(value, cspNonce)}`);
|
|
474
|
+
} else if (value !== false) {
|
|
475
|
+
result.push(name);
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
return [
|
|
479
|
+
enforce ? "Content-Security-Policy" : "Content-Security-Policy-Report-Only",
|
|
480
|
+
result.join("; ")
|
|
481
|
+
];
|
|
482
|
+
};
|
|
483
|
+
var useContentSecurityPolicy = (input) => {
|
|
484
|
+
const parsedInput = OutputSchema.safeParse(input);
|
|
485
|
+
if (!parsedInput.success) {
|
|
486
|
+
throw new Error(
|
|
487
|
+
printMessage(`Input validation failed ${parsedInput.error.message}`)
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
const config = parsedInput.data;
|
|
491
|
+
return async (context, next) => {
|
|
492
|
+
const { request } = context;
|
|
493
|
+
const response = await fetch(
|
|
494
|
+
new Request(request, {
|
|
495
|
+
...request,
|
|
496
|
+
redirect: "follow"
|
|
497
|
+
})
|
|
498
|
+
);
|
|
499
|
+
if (response.status > 300 && response.status < 400 || response.redirected) {
|
|
500
|
+
context.response = Response.redirect(
|
|
501
|
+
response.url,
|
|
502
|
+
response.status === 200 ? 307 : response.status
|
|
503
|
+
);
|
|
504
|
+
await next();
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
if (response.headers.has("Content-Type") && !response.headers.get("Content-Type")?.includes("text/html")) {
|
|
508
|
+
context.response = response;
|
|
509
|
+
await next();
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
const cspNonce = btoa(crypto.getRandomValues(new Uint32Array(2)).toString());
|
|
513
|
+
const [cspHeaderName, cspHeaderValue] = makeCSPHeader(
|
|
514
|
+
cspNonce,
|
|
515
|
+
config.directives,
|
|
516
|
+
config.enforce
|
|
517
|
+
);
|
|
518
|
+
const nextResponse = new Response(response.body, response);
|
|
519
|
+
nextResponse.headers.set(cspHeaderName, cspHeaderValue);
|
|
520
|
+
nextResponse.headers.set("content-type", "text/html; charset=utf-8");
|
|
521
|
+
removedHeaders.forEach((name) => {
|
|
522
|
+
nextResponse.headers.delete(name);
|
|
523
|
+
});
|
|
524
|
+
securityHeaders.forEach(([name, value]) => {
|
|
525
|
+
nextResponse.headers.set(name, value);
|
|
526
|
+
});
|
|
527
|
+
context.response = new HTMLRewriter().on("style", AppendAttribute("nonce", cspNonce)).on("script", AppendAttribute("nonce", cspNonce)).transform(nextResponse);
|
|
528
|
+
await next();
|
|
529
|
+
};
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
// src/utils/cloudflare/cloudflare-cache.ts
|
|
533
|
+
var store = {
|
|
534
|
+
json: (context, cacheKey, options) => {
|
|
535
|
+
const cacheKeyUrl = new URL(cacheKey, context.serviceOrigin);
|
|
536
|
+
return {
|
|
537
|
+
getValue: () => getCacheValue(context, cacheKeyUrl),
|
|
538
|
+
updateValue: (json) => updateCacheValue(
|
|
539
|
+
context,
|
|
540
|
+
cacheKeyUrl,
|
|
541
|
+
json,
|
|
542
|
+
options?.cacheExpirationSeconds
|
|
543
|
+
),
|
|
544
|
+
deleteValue: () => clearCache(context, cacheKeyUrl)
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
var getCacheValue = async (context, cacheKey) => {
|
|
549
|
+
const match = await context.cache.match(cacheKey);
|
|
550
|
+
if (!match) {
|
|
551
|
+
debug(`[${cacheKey.pathname}] Cache MISS!`);
|
|
552
|
+
return void 0;
|
|
553
|
+
}
|
|
554
|
+
debug(`[${cacheKey.pathname}] Cache MATCH!`);
|
|
555
|
+
return match;
|
|
556
|
+
};
|
|
557
|
+
var updateCacheValue = async (context, cacheKey, value, cacheExpirationSeconds) => {
|
|
558
|
+
debug(
|
|
559
|
+
"updating cache...",
|
|
560
|
+
cacheKey.pathname,
|
|
561
|
+
value,
|
|
562
|
+
cacheExpirationSeconds ? `expires in ${cacheExpirationSeconds}s` : ""
|
|
563
|
+
);
|
|
564
|
+
await context.cache.put(
|
|
565
|
+
cacheKey,
|
|
566
|
+
new Response(JSON.stringify(value), {
|
|
567
|
+
headers: {
|
|
568
|
+
"content-type": "application/json",
|
|
569
|
+
...cacheExpirationSeconds && {
|
|
570
|
+
"cache-control": `max-age=${cacheExpirationSeconds}`
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
})
|
|
574
|
+
);
|
|
575
|
+
};
|
|
576
|
+
var clearCache = (context, cacheKey) => context.cache.delete(cacheKey);
|
|
577
|
+
|
|
578
|
+
// src/utils/cloudflare/create-response.ts
|
|
579
|
+
var createResponse = (body, status) => new Response(body, {
|
|
580
|
+
status
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
// src/utils/cloudflare/delete-edge-value.ts
|
|
584
|
+
var deleteEdgeValue2 = async (context) => {
|
|
585
|
+
try {
|
|
586
|
+
switch (context.provider) {
|
|
587
|
+
case "cloudflare-cache": {
|
|
588
|
+
const success = await context.edgeCache.deleteValue();
|
|
589
|
+
if (!success) {
|
|
590
|
+
throw new Error();
|
|
591
|
+
}
|
|
592
|
+
break;
|
|
593
|
+
}
|
|
594
|
+
default:
|
|
595
|
+
throw new Error(`Unsupported provider: ${context.provider}`);
|
|
596
|
+
}
|
|
597
|
+
} catch (e) {
|
|
598
|
+
const message = "Failed to delete edge value";
|
|
599
|
+
console.error(
|
|
600
|
+
printMessage(e instanceof Error ? `${message} - ${e.message}` : message)
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
// src/utils/cloudflare/get-lock-value.ts
|
|
606
|
+
var getLockValue2 = async (context) => {
|
|
607
|
+
try {
|
|
608
|
+
let shouldDeleteEdgeValue = false;
|
|
609
|
+
let cacheResponse, lockValue = {
|
|
610
|
+
isLockedTest: 0,
|
|
611
|
+
isLocked: 0,
|
|
612
|
+
lastCheck: Date.now(),
|
|
613
|
+
code: ""
|
|
614
|
+
};
|
|
615
|
+
switch (context.provider) {
|
|
616
|
+
case "cloudflare-cache": {
|
|
617
|
+
cacheResponse = await context.edgeCache.getValue();
|
|
618
|
+
break;
|
|
619
|
+
}
|
|
620
|
+
default:
|
|
621
|
+
throw new Error(`Unsupported provider: ${context.provider}`);
|
|
622
|
+
}
|
|
623
|
+
if (!cacheResponse) {
|
|
624
|
+
return { lockValue: void 0 };
|
|
625
|
+
}
|
|
626
|
+
try {
|
|
627
|
+
const clonedResponse = cacheResponse?.clone();
|
|
628
|
+
lockValue = LockValue.parse(
|
|
629
|
+
clonedResponse ? await clonedResponse.json() : void 0
|
|
630
|
+
);
|
|
631
|
+
} catch (error) {
|
|
632
|
+
console.error(
|
|
633
|
+
printMessage(
|
|
634
|
+
`Failed to parse ${context.keyName} from edge cache - ${error}`
|
|
635
|
+
)
|
|
636
|
+
);
|
|
637
|
+
shouldDeleteEdgeValue = true;
|
|
638
|
+
}
|
|
639
|
+
return { lockValue, shouldDeleteEdgeValue };
|
|
640
|
+
} catch (e) {
|
|
641
|
+
const message = "Failed to retrieve edge value";
|
|
642
|
+
console.error(
|
|
643
|
+
printMessage(e instanceof Error ? `${message} - ${e.message}` : message)
|
|
644
|
+
);
|
|
645
|
+
return { lockValue: void 0 };
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
// src/utils/cloudflare/sync-edge-value.ts
|
|
650
|
+
var APIError2 = class extends Error {
|
|
651
|
+
constructor(message) {
|
|
652
|
+
super(message);
|
|
653
|
+
this.name = "APIError";
|
|
654
|
+
}
|
|
655
|
+
};
|
|
656
|
+
var syncEdgeValue2 = async (context) => {
|
|
657
|
+
debug(`syncing with api ${"https://bot-gateway.appwarden.io"}`);
|
|
658
|
+
try {
|
|
659
|
+
const response = await fetch(new URL("/v1/status/check", "https://bot-gateway.appwarden.io"), {
|
|
660
|
+
method: "POST",
|
|
661
|
+
headers: { "content-type": "application/json" },
|
|
662
|
+
body: JSON.stringify({
|
|
663
|
+
service: "cloudflare",
|
|
664
|
+
provider: context.provider,
|
|
665
|
+
fqdn: context.requestUrl.hostname,
|
|
666
|
+
appwardenApiToken: context.appwardenApiToken
|
|
667
|
+
})
|
|
668
|
+
});
|
|
669
|
+
if (response.status !== 200) {
|
|
670
|
+
throw new Error(`${response.status} ${response.statusText}`);
|
|
671
|
+
}
|
|
672
|
+
if (response.headers.get("content-type")?.includes("application/json")) {
|
|
673
|
+
const result = await response.json();
|
|
674
|
+
if (result.error) {
|
|
675
|
+
throw new APIError2(result.error.message);
|
|
676
|
+
}
|
|
677
|
+
if (result.content) {
|
|
678
|
+
try {
|
|
679
|
+
const parsedValue = LockValue.omit({ lastCheck: true }).parse(
|
|
680
|
+
result.content
|
|
681
|
+
);
|
|
682
|
+
debug(
|
|
683
|
+
`syncing with api...DONE ${JSON.stringify(parsedValue, null, 2)}`
|
|
684
|
+
);
|
|
685
|
+
await context.edgeCache.updateValue({
|
|
686
|
+
...parsedValue,
|
|
687
|
+
lastCheck: Date.now()
|
|
688
|
+
});
|
|
689
|
+
} catch (error) {
|
|
690
|
+
throw new APIError2(`Failed to parse check endpoint result - ${error}`);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
} catch (e) {
|
|
695
|
+
const message = "Failed to fetch from check endpoint";
|
|
696
|
+
console.error(
|
|
697
|
+
printMessage(
|
|
698
|
+
e instanceof APIError2 ? e.message : e instanceof Error ? `${message} - ${e.message}` : message
|
|
699
|
+
)
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
// src/handlers/maybe-quarantine.ts
|
|
705
|
+
var resolveLockValue = async (context, options) => {
|
|
706
|
+
const { lockValue, shouldDeleteEdgeValue } = await getLockValue2(context);
|
|
707
|
+
if (shouldDeleteEdgeValue) {
|
|
708
|
+
await deleteEdgeValue2(context);
|
|
709
|
+
}
|
|
710
|
+
if (lockValue?.isLocked || context.requestUrl.pathname === APPWARDEN_TEST_ROUTE && !MemoryCache.isTestExpired(lockValue)) {
|
|
711
|
+
await options.onLocked();
|
|
712
|
+
}
|
|
713
|
+
return lockValue;
|
|
714
|
+
};
|
|
715
|
+
var maybeQuarantine = async (context, options) => {
|
|
716
|
+
const cachedLockValue = await resolveLockValue(context, {
|
|
717
|
+
onLocked: options.onLocked
|
|
718
|
+
});
|
|
719
|
+
const shouldRecheck = MemoryCache.isExpired(cachedLockValue);
|
|
720
|
+
if (shouldRecheck) {
|
|
721
|
+
if (!cachedLockValue || cachedLockValue.isLocked) {
|
|
722
|
+
await syncEdgeValue2(context);
|
|
723
|
+
await resolveLockValue(context, {
|
|
724
|
+
onLocked: options.onLocked
|
|
725
|
+
});
|
|
726
|
+
} else {
|
|
727
|
+
context.waitUntil(syncEdgeValue2(context));
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
// src/handlers/reset-cache.ts
|
|
733
|
+
var isResetCacheRequest = (request) => request.method === "POST" && new URL(request.url).pathname === "/__appwarden/reset-cache" && request.headers.get("content-type") === "application/json";
|
|
734
|
+
var handleResetCache = async (keyName, provider, edgeCache, request) => {
|
|
735
|
+
const { lockValue } = await getLockValue2({
|
|
736
|
+
keyName,
|
|
737
|
+
provider,
|
|
738
|
+
edgeCache
|
|
739
|
+
});
|
|
740
|
+
try {
|
|
741
|
+
const body = await request.clone().json();
|
|
742
|
+
if (body.code === lockValue?.code) {
|
|
743
|
+
await edgeCache.deleteValue();
|
|
744
|
+
}
|
|
745
|
+
} catch (error) {
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
// src/middlewares/use-appwarden.ts
|
|
750
|
+
var useAppwarden = (input) => async (context, next) => {
|
|
751
|
+
const { request, response } = context;
|
|
752
|
+
try {
|
|
753
|
+
const requestUrl = new URL(request.url);
|
|
754
|
+
const provider = "cloudflare-cache";
|
|
755
|
+
const keyName = "appwarden-lock";
|
|
756
|
+
const edgeCache = store.json(
|
|
757
|
+
{
|
|
758
|
+
serviceOrigin: requestUrl.origin,
|
|
759
|
+
cache: await caches.open("appwarden:lock")
|
|
760
|
+
},
|
|
761
|
+
keyName
|
|
762
|
+
);
|
|
763
|
+
if (isResetCacheRequest(request)) {
|
|
764
|
+
await handleResetCache(keyName, provider, edgeCache, request);
|
|
765
|
+
await next();
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
const isHTMLRequest = response.headers.get("Content-Type")?.includes("text/html");
|
|
769
|
+
if (isHTMLRequest) {
|
|
770
|
+
const context_ = {
|
|
771
|
+
keyName,
|
|
772
|
+
request,
|
|
773
|
+
edgeCache,
|
|
774
|
+
requestUrl,
|
|
775
|
+
provider,
|
|
776
|
+
debug: input.debug,
|
|
777
|
+
lockPageSlug: input.lockPageSlug,
|
|
778
|
+
appwardenApiToken: input.appwardenApiToken,
|
|
779
|
+
waitUntil: (fn) => context.waitUntil(fn)
|
|
780
|
+
};
|
|
781
|
+
await maybeQuarantine(context_, {
|
|
782
|
+
onLocked: async () => {
|
|
783
|
+
context.response = await renderLockPage(context_);
|
|
784
|
+
}
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
} catch (e) {
|
|
788
|
+
const message = "Appwarden encountered an unknown error. Please contact Appwarden support at https://appwarden.io/join-community.";
|
|
789
|
+
console.error(
|
|
790
|
+
printMessage(
|
|
791
|
+
e instanceof Error ? `${message} - ${e.message}` : message
|
|
792
|
+
)
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
await next();
|
|
796
|
+
};
|
|
797
|
+
|
|
798
|
+
export {
|
|
799
|
+
LOCKDOWN_TEST_EXPIRY_MS,
|
|
800
|
+
globalErrors,
|
|
801
|
+
debug,
|
|
802
|
+
MemoryCache,
|
|
803
|
+
printMessage,
|
|
804
|
+
CloudflareConfigFnOutputSchema,
|
|
805
|
+
NextJsConfigFnOutputSchema,
|
|
806
|
+
BaseNextJsConfigSchema,
|
|
807
|
+
AppwardenConfigSchema,
|
|
808
|
+
syncEdgeValue,
|
|
809
|
+
handleVercelRequest,
|
|
810
|
+
usePipeline,
|
|
811
|
+
renderLockPage,
|
|
812
|
+
store,
|
|
813
|
+
createResponse,
|
|
814
|
+
maybeQuarantine,
|
|
815
|
+
isResetCacheRequest,
|
|
816
|
+
handleResetCache,
|
|
817
|
+
useAppwarden,
|
|
818
|
+
CSPDirectivesSchema,
|
|
819
|
+
CSPEnforcedSchema,
|
|
820
|
+
useContentSecurityPolicy
|
|
821
|
+
};
|