feat: implement API handler for edge runtime with method validation and target normalization
This commit is contained in:
@@ -5,7 +5,6 @@ node_modules
|
|||||||
out
|
out
|
||||||
dist
|
dist
|
||||||
*.tgz
|
*.tgz
|
||||||
api
|
|
||||||
|
|
||||||
# code coverage
|
# code coverage
|
||||||
coverage
|
coverage
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import {
|
||||||
|
buildRelayRequest,
|
||||||
|
createRelayResponse,
|
||||||
|
normalizeTargetUrl,
|
||||||
|
stripRelayHeaders,
|
||||||
|
isAllowedTarget,
|
||||||
|
} from "../relay-utils";
|
||||||
|
|
||||||
|
export const config = { runtime: "edge" };
|
||||||
|
|
||||||
|
const ALLOWED_METHODS = new Set(["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]);
|
||||||
|
|
||||||
|
export default async function handler(req: Request): Promise<Response> {
|
||||||
|
if (!ALLOWED_METHODS.has(req.method)) {
|
||||||
|
return new Response(JSON.stringify({ error: "Method not allowed" }), {
|
||||||
|
status: 405,
|
||||||
|
headers: { "content-type": "application/json" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = req.headers.get("x-relay-target");
|
||||||
|
const relayPath = req.headers.get("x-relay-path") || "/";
|
||||||
|
|
||||||
|
const targetUrl = normalizeTargetUrl(target, relayPath);
|
||||||
|
if (!targetUrl) {
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({ error: "Missing x-relay-target header" }),
|
||||||
|
{
|
||||||
|
status: 400,
|
||||||
|
headers: { "content-type": "application/json" },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isAllowedTarget(targetUrl)) {
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({ error: "Target domain not allowed" }),
|
||||||
|
{
|
||||||
|
status: 403,
|
||||||
|
headers: { "content-type": "application/json" },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const headers = stripRelayHeaders(new Headers(req.headers));
|
||||||
|
const fetchOptions = buildRelayRequest(req, headers);
|
||||||
|
|
||||||
|
const response = await fetch(targetUrl, fetchOptions);
|
||||||
|
return createRelayResponse(response);
|
||||||
|
}
|
||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "bun build src/index.ts --target=bun --outdir=api && mkdir -p dist && cp public/index.html dist/index.html",
|
"build": "mkdir -p dist && cp public/index.html dist/index.html",
|
||||||
"start": "bun run src/index.ts",
|
"start": "bun run src/index.ts",
|
||||||
"dev": "bun --hot run src/index.ts",
|
"dev": "bun --hot run src/index.ts",
|
||||||
"test": "bun test",
|
"test": "bun test",
|
||||||
|
|||||||
+8
-1
@@ -1,5 +1,12 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://openapi.vercel.sh/vercel.json",
|
"$schema": "https://openapi.vercel.sh/vercel.json",
|
||||||
"bunVersion": "1",
|
"bunVersion": "1",
|
||||||
"installCommand": "bun install"
|
"installCommand": "bun install",
|
||||||
|
"outputDirectory": "dist",
|
||||||
|
"rewrites": [
|
||||||
|
{
|
||||||
|
"source": "/api/(.*)",
|
||||||
|
"destination": "/api/index.ts"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user