@appwarden/middleware 3.2.0 → 3.3.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 +257 -22
- package/{chunk-COV6SHCD.js → chunk-7AVYENM2.js} +2 -4
- package/chunk-BYRGGUK7.js +51 -0
- package/{chunk-FGAJVKNM.js → chunk-HCGLR3Z3.js} +22 -75
- package/chunk-PH77FI6C.js +70 -0
- package/{chunk-ZX5QO4Y2.js → chunk-SUZPTFWY.js} +2 -58
- package/chunk-ZBYVJ3HA.js +354 -0
- package/cloudflare/astro.d.ts +4 -0
- package/cloudflare/astro.js +33 -9
- package/cloudflare/nextjs.d.ts +4 -0
- package/cloudflare/nextjs.js +35 -8
- package/cloudflare/react-router.d.ts +5 -0
- package/cloudflare/react-router.js +33 -9
- package/cloudflare/tanstack-start.d.ts +5 -0
- package/cloudflare/tanstack-start.js +33 -9
- package/cloudflare-LUT5TVEV.js +28 -0
- package/cloudflare.d.ts +440 -8
- package/cloudflare.js +25 -47
- package/index.d.ts +2 -1
- package/index.js +5 -4
- package/package.json +8 -3
- package/use-content-security-policy-CjlLe4yU.d.ts +16 -0
- package/{use-content-security-policy-DjRTjIpm.d.ts → use-content-security-policy-DUYpyUPy.d.ts} +1 -18
- package/vercel.d.ts +677 -0
- package/vercel.js +59 -9
- package/chunk-L5EQIJZB.js +0 -54
- package/chunk-MDODCAA3.js +0 -232
package/vercel.js
CHANGED
|
@@ -4,22 +4,27 @@ import {
|
|
|
4
4
|
} from "./chunk-QEFORWCW.js";
|
|
5
5
|
import {
|
|
6
6
|
validateConfig
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-7AVYENM2.js";
|
|
8
8
|
import {
|
|
9
|
-
LockValue,
|
|
10
9
|
MemoryCache,
|
|
11
10
|
TEMPORARY_REDIRECT_STATUS,
|
|
12
11
|
buildLockPageUrl,
|
|
13
12
|
isOnLockPage
|
|
14
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-SUZPTFWY.js";
|
|
15
14
|
import {
|
|
16
15
|
APPWARDEN_CACHE_KEY,
|
|
17
|
-
|
|
16
|
+
CSPDirectivesSchema,
|
|
17
|
+
CSPModeSchema,
|
|
18
18
|
errors,
|
|
19
19
|
globalErrors,
|
|
20
|
-
isHTMLRequest
|
|
20
|
+
isHTMLRequest
|
|
21
|
+
} from "./chunk-HCGLR3Z3.js";
|
|
22
|
+
import {
|
|
23
|
+
LockValue,
|
|
24
|
+
debug,
|
|
25
|
+
makeCSPHeader,
|
|
21
26
|
printMessage
|
|
22
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-ZBYVJ3HA.js";
|
|
23
28
|
|
|
24
29
|
// src/runners/appwarden-on-vercel.ts
|
|
25
30
|
import { waitUntil } from "@vercel/functions";
|
|
@@ -134,11 +139,42 @@ var syncEdgeValue = async (context) => {
|
|
|
134
139
|
};
|
|
135
140
|
|
|
136
141
|
// src/schemas/vercel.ts
|
|
142
|
+
var VercelCSPSchema = z.object({
|
|
143
|
+
mode: CSPModeSchema,
|
|
144
|
+
directives: z.lazy(() => CSPDirectivesSchema).optional().refine(
|
|
145
|
+
(val) => {
|
|
146
|
+
try {
|
|
147
|
+
if (typeof val === "string") {
|
|
148
|
+
JSON.parse(val);
|
|
149
|
+
}
|
|
150
|
+
return true;
|
|
151
|
+
} catch {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
{ message: "DirectivesBadParse" /* DirectivesBadParse */ }
|
|
156
|
+
).refine(
|
|
157
|
+
(val) => {
|
|
158
|
+
if (!val) return true;
|
|
159
|
+
const serialized = typeof val === "string" ? val : JSON.stringify(val);
|
|
160
|
+
return !serialized.includes("{{nonce}}");
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
message: "Nonce-based CSP is not supported in Vercel Edge Middleware. Remove '{{nonce}}' placeholders from your CSP directives, as Vercel does not support nonce injection."
|
|
164
|
+
}
|
|
165
|
+
).transform(
|
|
166
|
+
(val) => typeof val === "string" ? JSON.parse(val) : val
|
|
167
|
+
)
|
|
168
|
+
}).refine(
|
|
169
|
+
(values) => ["report-only", "enforced"].includes(values.mode) ? !!values.directives : true,
|
|
170
|
+
{ path: ["directives"], message: "DirectivesRequired" /* DirectivesRequired */ }
|
|
171
|
+
);
|
|
137
172
|
var BaseNextJsConfigSchema = z.object({
|
|
138
173
|
cacheUrl: z.string(),
|
|
139
174
|
appwardenApiToken: z.string(),
|
|
140
175
|
vercelApiToken: z.string().optional(),
|
|
141
|
-
lockPageSlug: z.string().default("").transform((val) => val.replace(/^\/?/, "/"))
|
|
176
|
+
lockPageSlug: z.string().default("").transform((val) => val.replace(/^\/?/, "/")),
|
|
177
|
+
contentSecurityPolicy: VercelCSPSchema.optional()
|
|
142
178
|
});
|
|
143
179
|
var AppwardenConfigSchema = BaseNextJsConfigSchema.refine(
|
|
144
180
|
(data) => {
|
|
@@ -203,6 +239,18 @@ function createAppwardenMiddleware(config) {
|
|
|
203
239
|
return NextResponse.next();
|
|
204
240
|
}
|
|
205
241
|
const parsedConfig = AppwardenConfigSchema.parse(config);
|
|
242
|
+
const applyCspHeaders = (response) => {
|
|
243
|
+
const cspConfig = parsedConfig.contentSecurityPolicy;
|
|
244
|
+
if (cspConfig && ["enforced", "report-only"].includes(cspConfig.mode)) {
|
|
245
|
+
const [headerName, headerValue] = makeCSPHeader(
|
|
246
|
+
"",
|
|
247
|
+
cspConfig.directives,
|
|
248
|
+
cspConfig.mode
|
|
249
|
+
);
|
|
250
|
+
response.headers.set(headerName, headerValue);
|
|
251
|
+
}
|
|
252
|
+
return response;
|
|
253
|
+
};
|
|
206
254
|
try {
|
|
207
255
|
const requestUrl = new URL(request.url);
|
|
208
256
|
const isHTML = isHTMLRequest(request);
|
|
@@ -239,12 +287,14 @@ function createAppwardenMiddleware(config) {
|
|
|
239
287
|
parsedConfig.lockPageSlug,
|
|
240
288
|
request.url
|
|
241
289
|
);
|
|
242
|
-
|
|
290
|
+
const redirectResponse = Response.redirect(
|
|
243
291
|
lockPageUrl.toString(),
|
|
244
292
|
TEMPORARY_REDIRECT_STATUS
|
|
245
293
|
);
|
|
294
|
+
return applyCspHeaders(redirectResponse);
|
|
246
295
|
}
|
|
247
|
-
|
|
296
|
+
const response = NextResponse.next();
|
|
297
|
+
return applyCspHeaders(response);
|
|
248
298
|
} catch (e) {
|
|
249
299
|
const message = "Appwarden encountered an unknown error. Please contact Appwarden support at https://appwarden.io/join-community.";
|
|
250
300
|
if (e instanceof Error) {
|
package/chunk-L5EQIJZB.js
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
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_CACHE_KEY = "appwarden-lock";
|
|
7
|
-
|
|
8
|
-
// src/utils/debug.ts
|
|
9
|
-
var debug = (...msg) => {
|
|
10
|
-
if (true) {
|
|
11
|
-
const formatted = msg.map((m) => {
|
|
12
|
-
if (typeof m === "object" && m !== null) {
|
|
13
|
-
if (m instanceof Error) {
|
|
14
|
-
return m.stack ?? m.message;
|
|
15
|
-
}
|
|
16
|
-
try {
|
|
17
|
-
return JSON.stringify(m);
|
|
18
|
-
} catch {
|
|
19
|
-
try {
|
|
20
|
-
return String(m);
|
|
21
|
-
} catch {
|
|
22
|
-
return "[Unserializable value]";
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
return m;
|
|
27
|
-
});
|
|
28
|
-
console.log(...formatted);
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
// src/utils/print-message.ts
|
|
33
|
-
var addSlashes = (str) => str.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$").replace(/"/g, '\\"').replace(/'/g, "\\'").replace(/\u0000/g, "\\0").replace(/<\/script>/gi, "<\\/script>");
|
|
34
|
-
var printMessage = (message) => `[@appwarden/middleware] ${addSlashes(message)}`;
|
|
35
|
-
|
|
36
|
-
// src/utils/request-checks.ts
|
|
37
|
-
function isHTMLResponse(response) {
|
|
38
|
-
return response.headers.get("Content-Type")?.includes("text/html") ?? false;
|
|
39
|
-
}
|
|
40
|
-
function isHTMLRequest(request) {
|
|
41
|
-
return request.headers.get("accept")?.includes("text/html") ?? false;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export {
|
|
45
|
-
LOCKDOWN_TEST_EXPIRY_MS,
|
|
46
|
-
errors,
|
|
47
|
-
globalErrors,
|
|
48
|
-
APPWARDEN_TEST_ROUTE,
|
|
49
|
-
APPWARDEN_CACHE_KEY,
|
|
50
|
-
debug,
|
|
51
|
-
printMessage,
|
|
52
|
-
isHTMLResponse,
|
|
53
|
-
isHTMLRequest
|
|
54
|
-
};
|
package/chunk-MDODCAA3.js
DELETED
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
LockValue,
|
|
3
|
-
MemoryCache
|
|
4
|
-
} from "./chunk-ZX5QO4Y2.js";
|
|
5
|
-
import {
|
|
6
|
-
APPWARDEN_CACHE_KEY,
|
|
7
|
-
APPWARDEN_TEST_ROUTE,
|
|
8
|
-
debug,
|
|
9
|
-
printMessage
|
|
10
|
-
} from "./chunk-L5EQIJZB.js";
|
|
11
|
-
|
|
12
|
-
// src/utils/cloudflare/cloudflare-cache.ts
|
|
13
|
-
var store = {
|
|
14
|
-
json: (context, cacheKey, options) => {
|
|
15
|
-
const cacheKeyUrl = new URL(cacheKey, context.serviceOrigin);
|
|
16
|
-
return {
|
|
17
|
-
getValue: () => getCacheValue(context, cacheKeyUrl),
|
|
18
|
-
updateValue: (json) => updateCacheValue(context, cacheKeyUrl, json, options?.ttl),
|
|
19
|
-
deleteValue: () => clearCache(context, cacheKeyUrl)
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
};
|
|
23
|
-
var getCacheValue = async (context, cacheKey) => {
|
|
24
|
-
const match = await context.cache.match(cacheKey);
|
|
25
|
-
if (!match) {
|
|
26
|
-
debug(`[${cacheKey.pathname}] Cache MISS!`);
|
|
27
|
-
return void 0;
|
|
28
|
-
}
|
|
29
|
-
debug(`[${cacheKey.pathname}] Cache MATCH!`);
|
|
30
|
-
return match;
|
|
31
|
-
};
|
|
32
|
-
var updateCacheValue = async (context, cacheKey, value, ttl) => {
|
|
33
|
-
debug(
|
|
34
|
-
"updating cache...",
|
|
35
|
-
cacheKey.href,
|
|
36
|
-
value,
|
|
37
|
-
ttl ? `expires in ${ttl}s` : ""
|
|
38
|
-
);
|
|
39
|
-
await context.cache.put(
|
|
40
|
-
cacheKey,
|
|
41
|
-
new Response(JSON.stringify(value), {
|
|
42
|
-
headers: {
|
|
43
|
-
"content-type": "application/json",
|
|
44
|
-
...ttl && {
|
|
45
|
-
"cache-control": `max-age=${ttl}`
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
})
|
|
49
|
-
);
|
|
50
|
-
};
|
|
51
|
-
var clearCache = (context, cacheKey) => context.cache.delete(cacheKey);
|
|
52
|
-
|
|
53
|
-
// src/utils/cloudflare/delete-edge-value.ts
|
|
54
|
-
var deleteEdgeValue = async (context) => {
|
|
55
|
-
try {
|
|
56
|
-
switch (context.provider) {
|
|
57
|
-
case "cloudflare-cache": {
|
|
58
|
-
const success = await context.edgeCache.deleteValue();
|
|
59
|
-
if (!success) {
|
|
60
|
-
throw new Error();
|
|
61
|
-
}
|
|
62
|
-
break;
|
|
63
|
-
}
|
|
64
|
-
default:
|
|
65
|
-
throw new Error(`Unsupported provider: ${context.provider}`);
|
|
66
|
-
}
|
|
67
|
-
} catch (e) {
|
|
68
|
-
const message = "Failed to delete edge value";
|
|
69
|
-
console.error(
|
|
70
|
-
printMessage(e instanceof Error ? `${message} - ${e.message}` : message)
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
// src/utils/cloudflare/get-lock-value.ts
|
|
76
|
-
var getLockValue = async (context) => {
|
|
77
|
-
try {
|
|
78
|
-
let shouldDeleteEdgeValue = false;
|
|
79
|
-
let cacheResponse, lockValue = {
|
|
80
|
-
isLocked: 0,
|
|
81
|
-
isLockedTest: 0,
|
|
82
|
-
lastCheck: Date.now(),
|
|
83
|
-
code: ""
|
|
84
|
-
};
|
|
85
|
-
switch (context.provider) {
|
|
86
|
-
case "cloudflare-cache": {
|
|
87
|
-
cacheResponse = await context.edgeCache.getValue();
|
|
88
|
-
break;
|
|
89
|
-
}
|
|
90
|
-
default:
|
|
91
|
-
throw new Error(`Unsupported provider: ${context.provider}`);
|
|
92
|
-
}
|
|
93
|
-
if (!cacheResponse) {
|
|
94
|
-
return { lockValue: void 0 };
|
|
95
|
-
}
|
|
96
|
-
try {
|
|
97
|
-
const clonedResponse = cacheResponse?.clone();
|
|
98
|
-
lockValue = LockValue.parse(
|
|
99
|
-
clonedResponse ? await clonedResponse.json() : void 0
|
|
100
|
-
);
|
|
101
|
-
} catch (error) {
|
|
102
|
-
console.error(
|
|
103
|
-
printMessage(
|
|
104
|
-
`Failed to parse ${context.keyName} from edge cache - ${error}`
|
|
105
|
-
)
|
|
106
|
-
);
|
|
107
|
-
shouldDeleteEdgeValue = true;
|
|
108
|
-
}
|
|
109
|
-
return { lockValue, shouldDeleteEdgeValue };
|
|
110
|
-
} catch (e) {
|
|
111
|
-
const message = "Failed to retrieve edge value";
|
|
112
|
-
console.error(
|
|
113
|
-
printMessage(e instanceof Error ? `${message} - ${e.message}` : message)
|
|
114
|
-
);
|
|
115
|
-
return { lockValue: void 0 };
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
// src/utils/cloudflare/sync-edge-value.ts
|
|
120
|
-
var APIError = class extends Error {
|
|
121
|
-
constructor(message) {
|
|
122
|
-
super(message);
|
|
123
|
-
this.name = "APIError";
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
var DEFAULT_API_HOSTNAME = "https://api.appwarden.io";
|
|
127
|
-
var syncEdgeValue = async (context) => {
|
|
128
|
-
debug(`syncing with api`);
|
|
129
|
-
try {
|
|
130
|
-
const apiHostname = context.appwardenApiHostname ?? DEFAULT_API_HOSTNAME;
|
|
131
|
-
const response = await fetch(new URL("/v1/status/check", apiHostname), {
|
|
132
|
-
method: "POST",
|
|
133
|
-
headers: { "content-type": "application/json" },
|
|
134
|
-
body: JSON.stringify({
|
|
135
|
-
service: "cloudflare",
|
|
136
|
-
provider: context.provider,
|
|
137
|
-
fqdn: context.requestUrl.hostname,
|
|
138
|
-
appwardenApiToken: context.appwardenApiToken
|
|
139
|
-
})
|
|
140
|
-
});
|
|
141
|
-
if (response.status !== 200) {
|
|
142
|
-
throw new Error(`${response.status} ${response.statusText}`);
|
|
143
|
-
}
|
|
144
|
-
if (response.headers.get("content-type")?.includes("application/json")) {
|
|
145
|
-
const result = await response.json();
|
|
146
|
-
if (result.error) {
|
|
147
|
-
throw new APIError(result.error.message);
|
|
148
|
-
}
|
|
149
|
-
if (!result.content) {
|
|
150
|
-
throw new APIError("no content from api");
|
|
151
|
-
}
|
|
152
|
-
try {
|
|
153
|
-
const parsedValue = LockValue.omit({ lastCheck: true }).parse(
|
|
154
|
-
result.content
|
|
155
|
-
);
|
|
156
|
-
debug(`syncing with api...DONE ${JSON.stringify(parsedValue, null, 2)}`);
|
|
157
|
-
await context.edgeCache.updateValue({
|
|
158
|
-
...parsedValue,
|
|
159
|
-
lastCheck: Date.now()
|
|
160
|
-
});
|
|
161
|
-
} catch (error) {
|
|
162
|
-
throw new APIError(`Failed to parse check endpoint result - ${error}`);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
} catch (e) {
|
|
166
|
-
const message = "Failed to fetch from check endpoint";
|
|
167
|
-
console.error(
|
|
168
|
-
printMessage(
|
|
169
|
-
e instanceof APIError ? e.message : e instanceof Error ? `${message} - ${e.message}` : message
|
|
170
|
-
)
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
// src/core/check-lock-status.ts
|
|
176
|
-
var createContext = async (config) => {
|
|
177
|
-
const requestUrl = new URL(config.request.url);
|
|
178
|
-
const keyName = APPWARDEN_CACHE_KEY;
|
|
179
|
-
const provider = "cloudflare-cache";
|
|
180
|
-
const edgeCache = store.json(
|
|
181
|
-
{
|
|
182
|
-
serviceOrigin: requestUrl.origin,
|
|
183
|
-
cache: await caches.open("appwarden:lock")
|
|
184
|
-
},
|
|
185
|
-
keyName
|
|
186
|
-
);
|
|
187
|
-
return {
|
|
188
|
-
keyName,
|
|
189
|
-
request: config.request,
|
|
190
|
-
edgeCache,
|
|
191
|
-
requestUrl,
|
|
192
|
-
provider,
|
|
193
|
-
debug: config.debug ?? false,
|
|
194
|
-
lockPageSlug: config.lockPageSlug,
|
|
195
|
-
appwardenApiToken: config.appwardenApiToken,
|
|
196
|
-
appwardenApiHostname: config.appwardenApiHostname,
|
|
197
|
-
waitUntil: config.waitUntil
|
|
198
|
-
};
|
|
199
|
-
};
|
|
200
|
-
var resolveLockStatus = async (context) => {
|
|
201
|
-
const { lockValue, shouldDeleteEdgeValue } = await getLockValue(context);
|
|
202
|
-
if (shouldDeleteEdgeValue) {
|
|
203
|
-
await deleteEdgeValue(context);
|
|
204
|
-
}
|
|
205
|
-
const isTestRoute = context.requestUrl.pathname === APPWARDEN_TEST_ROUTE;
|
|
206
|
-
const isTestLock = isTestRoute && !MemoryCache.isTestExpired(lockValue) && !!lockValue;
|
|
207
|
-
return {
|
|
208
|
-
isLocked: !!lockValue?.isLocked || isTestLock,
|
|
209
|
-
isTestLock,
|
|
210
|
-
lockValue,
|
|
211
|
-
wasDeleted: shouldDeleteEdgeValue ?? false
|
|
212
|
-
};
|
|
213
|
-
};
|
|
214
|
-
var checkLockStatus = async (config) => {
|
|
215
|
-
const context = await createContext(config);
|
|
216
|
-
let { isLocked, isTestLock, lockValue, wasDeleted } = await resolveLockStatus(context);
|
|
217
|
-
if (MemoryCache.isExpired(lockValue) || wasDeleted) {
|
|
218
|
-
if (!lockValue || wasDeleted || lockValue.isLocked) {
|
|
219
|
-
await syncEdgeValue(context);
|
|
220
|
-
({ isLocked, isTestLock } = await resolveLockStatus(context));
|
|
221
|
-
} else {
|
|
222
|
-
config.waitUntil(syncEdgeValue(context));
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
return { isLocked, isTestLock };
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
export {
|
|
229
|
-
store,
|
|
230
|
-
getLockValue,
|
|
231
|
-
checkLockStatus
|
|
232
|
-
};
|