@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/vercel.js
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getEdgeConfigId,
|
|
3
|
+
isCacheUrl,
|
|
4
|
+
isValidCacheUrl
|
|
5
|
+
} from "./chunk-QEFORWCW.js";
|
|
6
|
+
import {
|
|
7
|
+
LockValue,
|
|
8
|
+
MemoryCache,
|
|
9
|
+
getErrors
|
|
10
|
+
} from "./chunk-47MLTBFC.js";
|
|
11
|
+
import {
|
|
12
|
+
APPWARDEN_CACHE_KEY,
|
|
13
|
+
APPWARDEN_TEST_ROUTE,
|
|
14
|
+
APPWARDEN_USER_AGENT,
|
|
15
|
+
debug,
|
|
16
|
+
errors,
|
|
17
|
+
globalErrors,
|
|
18
|
+
printMessage
|
|
19
|
+
} from "./chunk-JWUAFJ2E.js";
|
|
20
|
+
|
|
21
|
+
// src/runners/appwarden-on-vercel.ts
|
|
22
|
+
import { NextResponse } from "next/server";
|
|
23
|
+
|
|
24
|
+
// src/schemas/vercel.ts
|
|
25
|
+
import { z } from "zod";
|
|
26
|
+
|
|
27
|
+
// src/utils/vercel/delete-edge-value.ts
|
|
28
|
+
var deleteEdgeValue = async ({
|
|
29
|
+
keyName,
|
|
30
|
+
provider,
|
|
31
|
+
cacheUrl,
|
|
32
|
+
vercelApiToken
|
|
33
|
+
}) => {
|
|
34
|
+
try {
|
|
35
|
+
switch (provider) {
|
|
36
|
+
case "edge-config": {
|
|
37
|
+
const edgeConfigId = getEdgeConfigId(cacheUrl);
|
|
38
|
+
if (!edgeConfigId) {
|
|
39
|
+
throw new Error("Failed to parse `edgeConfigId`");
|
|
40
|
+
}
|
|
41
|
+
const res = await fetch(
|
|
42
|
+
`https://api.vercel.com/v1/edge-config/${edgeConfigId}/items`,
|
|
43
|
+
{
|
|
44
|
+
method: "PATCH",
|
|
45
|
+
headers: {
|
|
46
|
+
Authorization: `Bearer ${vercelApiToken}`,
|
|
47
|
+
"Content-Type": "application/json"
|
|
48
|
+
},
|
|
49
|
+
body: JSON.stringify({
|
|
50
|
+
items: [
|
|
51
|
+
{
|
|
52
|
+
key: keyName,
|
|
53
|
+
operation: "delete"
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
if (res.status !== 200) {
|
|
60
|
+
let response = void 0;
|
|
61
|
+
try {
|
|
62
|
+
response = await res.json();
|
|
63
|
+
} catch (error) {
|
|
64
|
+
}
|
|
65
|
+
throw new Error(
|
|
66
|
+
`api.vercel.com/v1/edge-config responded with ${res.status} - ${res.statusText}${response?.error?.message ? ` - ${response?.error?.message}` : ""}`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
case "upstash": {
|
|
72
|
+
const { hostname, password } = new URL(cacheUrl);
|
|
73
|
+
const { Redis } = await import("@upstash/redis");
|
|
74
|
+
const redis = new Redis({ url: `https://${hostname}`, token: password });
|
|
75
|
+
await redis.del(keyName);
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
default:
|
|
79
|
+
throw new Error(`Unsupported provider: ${provider}`);
|
|
80
|
+
}
|
|
81
|
+
} catch (e) {
|
|
82
|
+
const message = "Failed to delete edge value";
|
|
83
|
+
console.error(
|
|
84
|
+
printMessage(e instanceof Error ? `${message} - ${e.message}` : message)
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// src/utils/vercel/get-lock-value.ts
|
|
90
|
+
var getLockValue = async (context) => {
|
|
91
|
+
try {
|
|
92
|
+
let shouldDeleteEdgeValue = false;
|
|
93
|
+
let serializedValue, lockValue = {
|
|
94
|
+
isLocked: 0,
|
|
95
|
+
isLockedTest: 0,
|
|
96
|
+
lastCheck: 0,
|
|
97
|
+
code: ""
|
|
98
|
+
};
|
|
99
|
+
switch (context.provider) {
|
|
100
|
+
case "edge-config": {
|
|
101
|
+
const { createClient } = await import("@vercel/edge-config");
|
|
102
|
+
const edgeConfig = createClient(context.cacheUrl);
|
|
103
|
+
serializedValue = await edgeConfig.get(
|
|
104
|
+
context.keyName
|
|
105
|
+
);
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
case "upstash": {
|
|
109
|
+
const { hostname, password } = new URL(context.cacheUrl);
|
|
110
|
+
const { Redis } = await import("@upstash/redis");
|
|
111
|
+
const redis = new Redis({
|
|
112
|
+
url: `https://${hostname}`,
|
|
113
|
+
token: password
|
|
114
|
+
});
|
|
115
|
+
const redisValue = await redis.get(
|
|
116
|
+
context.keyName
|
|
117
|
+
);
|
|
118
|
+
serializedValue = redisValue === null ? void 0 : redisValue;
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
default:
|
|
122
|
+
throw new Error(`Unsupported provider: ${context.provider}`);
|
|
123
|
+
}
|
|
124
|
+
if (!serializedValue) {
|
|
125
|
+
return { lockValue: void 0 };
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
lockValue = LockValue.parse(
|
|
129
|
+
typeof serializedValue === "string" ? JSON.parse(serializedValue) : serializedValue
|
|
130
|
+
);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error(
|
|
133
|
+
printMessage(
|
|
134
|
+
`Failed to parse ${context.keyName} from edge cache - ${error}`
|
|
135
|
+
)
|
|
136
|
+
);
|
|
137
|
+
shouldDeleteEdgeValue = true;
|
|
138
|
+
}
|
|
139
|
+
return { lockValue, shouldDeleteEdgeValue };
|
|
140
|
+
} catch (e) {
|
|
141
|
+
const message = "Failed to retrieve edge value";
|
|
142
|
+
console.error(
|
|
143
|
+
printMessage(e instanceof Error ? `${message} - ${e.message}` : message)
|
|
144
|
+
);
|
|
145
|
+
if (e instanceof Error) {
|
|
146
|
+
if (e.message.includes("Invalid connection string provided")) {
|
|
147
|
+
throw new Error(errors.badCacheConnection);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return { lockValue: void 0 };
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// src/utils/vercel/sync-edge-value.ts
|
|
155
|
+
var APIError = class extends Error {
|
|
156
|
+
constructor(message) {
|
|
157
|
+
super(message);
|
|
158
|
+
this.name = "APIError";
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
var syncEdgeValue = async (context) => {
|
|
162
|
+
debug(`syncing with api`);
|
|
163
|
+
try {
|
|
164
|
+
const response = await fetch(new URL("/v1/status/check", "https://bot-gateway.appwarden.io"), {
|
|
165
|
+
method: "POST",
|
|
166
|
+
headers: { "content-type": "application/json" },
|
|
167
|
+
body: JSON.stringify({
|
|
168
|
+
service: "vercel",
|
|
169
|
+
cacheUrl: context.cacheUrl,
|
|
170
|
+
fqdn: context.requestUrl.hostname,
|
|
171
|
+
vercelApiToken: context.vercelApiToken ?? "",
|
|
172
|
+
appwardenApiToken: context.appwardenApiToken
|
|
173
|
+
})
|
|
174
|
+
});
|
|
175
|
+
if (response.status !== 200) {
|
|
176
|
+
throw new Error(`${response.status} ${response.statusText}`);
|
|
177
|
+
}
|
|
178
|
+
if (response.headers.get("content-type")?.includes("application/json")) {
|
|
179
|
+
const result = await response.json();
|
|
180
|
+
if (result.error) {
|
|
181
|
+
throw new APIError(result.error.message);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
} catch (e) {
|
|
185
|
+
const message = "Failed to fetch from check endpoint";
|
|
186
|
+
console.error(
|
|
187
|
+
printMessage(
|
|
188
|
+
e instanceof APIError ? e.message : e instanceof Error ? `${message} - ${e.message}` : message
|
|
189
|
+
)
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// src/utils/handle-vercel-request.ts
|
|
195
|
+
var handleVercelRequest = async (context, options) => {
|
|
196
|
+
let cachedLockValue = context.memoryCache.get(context.keyName);
|
|
197
|
+
const shouldRecheck = MemoryCache.isExpired(cachedLockValue);
|
|
198
|
+
if (shouldRecheck) {
|
|
199
|
+
const { lockValue, shouldDeleteEdgeValue } = await getLockValue(context);
|
|
200
|
+
if (lockValue) {
|
|
201
|
+
context.memoryCache.put(context.keyName, lockValue);
|
|
202
|
+
}
|
|
203
|
+
if (shouldDeleteEdgeValue) {
|
|
204
|
+
await deleteEdgeValue(context);
|
|
205
|
+
}
|
|
206
|
+
cachedLockValue = lockValue;
|
|
207
|
+
}
|
|
208
|
+
if (cachedLockValue?.isLocked || context.requestUrl.pathname === APPWARDEN_TEST_ROUTE && !MemoryCache.isTestExpired(cachedLockValue)) {
|
|
209
|
+
options.onLocked();
|
|
210
|
+
}
|
|
211
|
+
return cachedLockValue;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// src/schemas/vercel.ts
|
|
215
|
+
var BaseNextJsConfigSchema = z.object({
|
|
216
|
+
cacheUrl: z.string(),
|
|
217
|
+
appwardenApiToken: z.string(),
|
|
218
|
+
vercelApiToken: z.string().optional(),
|
|
219
|
+
lockPageSlug: z.string().default("").transform((val) => val.replace(/^\/?/, "/"))
|
|
220
|
+
});
|
|
221
|
+
var AppwardenConfigSchema = BaseNextJsConfigSchema.refine(
|
|
222
|
+
(data) => {
|
|
223
|
+
return isCacheUrl.edgeConfig(data.cacheUrl) || isCacheUrl.upstash(data.cacheUrl);
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
message: printMessage(
|
|
227
|
+
"Provided `cacheUrl` is not recognized. Please provide a Vercel Edge Config or Upstash KV url."
|
|
228
|
+
),
|
|
229
|
+
path: ["cacheUrl"]
|
|
230
|
+
}
|
|
231
|
+
).superRefine((data, ctx) => {
|
|
232
|
+
if (!isValidCacheUrl.edgeConfig(data.cacheUrl) && isValidCacheUrl.upstash(data.cacheUrl)) {
|
|
233
|
+
ctx.addIssue({
|
|
234
|
+
code: "custom",
|
|
235
|
+
message: printMessage(
|
|
236
|
+
"Provided Vercel Edge Config `cacheUrl` is not valid. It should be in the format https://edge-config.vercel.com/ecfg_*"
|
|
237
|
+
),
|
|
238
|
+
path: ["cacheUrl"]
|
|
239
|
+
});
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
if (!isValidCacheUrl.upstash(data.cacheUrl) && isValidCacheUrl.edgeConfig(data.cacheUrl)) {
|
|
243
|
+
ctx.addIssue({
|
|
244
|
+
code: "custom",
|
|
245
|
+
message: printMessage(
|
|
246
|
+
"Provided Upstash KV `cacheUrl` is not valid. It should be in the format rediss://:password@hostname.upstash.io:6379"
|
|
247
|
+
),
|
|
248
|
+
path: ["cacheUrl"]
|
|
249
|
+
});
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
return true;
|
|
253
|
+
}).refine(
|
|
254
|
+
(data) => isCacheUrl.edgeConfig(data.cacheUrl) ? !!data.vercelApiToken : true,
|
|
255
|
+
{
|
|
256
|
+
message: printMessage(
|
|
257
|
+
"The `vercelApiToken` option is required when using Vercel Edge Config"
|
|
258
|
+
),
|
|
259
|
+
path: ["vercelApiToken"]
|
|
260
|
+
}
|
|
261
|
+
).refine((data) => !!data.appwardenApiToken, {
|
|
262
|
+
message: printMessage(
|
|
263
|
+
"Please provide a valid `appwardenApiToken`. Learn more at https://appwarden.com/docs/guides/api-token-management."
|
|
264
|
+
),
|
|
265
|
+
path: ["appwardenApiToken"]
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// src/runners/appwarden-on-vercel.ts
|
|
269
|
+
debug("Instantiating isolate");
|
|
270
|
+
var renderLockPage = (context) => {
|
|
271
|
+
context.req.nextUrl.pathname = context.lockPageSlug;
|
|
272
|
+
return NextResponse.rewrite(context.req.nextUrl, {
|
|
273
|
+
headers: {
|
|
274
|
+
// no browser caching, otherwise we need to hard refresh to disable lock screen
|
|
275
|
+
"Cache-Control": "no-store"
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
};
|
|
279
|
+
var memoryCache = new MemoryCache({ maxSize: 1 });
|
|
280
|
+
var appwardenOnVercel = (input) => async (req, event) => {
|
|
281
|
+
event.passThroughOnException();
|
|
282
|
+
const parsedConfig = AppwardenConfigSchema.safeParse(input);
|
|
283
|
+
if (!parsedConfig.success) {
|
|
284
|
+
for (const error of getErrors(parsedConfig.error)) {
|
|
285
|
+
console.error(printMessage(error));
|
|
286
|
+
}
|
|
287
|
+
return NextResponse.next();
|
|
288
|
+
}
|
|
289
|
+
try {
|
|
290
|
+
const requestUrl = new URL(req.url);
|
|
291
|
+
const isHTMLRequest = req.headers.get("accept")?.includes("text/html");
|
|
292
|
+
const isMonitoringRequest = req.headers.get("User-Agent") === APPWARDEN_USER_AGENT;
|
|
293
|
+
debug({
|
|
294
|
+
isHTMLRequest,
|
|
295
|
+
url: requestUrl.pathname
|
|
296
|
+
});
|
|
297
|
+
if (isHTMLRequest && !isMonitoringRequest) {
|
|
298
|
+
let appwardenResponse = void 0;
|
|
299
|
+
const context = {
|
|
300
|
+
req,
|
|
301
|
+
event,
|
|
302
|
+
requestUrl,
|
|
303
|
+
memoryCache,
|
|
304
|
+
waitUntil: (fn) => event.waitUntil(fn),
|
|
305
|
+
keyName: APPWARDEN_CACHE_KEY,
|
|
306
|
+
provider: isCacheUrl.edgeConfig(parsedConfig.data.cacheUrl) ? "edge-config" : "upstash",
|
|
307
|
+
...parsedConfig.data
|
|
308
|
+
};
|
|
309
|
+
const cacheValue = await handleVercelRequest(context, {
|
|
310
|
+
onLocked: () => {
|
|
311
|
+
appwardenResponse = renderLockPage(context);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
const shouldRecheck = MemoryCache.isExpired(cacheValue);
|
|
315
|
+
if (!cacheValue || shouldRecheck) {
|
|
316
|
+
event.waitUntil(syncEdgeValue(context));
|
|
317
|
+
}
|
|
318
|
+
if (appwardenResponse) {
|
|
319
|
+
return appwardenResponse;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
} catch (e) {
|
|
323
|
+
const message = "Appwarden encountered an unknown error. Please contact Appwarden support at https://appwarden.io/join-community.";
|
|
324
|
+
if (e instanceof Error) {
|
|
325
|
+
if (!globalErrors.includes(e.message)) {
|
|
326
|
+
console.error(printMessage(`${message} - ${e.message}`));
|
|
327
|
+
}
|
|
328
|
+
} else {
|
|
329
|
+
console.error(printMessage(message));
|
|
330
|
+
}
|
|
331
|
+
throw e;
|
|
332
|
+
}
|
|
333
|
+
return NextResponse.next();
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// src/bundles/vercel.ts
|
|
337
|
+
var withAppwarden = appwardenOnVercel;
|
|
338
|
+
export {
|
|
339
|
+
BaseNextJsConfigSchema,
|
|
340
|
+
withAppwarden
|
|
341
|
+
};
|