@vistagenic/vista 0.3.2 → 0.3.4
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/bin/vista.js +79 -109
- package/dist/ai/embeddings.d.ts +11 -11
- package/dist/ai/embeddings.js +71 -71
- package/dist/ai/index.d.ts +10 -10
- package/dist/ai/index.js +26 -26
- package/dist/ai/providers/base.js +85 -85
- package/dist/ai/react/react-server.d.ts +1 -1
- package/dist/ai/react/react-server.js +17 -17
- package/dist/ai/react/use-agent.js +138 -138
- package/dist/auth/core.d.ts +140 -140
- package/dist/auth/core.js +165 -165
- package/dist/auth/index.d.ts +20 -20
- package/dist/auth/index.js +369 -369
- package/dist/auth/react-server.d.ts +1 -1
- package/dist/auth/react-server.js +10 -10
- package/dist/auth/react.d.ts +18 -18
- package/dist/auth/react.js +71 -71
- package/dist/bin/build-rsc-flashpack.js +6 -0
- package/dist/bin/build-rsc.js +555 -492
- package/dist/bin/build.js +365 -353
- package/dist/bin/generate.js +513 -510
- package/dist/bin/webpack.config.d.ts +3 -1
- package/dist/bin/webpack.config.js +7 -4
- package/dist/build/manifest.d.ts +169 -169
- package/dist/build/manifest.js +423 -423
- package/dist/build/rsc/client-manifest.d.ts +62 -62
- package/dist/build/rsc/client-manifest.js +295 -295
- package/dist/build/rsc/compiler.d.ts +3 -1
- package/dist/build/rsc/compiler.js +12 -7
- package/dist/build/rsc/native-scanner.d.ts +135 -135
- package/dist/build/rsc/native-scanner.js +203 -203
- package/dist/build/rsc/react-client-reference-manifest.d.ts +35 -28
- package/dist/build/rsc/react-client-reference-manifest.js +366 -240
- package/dist/build/standalone.js +260 -254
- package/dist/client/dynamic.react-server.d.ts +2 -2
- package/dist/client/dynamic.react-server.js +23 -23
- package/dist/client/link.js +1 -1
- package/dist/client/link.react-server.d.ts +2 -2
- package/dist/client/link.react-server.js +11 -11
- package/dist/client/navigation.js +2 -2
- package/dist/client/navigation.react-server.d.ts +1 -1
- package/dist/client/navigation.react-server.js +17 -17
- package/dist/client/router.react-server.d.ts +1 -1
- package/dist/client/router.react-server.js +17 -17
- package/dist/client/rsc-router.d.ts +1 -3
- package/dist/client/rsc-router.js +98 -45
- package/dist/client/rsc-router.react-server.d.ts +1 -1
- package/dist/client/rsc-router.react-server.js +17 -17
- package/dist/client/script.react-server.d.ts +2 -2
- package/dist/client/script.react-server.js +23 -23
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -7
- package/dist/deploy/adapters/cloudflare.js +42 -41
- package/dist/deploy/adapters/docker.d.ts +1 -0
- package/dist/deploy/adapters/docker.js +5 -4
- package/dist/deploy/adapters/netlify.js +60 -27
- package/dist/deploy/adapters/vercel.js +80 -50
- package/dist/deploy/preflight.js +4 -3
- package/dist/deploy/runtime-pack.d.ts +11 -0
- package/dist/deploy/runtime-pack.js +300 -0
- package/dist/deploy/utils.d.ts +7 -1
- package/dist/deploy/utils.js +95 -3
- package/dist/flashpack/command.js +1 -0
- package/dist/flashpack/runtime.js +2 -1
- package/dist/image/get-img-props.js +21 -11
- package/dist/image/image-config.d.ts +2 -0
- package/dist/image/image-config.js +17 -0
- package/dist/image/index.d.ts +18 -3
- package/dist/image/index.js +1 -1
- package/dist/image/react-server.d.ts +1 -2
- package/dist/image/react-server.js +1 -1
- package/dist/index.d.ts +23 -23
- package/dist/index.js +61 -61
- package/dist/server/app-dir.d.ts +22 -0
- package/dist/server/app-dir.js +77 -0
- package/dist/server/app-router-runtime.d.ts +1 -0
- package/dist/server/app-router-runtime.js +34 -9
- package/dist/server/cookie-parse.d.ts +4 -4
- package/dist/server/cookie-parse.js +14 -14
- package/dist/server/engine.js +5 -5
- package/dist/server/hydration-chunks.d.ts +7 -0
- package/dist/server/hydration-chunks.js +49 -0
- package/dist/server/image-optimizer.js +2 -1
- package/dist/server/index.d.ts +109 -109
- package/dist/server/index.js +315 -315
- package/dist/server/middleware-runner.d.ts +125 -125
- package/dist/server/middleware-runner.js +607 -615
- package/dist/server/middleware-security.d.ts +36 -36
- package/dist/server/middleware-security.js +183 -183
- package/dist/server/module-compile-hook.js +706 -695
- package/dist/server/root-resolver.js +6 -5
- package/dist/server/rsc-engine-flashpack.d.ts +3 -1
- package/dist/server/rsc-engine-flashpack.js +12 -1
- package/dist/server/rsc-engine.d.ts +6 -1
- package/dist/server/rsc-engine.js +217 -280
- package/dist/server/rsc-upstream.js +900 -892
- package/dist/server/ssr-webpack-shim.d.ts +6 -0
- package/dist/server/ssr-webpack-shim.js +29 -0
- package/dist/server/static-generator.d.ts +2 -0
- package/dist/server/static-generator.js +33 -32
- package/dist/server/structure-validator.js +2 -1
- package/dist/server/structure-watch.js +2 -0
- package/dist/server/typed-api-runtime.js +639 -635
- package/dist/server/vista-import-map.js +134 -134
- package/dist/stack/index.d.ts +34 -34
- package/dist/stack/index.js +49 -49
- package/dist/stack/server/caller.d.ts +13 -13
- package/dist/stack/server/caller.js +42 -42
- package/dist/stack/server/executor.d.ts +40 -40
- package/dist/stack/server/executor.js +222 -222
- package/dist/stack/server/index.d.ts +11 -11
- package/dist/stack/server/index.js +24 -24
- package/dist/stack/server/procedure.d.ts +20 -20
- package/dist/stack/server/procedure.js +66 -66
- package/dist/stack/server/types.d.ts +113 -113
- package/dist/theme/react-server.d.ts +10 -10
- package/dist/theme/react-server.js +16 -16
- package/package.json +1 -1
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Middleware security primitives.
|
|
3
|
-
*
|
|
4
|
-
* These are Next-inspired helpers that apps can compose in `middleware.ts`.
|
|
5
|
-
* The runner also uses the redirect/header sanitizers internally.
|
|
6
|
-
*/
|
|
7
|
-
export declare const FORBIDDEN_REQUEST_HEADERS: Set<string>;
|
|
8
|
-
export declare function isForbiddenRequestHeader(name: string): boolean;
|
|
9
|
-
export declare function sanitizeRequestHeaderMap(headers: Map<string, string> | Headers | Record<string, string>): Map<string, string>;
|
|
10
|
-
export declare function isSafeRedirectLocation(location: string, requestUrl: string, allowedHosts?: string[]): boolean;
|
|
11
|
-
export declare function isSafeRewriteLocation(location: string): boolean;
|
|
12
|
-
export declare function securityHeaders(init?: ResponseInit): Headers;
|
|
13
|
-
export declare function cors(options?: {
|
|
14
|
-
origin?: string | string[] | '*';
|
|
15
|
-
methods?: string[];
|
|
16
|
-
headers?: string[];
|
|
17
|
-
credentials?: boolean;
|
|
18
|
-
maxAge?: number;
|
|
19
|
-
}): (request: Request) => Headers;
|
|
20
|
-
export declare function rateLimit(options?: {
|
|
21
|
-
key?: string;
|
|
22
|
-
limit?: number;
|
|
23
|
-
windowMs?: number;
|
|
24
|
-
identifier?: (request: {
|
|
25
|
-
headers: Headers;
|
|
26
|
-
ip?: string;
|
|
27
|
-
}) => string;
|
|
28
|
-
}): (request: {
|
|
29
|
-
headers: Headers;
|
|
30
|
-
ip?: string;
|
|
31
|
-
}) => {
|
|
32
|
-
ok: boolean;
|
|
33
|
-
remaining: number;
|
|
34
|
-
};
|
|
35
|
-
export type MiddlewareLike = (context: any) => Promise<Response | void> | Response | void;
|
|
36
|
-
export declare function chain(middlewares: MiddlewareLike[]): MiddlewareLike;
|
|
1
|
+
/**
|
|
2
|
+
* Middleware security primitives.
|
|
3
|
+
*
|
|
4
|
+
* These are Next-inspired helpers that apps can compose in `middleware.ts`.
|
|
5
|
+
* The runner also uses the redirect/header sanitizers internally.
|
|
6
|
+
*/
|
|
7
|
+
export declare const FORBIDDEN_REQUEST_HEADERS: Set<string>;
|
|
8
|
+
export declare function isForbiddenRequestHeader(name: string): boolean;
|
|
9
|
+
export declare function sanitizeRequestHeaderMap(headers: Map<string, string> | Headers | Record<string, string>): Map<string, string>;
|
|
10
|
+
export declare function isSafeRedirectLocation(location: string, requestUrl: string, allowedHosts?: string[]): boolean;
|
|
11
|
+
export declare function isSafeRewriteLocation(location: string): boolean;
|
|
12
|
+
export declare function securityHeaders(init?: ResponseInit): Headers;
|
|
13
|
+
export declare function cors(options?: {
|
|
14
|
+
origin?: string | string[] | '*';
|
|
15
|
+
methods?: string[];
|
|
16
|
+
headers?: string[];
|
|
17
|
+
credentials?: boolean;
|
|
18
|
+
maxAge?: number;
|
|
19
|
+
}): (request: Request) => Headers;
|
|
20
|
+
export declare function rateLimit(options?: {
|
|
21
|
+
key?: string;
|
|
22
|
+
limit?: number;
|
|
23
|
+
windowMs?: number;
|
|
24
|
+
identifier?: (request: {
|
|
25
|
+
headers: Headers;
|
|
26
|
+
ip?: string;
|
|
27
|
+
}) => string;
|
|
28
|
+
}): (request: {
|
|
29
|
+
headers: Headers;
|
|
30
|
+
ip?: string;
|
|
31
|
+
}) => {
|
|
32
|
+
ok: boolean;
|
|
33
|
+
remaining: number;
|
|
34
|
+
};
|
|
35
|
+
export type MiddlewareLike = (context: any) => Promise<Response | void> | Response | void;
|
|
36
|
+
export declare function chain(middlewares: MiddlewareLike[]): MiddlewareLike;
|
|
@@ -1,183 +1,183 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Middleware security primitives.
|
|
4
|
-
*
|
|
5
|
-
* These are Next-inspired helpers that apps can compose in `middleware.ts`.
|
|
6
|
-
* The runner also uses the redirect/header sanitizers internally.
|
|
7
|
-
*/
|
|
8
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.FORBIDDEN_REQUEST_HEADERS = void 0;
|
|
10
|
-
exports.isForbiddenRequestHeader = isForbiddenRequestHeader;
|
|
11
|
-
exports.sanitizeRequestHeaderMap = sanitizeRequestHeaderMap;
|
|
12
|
-
exports.isSafeRedirectLocation = isSafeRedirectLocation;
|
|
13
|
-
exports.isSafeRewriteLocation = isSafeRewriteLocation;
|
|
14
|
-
exports.securityHeaders = securityHeaders;
|
|
15
|
-
exports.cors = cors;
|
|
16
|
-
exports.rateLimit = rateLimit;
|
|
17
|
-
exports.chain = chain;
|
|
18
|
-
exports.FORBIDDEN_REQUEST_HEADERS = new Set([
|
|
19
|
-
'host',
|
|
20
|
-
'cookie',
|
|
21
|
-
'content-length',
|
|
22
|
-
'content-type',
|
|
23
|
-
'transfer-encoding',
|
|
24
|
-
'x-forwarded-for',
|
|
25
|
-
'x-forwarded-host',
|
|
26
|
-
'x-forwarded-proto',
|
|
27
|
-
'x-forwarded-port',
|
|
28
|
-
'x-real-ip',
|
|
29
|
-
'x-middleware-next',
|
|
30
|
-
'x-middleware-rewrite',
|
|
31
|
-
]);
|
|
32
|
-
function isForbiddenRequestHeader(name) {
|
|
33
|
-
return exports.FORBIDDEN_REQUEST_HEADERS.has(name.toLowerCase());
|
|
34
|
-
}
|
|
35
|
-
function sanitizeRequestHeaderMap(headers) {
|
|
36
|
-
const sanitized = new Map();
|
|
37
|
-
const append = (key, value) => {
|
|
38
|
-
if (!isForbiddenRequestHeader(key)) {
|
|
39
|
-
sanitized.set(key.toLowerCase(), value);
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
if (headers instanceof Map) {
|
|
43
|
-
headers.forEach((value, key) => append(key, value));
|
|
44
|
-
}
|
|
45
|
-
else if (typeof headers.forEach === 'function' && !Array.isArray(headers)) {
|
|
46
|
-
headers.forEach((value, key) => append(key, value));
|
|
47
|
-
}
|
|
48
|
-
else {
|
|
49
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
50
|
-
append(key, value);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return sanitized;
|
|
54
|
-
}
|
|
55
|
-
function isSafeRedirectLocation(location, requestUrl, allowedHosts = []) {
|
|
56
|
-
const trimmed = String(location || '').trim();
|
|
57
|
-
if (!trimmed)
|
|
58
|
-
return false;
|
|
59
|
-
if (trimmed.startsWith('/') && !trimmed.startsWith('//') && !trimmed.startsWith('/\\')) {
|
|
60
|
-
return !trimmed.includes('://');
|
|
61
|
-
}
|
|
62
|
-
try {
|
|
63
|
-
const base = new URL(requestUrl);
|
|
64
|
-
const target = new URL(trimmed, base);
|
|
65
|
-
if (target.origin === base.origin) {
|
|
66
|
-
return true;
|
|
67
|
-
}
|
|
68
|
-
const allowed = new Set(allowedHosts.map((host) => host.toLowerCase()));
|
|
69
|
-
return allowed.has(target.host.toLowerCase());
|
|
70
|
-
}
|
|
71
|
-
catch {
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
function isSafeRewriteLocation(location) {
|
|
76
|
-
const trimmed = String(location || '').trim();
|
|
77
|
-
if (!trimmed)
|
|
78
|
-
return false;
|
|
79
|
-
if (trimmed.startsWith('/') && !trimmed.startsWith('//') && !trimmed.startsWith('/\\')) {
|
|
80
|
-
return !trimmed.includes('://');
|
|
81
|
-
}
|
|
82
|
-
return false;
|
|
83
|
-
}
|
|
84
|
-
function securityHeaders(init = {}) {
|
|
85
|
-
const headers = new Headers(init.headers);
|
|
86
|
-
if (!headers.has('X-Content-Type-Options'))
|
|
87
|
-
headers.set('X-Content-Type-Options', 'nosniff');
|
|
88
|
-
if (!headers.has('X-Frame-Options'))
|
|
89
|
-
headers.set('X-Frame-Options', 'DENY');
|
|
90
|
-
if (!headers.has('Referrer-Policy'))
|
|
91
|
-
headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
|
|
92
|
-
if (!headers.has('X-DNS-Prefetch-Control'))
|
|
93
|
-
headers.set('X-DNS-Prefetch-Control', 'off');
|
|
94
|
-
if (!headers.has('Permissions-Policy')) {
|
|
95
|
-
headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
|
|
96
|
-
}
|
|
97
|
-
if (process.env.NODE_ENV === 'production' && !headers.has('Strict-Transport-Security')) {
|
|
98
|
-
headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
|
99
|
-
}
|
|
100
|
-
return headers;
|
|
101
|
-
}
|
|
102
|
-
function cors(options = {}) {
|
|
103
|
-
const origin = options.origin ?? '*';
|
|
104
|
-
const methods = (options.methods ?? ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']).join(', ');
|
|
105
|
-
const allowHeaders = (options.headers ?? ['Content-Type', 'Authorization']).join(', ');
|
|
106
|
-
return (request) => {
|
|
107
|
-
const headers = new Headers();
|
|
108
|
-
const requestOrigin = request.headers.get('origin');
|
|
109
|
-
if (origin === '*') {
|
|
110
|
-
headers.set('Access-Control-Allow-Origin', '*');
|
|
111
|
-
}
|
|
112
|
-
else if (Array.isArray(origin)) {
|
|
113
|
-
if (requestOrigin && origin.includes(requestOrigin)) {
|
|
114
|
-
headers.set('Access-Control-Allow-Origin', requestOrigin);
|
|
115
|
-
headers.set('Vary', 'Origin');
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
else if (origin) {
|
|
119
|
-
headers.set('Access-Control-Allow-Origin', origin);
|
|
120
|
-
}
|
|
121
|
-
headers.set('Access-Control-Allow-Methods', methods);
|
|
122
|
-
headers.set('Access-Control-Allow-Headers', allowHeaders);
|
|
123
|
-
if (options.credentials) {
|
|
124
|
-
headers.set('Access-Control-Allow-Credentials', 'true');
|
|
125
|
-
}
|
|
126
|
-
if (options.maxAge != null) {
|
|
127
|
-
headers.set('Access-Control-Max-Age', String(options.maxAge));
|
|
128
|
-
}
|
|
129
|
-
return headers;
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
const rateLimitStores = new Map();
|
|
133
|
-
function rateLimit(options = {}) {
|
|
134
|
-
const limit = options.limit ?? 60;
|
|
135
|
-
const windowMs = options.windowMs ?? 60_000;
|
|
136
|
-
const storeKey = options.key ?? 'default';
|
|
137
|
-
if (!rateLimitStores.has(storeKey)) {
|
|
138
|
-
rateLimitStores.set(storeKey, new Map());
|
|
139
|
-
}
|
|
140
|
-
const store = rateLimitStores.get(storeKey);
|
|
141
|
-
return (request) => {
|
|
142
|
-
const identifier = options.identifier?.(request) ||
|
|
143
|
-
request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ||
|
|
144
|
-
request.ip ||
|
|
145
|
-
'anonymous';
|
|
146
|
-
const now = Date.now();
|
|
147
|
-
const bucket = store.get(identifier) || { tokens: limit, updatedAt: now };
|
|
148
|
-
const elapsed = now - bucket.updatedAt;
|
|
149
|
-
const refill = (elapsed / windowMs) * limit;
|
|
150
|
-
bucket.tokens = Math.min(limit, bucket.tokens + refill);
|
|
151
|
-
bucket.updatedAt = now;
|
|
152
|
-
if (bucket.tokens < 1) {
|
|
153
|
-
store.set(identifier, bucket);
|
|
154
|
-
return { ok: false, remaining: 0 };
|
|
155
|
-
}
|
|
156
|
-
bucket.tokens -= 1;
|
|
157
|
-
store.set(identifier, bucket);
|
|
158
|
-
return { ok: true, remaining: Math.floor(bucket.tokens) };
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
function chain(middlewares) {
|
|
162
|
-
return async (context) => {
|
|
163
|
-
const originalNext = context.next;
|
|
164
|
-
let index = -1;
|
|
165
|
-
const dispatch = async (current) => {
|
|
166
|
-
if (current <= index) {
|
|
167
|
-
throw new Error('next() called multiple times');
|
|
168
|
-
}
|
|
169
|
-
index = current;
|
|
170
|
-
const fn = middlewares[current];
|
|
171
|
-
if (!fn) {
|
|
172
|
-
return originalNext();
|
|
173
|
-
}
|
|
174
|
-
const next = async () => dispatch(current + 1);
|
|
175
|
-
const result = await fn({ ...context, next });
|
|
176
|
-
if (result instanceof Response) {
|
|
177
|
-
return result;
|
|
178
|
-
}
|
|
179
|
-
return next();
|
|
180
|
-
};
|
|
181
|
-
return dispatch(0);
|
|
182
|
-
};
|
|
183
|
-
}
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Middleware security primitives.
|
|
4
|
+
*
|
|
5
|
+
* These are Next-inspired helpers that apps can compose in `middleware.ts`.
|
|
6
|
+
* The runner also uses the redirect/header sanitizers internally.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.FORBIDDEN_REQUEST_HEADERS = void 0;
|
|
10
|
+
exports.isForbiddenRequestHeader = isForbiddenRequestHeader;
|
|
11
|
+
exports.sanitizeRequestHeaderMap = sanitizeRequestHeaderMap;
|
|
12
|
+
exports.isSafeRedirectLocation = isSafeRedirectLocation;
|
|
13
|
+
exports.isSafeRewriteLocation = isSafeRewriteLocation;
|
|
14
|
+
exports.securityHeaders = securityHeaders;
|
|
15
|
+
exports.cors = cors;
|
|
16
|
+
exports.rateLimit = rateLimit;
|
|
17
|
+
exports.chain = chain;
|
|
18
|
+
exports.FORBIDDEN_REQUEST_HEADERS = new Set([
|
|
19
|
+
'host',
|
|
20
|
+
'cookie',
|
|
21
|
+
'content-length',
|
|
22
|
+
'content-type',
|
|
23
|
+
'transfer-encoding',
|
|
24
|
+
'x-forwarded-for',
|
|
25
|
+
'x-forwarded-host',
|
|
26
|
+
'x-forwarded-proto',
|
|
27
|
+
'x-forwarded-port',
|
|
28
|
+
'x-real-ip',
|
|
29
|
+
'x-middleware-next',
|
|
30
|
+
'x-middleware-rewrite',
|
|
31
|
+
]);
|
|
32
|
+
function isForbiddenRequestHeader(name) {
|
|
33
|
+
return exports.FORBIDDEN_REQUEST_HEADERS.has(name.toLowerCase());
|
|
34
|
+
}
|
|
35
|
+
function sanitizeRequestHeaderMap(headers) {
|
|
36
|
+
const sanitized = new Map();
|
|
37
|
+
const append = (key, value) => {
|
|
38
|
+
if (!isForbiddenRequestHeader(key)) {
|
|
39
|
+
sanitized.set(key.toLowerCase(), value);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
if (headers instanceof Map) {
|
|
43
|
+
headers.forEach((value, key) => append(key, value));
|
|
44
|
+
}
|
|
45
|
+
else if (typeof headers.forEach === 'function' && !Array.isArray(headers)) {
|
|
46
|
+
headers.forEach((value, key) => append(key, value));
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
50
|
+
append(key, value);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return sanitized;
|
|
54
|
+
}
|
|
55
|
+
function isSafeRedirectLocation(location, requestUrl, allowedHosts = []) {
|
|
56
|
+
const trimmed = String(location || '').trim();
|
|
57
|
+
if (!trimmed)
|
|
58
|
+
return false;
|
|
59
|
+
if (trimmed.startsWith('/') && !trimmed.startsWith('//') && !trimmed.startsWith('/\\')) {
|
|
60
|
+
return !trimmed.includes('://');
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const base = new URL(requestUrl);
|
|
64
|
+
const target = new URL(trimmed, base);
|
|
65
|
+
if (target.origin === base.origin) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
const allowed = new Set(allowedHosts.map((host) => host.toLowerCase()));
|
|
69
|
+
return allowed.has(target.host.toLowerCase());
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function isSafeRewriteLocation(location) {
|
|
76
|
+
const trimmed = String(location || '').trim();
|
|
77
|
+
if (!trimmed)
|
|
78
|
+
return false;
|
|
79
|
+
if (trimmed.startsWith('/') && !trimmed.startsWith('//') && !trimmed.startsWith('/\\')) {
|
|
80
|
+
return !trimmed.includes('://');
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
function securityHeaders(init = {}) {
|
|
85
|
+
const headers = new Headers(init.headers);
|
|
86
|
+
if (!headers.has('X-Content-Type-Options'))
|
|
87
|
+
headers.set('X-Content-Type-Options', 'nosniff');
|
|
88
|
+
if (!headers.has('X-Frame-Options'))
|
|
89
|
+
headers.set('X-Frame-Options', 'DENY');
|
|
90
|
+
if (!headers.has('Referrer-Policy'))
|
|
91
|
+
headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
|
|
92
|
+
if (!headers.has('X-DNS-Prefetch-Control'))
|
|
93
|
+
headers.set('X-DNS-Prefetch-Control', 'off');
|
|
94
|
+
if (!headers.has('Permissions-Policy')) {
|
|
95
|
+
headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
|
|
96
|
+
}
|
|
97
|
+
if (process.env.NODE_ENV === 'production' && !headers.has('Strict-Transport-Security')) {
|
|
98
|
+
headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
|
99
|
+
}
|
|
100
|
+
return headers;
|
|
101
|
+
}
|
|
102
|
+
function cors(options = {}) {
|
|
103
|
+
const origin = options.origin ?? '*';
|
|
104
|
+
const methods = (options.methods ?? ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']).join(', ');
|
|
105
|
+
const allowHeaders = (options.headers ?? ['Content-Type', 'Authorization']).join(', ');
|
|
106
|
+
return (request) => {
|
|
107
|
+
const headers = new Headers();
|
|
108
|
+
const requestOrigin = request.headers.get('origin');
|
|
109
|
+
if (origin === '*') {
|
|
110
|
+
headers.set('Access-Control-Allow-Origin', '*');
|
|
111
|
+
}
|
|
112
|
+
else if (Array.isArray(origin)) {
|
|
113
|
+
if (requestOrigin && origin.includes(requestOrigin)) {
|
|
114
|
+
headers.set('Access-Control-Allow-Origin', requestOrigin);
|
|
115
|
+
headers.set('Vary', 'Origin');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else if (origin) {
|
|
119
|
+
headers.set('Access-Control-Allow-Origin', origin);
|
|
120
|
+
}
|
|
121
|
+
headers.set('Access-Control-Allow-Methods', methods);
|
|
122
|
+
headers.set('Access-Control-Allow-Headers', allowHeaders);
|
|
123
|
+
if (options.credentials) {
|
|
124
|
+
headers.set('Access-Control-Allow-Credentials', 'true');
|
|
125
|
+
}
|
|
126
|
+
if (options.maxAge != null) {
|
|
127
|
+
headers.set('Access-Control-Max-Age', String(options.maxAge));
|
|
128
|
+
}
|
|
129
|
+
return headers;
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const rateLimitStores = new Map();
|
|
133
|
+
function rateLimit(options = {}) {
|
|
134
|
+
const limit = options.limit ?? 60;
|
|
135
|
+
const windowMs = options.windowMs ?? 60_000;
|
|
136
|
+
const storeKey = options.key ?? 'default';
|
|
137
|
+
if (!rateLimitStores.has(storeKey)) {
|
|
138
|
+
rateLimitStores.set(storeKey, new Map());
|
|
139
|
+
}
|
|
140
|
+
const store = rateLimitStores.get(storeKey);
|
|
141
|
+
return (request) => {
|
|
142
|
+
const identifier = options.identifier?.(request) ||
|
|
143
|
+
request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ||
|
|
144
|
+
request.ip ||
|
|
145
|
+
'anonymous';
|
|
146
|
+
const now = Date.now();
|
|
147
|
+
const bucket = store.get(identifier) || { tokens: limit, updatedAt: now };
|
|
148
|
+
const elapsed = now - bucket.updatedAt;
|
|
149
|
+
const refill = (elapsed / windowMs) * limit;
|
|
150
|
+
bucket.tokens = Math.min(limit, bucket.tokens + refill);
|
|
151
|
+
bucket.updatedAt = now;
|
|
152
|
+
if (bucket.tokens < 1) {
|
|
153
|
+
store.set(identifier, bucket);
|
|
154
|
+
return { ok: false, remaining: 0 };
|
|
155
|
+
}
|
|
156
|
+
bucket.tokens -= 1;
|
|
157
|
+
store.set(identifier, bucket);
|
|
158
|
+
return { ok: true, remaining: Math.floor(bucket.tokens) };
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function chain(middlewares) {
|
|
162
|
+
return async (context) => {
|
|
163
|
+
const originalNext = context.next;
|
|
164
|
+
let index = -1;
|
|
165
|
+
const dispatch = async (current) => {
|
|
166
|
+
if (current <= index) {
|
|
167
|
+
throw new Error('next() called multiple times');
|
|
168
|
+
}
|
|
169
|
+
index = current;
|
|
170
|
+
const fn = middlewares[current];
|
|
171
|
+
if (!fn) {
|
|
172
|
+
return originalNext();
|
|
173
|
+
}
|
|
174
|
+
const next = async () => dispatch(current + 1);
|
|
175
|
+
const result = await fn({ ...context, next });
|
|
176
|
+
if (result instanceof Response) {
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
return next();
|
|
180
|
+
};
|
|
181
|
+
return dispatch(0);
|
|
182
|
+
};
|
|
183
|
+
}
|