mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-25 04:20:02 +00:00
fix: allowedUrls should not expect every possible URL. (#1913)
* fix: allowedUrls should not expect every possible URL. * OpenAPI: Introduce `allowedOrigins` and `filterRequest` options to `createProxy` --------- Co-authored-by: Fuma Nama <[email protected]>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-openapi': patch
|
||||
---
|
||||
|
||||
Introduce `allowedOrigins` and `filterRequest` options to `createProxy`. Deprecate `allowedUrls` in favour of new APIs.
|
||||
@@ -21,7 +21,10 @@ Create a route handler for proxy server.
|
||||
```ts title="/api/proxy/route.ts"
|
||||
import { openapi } from '@/lib/source';
|
||||
|
||||
export const { GET, HEAD, PUT, POST, PATCH, DELETE } = openapi.createProxy();
|
||||
export const { GET, HEAD, PUT, POST, PATCH, DELETE } = openapi.createProxy({
|
||||
// optional, we recommend to set a list of allowed origins for proxied requests
|
||||
allowedOrigins: ['https://example.com'],
|
||||
});
|
||||
```
|
||||
|
||||
> Follow the [Getting Started](/docs/ui/openapi) guide if `openapi` server is not yet configured.
|
||||
|
||||
@@ -7,10 +7,27 @@ type Proxy = {
|
||||
};
|
||||
|
||||
interface CreateProxyOptions {
|
||||
/**
|
||||
* Filter by prefixes of request url
|
||||
*
|
||||
* @deprecated Use `allowedOrigins` for filtering origins, or `filterRequest` for more detailed rules.
|
||||
*/
|
||||
allowedUrls?: string[];
|
||||
|
||||
/**
|
||||
* Override original request/response with yours
|
||||
* List of allowed origins to proxy to.
|
||||
*/
|
||||
allowedOrigins?: string[];
|
||||
|
||||
/**
|
||||
* Determine if the proxied request is allowed.
|
||||
*
|
||||
* @returns true if allowed, otherwise forbidden.
|
||||
*/
|
||||
filterRequest?: (request: Request) => boolean;
|
||||
|
||||
/**
|
||||
* Override proxied request/response with yours
|
||||
*/
|
||||
overrides?: {
|
||||
request?: (request: Request) => Request;
|
||||
@@ -19,7 +36,16 @@ interface CreateProxyOptions {
|
||||
}
|
||||
|
||||
export function createProxy(options: CreateProxyOptions = {}): Proxy {
|
||||
const { allowedUrls, overrides } = options;
|
||||
const {
|
||||
allowedOrigins,
|
||||
allowedUrls,
|
||||
filterRequest = (req) => {
|
||||
return (
|
||||
!allowedUrls || allowedUrls.some((item) => req.url.startsWith(item))
|
||||
);
|
||||
},
|
||||
overrides,
|
||||
} = options;
|
||||
const handlers: Partial<Proxy> = {};
|
||||
|
||||
async function handler(req: NextRequest): Promise<Response> {
|
||||
@@ -27,45 +53,59 @@ export function createProxy(options: CreateProxyOptions = {}): Proxy {
|
||||
|
||||
if (!url) {
|
||||
return Response.json(
|
||||
'A `url` query parameter is required for proxy url',
|
||||
'[Proxy] A `url` query parameter is required for proxy url',
|
||||
{
|
||||
status: 400,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
allowedUrls &&
|
||||
allowedUrls.every((allowedUrl) => !allowedUrl.startsWith(url))
|
||||
) {
|
||||
return Response.json('The given `url` query parameter is not allowed', {
|
||||
let parsedUrl;
|
||||
try {
|
||||
parsedUrl = new URL(url);
|
||||
} catch {
|
||||
return Response.json('[Proxy] Invalid `url` parameter value.', {
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
let clonedReq = new Request(url, {
|
||||
if (allowedOrigins && !allowedOrigins.includes(parsedUrl.origin)) {
|
||||
return Response.json(
|
||||
`[Proxy] The origin "${parsedUrl.origin}" is not allowed.`,
|
||||
{
|
||||
status: 400,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let proxied = new Request(parsedUrl, {
|
||||
...req,
|
||||
cache: 'no-cache',
|
||||
mode: 'cors',
|
||||
});
|
||||
|
||||
if (overrides?.request) {
|
||||
clonedReq = overrides.request(clonedReq);
|
||||
proxied = overrides.request(proxied);
|
||||
}
|
||||
|
||||
clonedReq.headers.forEach((_value, originalKey) => {
|
||||
const key = originalKey.toLowerCase();
|
||||
const notAllowed = key === 'origin';
|
||||
if (!filterRequest(proxied)) {
|
||||
return Response.json('[Proxy] The proxied request is not allowed', {
|
||||
status: 403,
|
||||
});
|
||||
}
|
||||
|
||||
if (notAllowed) {
|
||||
clonedReq.headers.delete(originalKey);
|
||||
proxied.headers.forEach((_value, originalKey) => {
|
||||
const key = originalKey.toLowerCase();
|
||||
|
||||
if (key === 'origin') {
|
||||
proxied.headers.delete(originalKey);
|
||||
}
|
||||
});
|
||||
|
||||
let res = await fetch(clonedReq).catch((e) => new Error(e.toString()));
|
||||
let res = await fetch(proxied).catch((e) => new Error(e.toString()));
|
||||
if (res instanceof Error) {
|
||||
return Response.json(`Failed to proxy request: ${res.message}`, {
|
||||
status: 400,
|
||||
return Response.json(`[Proxy] Failed to proxy request: ${res.message}`, {
|
||||
status: 500,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -76,17 +116,15 @@ export function createProxy(options: CreateProxyOptions = {}): Proxy {
|
||||
const headers = new Headers(res.headers);
|
||||
headers.forEach((_value, originalKey) => {
|
||||
const key = originalKey.toLowerCase();
|
||||
const notAllowed =
|
||||
key.startsWith('access-control-') || key === 'content-encoding';
|
||||
|
||||
if (notAllowed) {
|
||||
if (key.startsWith('access-control-') || key === 'content-encoding') {
|
||||
headers.delete(originalKey);
|
||||
}
|
||||
});
|
||||
headers.set('X-Forwarded-Host', res.url);
|
||||
|
||||
return new Response(res.body, {
|
||||
...res,
|
||||
status: res.status,
|
||||
statusText: res.statusText,
|
||||
headers,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user