allow local dev origins on api
This commit is contained in:
parent
4446b228d7
commit
7e095b51f6
5 changed files with 213 additions and 6 deletions
107
services/api/src/cors.ts
Normal file
107
services/api/src/cors.ts
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
export const DEFAULT_API_CORS_ORIGINS = [
|
||||
"https://flow.deltaisland.io",
|
||||
"http://127.0.0.1:3000",
|
||||
"http://localhost:3000",
|
||||
"http://127.0.0.1:3100",
|
||||
"http://localhost:3100"
|
||||
].join(",");
|
||||
|
||||
const DEFAULT_ALLOWED_HEADERS = "authorization,content-type,x-synthetic-admin-token";
|
||||
const DEFAULT_ALLOWED_METHODS = "GET,POST,PUT,OPTIONS";
|
||||
|
||||
const normalizeOrigin = (origin: string): string | null => {
|
||||
const trimmed = origin.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
if (trimmed === "*") {
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
try {
|
||||
return new URL(trimmed).origin;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const parseCorsAllowedOrigins = (value: string): Set<string> => {
|
||||
const origins = new Set<string>();
|
||||
for (const entry of value.split(",")) {
|
||||
const origin = normalizeOrigin(entry);
|
||||
if (origin) {
|
||||
origins.add(origin);
|
||||
}
|
||||
}
|
||||
return origins;
|
||||
};
|
||||
|
||||
export const resolveCorsOrigin = (req: Request, allowedOrigins: Set<string>): string | null => {
|
||||
const origin = normalizeOrigin(req.headers.get("origin") ?? "");
|
||||
if (!origin) {
|
||||
return null;
|
||||
}
|
||||
if (allowedOrigins.has("*")) {
|
||||
return "*";
|
||||
}
|
||||
return allowedOrigins.has(origin) ? origin : null;
|
||||
};
|
||||
|
||||
const appendVaryOrigin = (headers: Headers): void => {
|
||||
const vary = headers.get("vary");
|
||||
if (!vary) {
|
||||
headers.set("vary", "Origin");
|
||||
return;
|
||||
}
|
||||
if (!vary.split(",").some((value) => value.trim().toLowerCase() === "origin")) {
|
||||
headers.set("vary", `${vary}, Origin`);
|
||||
}
|
||||
};
|
||||
|
||||
export const withCorsHeaders = (
|
||||
req: Request,
|
||||
response: Response,
|
||||
allowedOrigins: Set<string>
|
||||
): Response => {
|
||||
if (response.status === 101) {
|
||||
return response;
|
||||
}
|
||||
|
||||
const allowedOrigin = resolveCorsOrigin(req, allowedOrigins);
|
||||
if (!allowedOrigin) {
|
||||
return response;
|
||||
}
|
||||
|
||||
const headers = new Headers(response.headers);
|
||||
headers.set("access-control-allow-origin", allowedOrigin);
|
||||
appendVaryOrigin(headers);
|
||||
|
||||
return new Response(response.body, {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers
|
||||
});
|
||||
};
|
||||
|
||||
export const createCorsPreflightResponse = (
|
||||
req: Request,
|
||||
allowedOrigins: Set<string>
|
||||
): Response => {
|
||||
const headers = new Headers();
|
||||
const allowedOrigin = resolveCorsOrigin(req, allowedOrigins);
|
||||
if (allowedOrigin) {
|
||||
headers.set("access-control-allow-origin", allowedOrigin);
|
||||
headers.set("access-control-allow-methods", DEFAULT_ALLOWED_METHODS);
|
||||
headers.set(
|
||||
"access-control-allow-headers",
|
||||
req.headers.get("access-control-request-headers") ?? DEFAULT_ALLOWED_HEADERS
|
||||
);
|
||||
headers.set("access-control-max-age", "86400");
|
||||
appendVaryOrigin(headers);
|
||||
}
|
||||
|
||||
return new Response(null, {
|
||||
status: 204,
|
||||
headers
|
||||
});
|
||||
};
|
||||
|
|
@ -138,6 +138,12 @@ import {
|
|||
recordSyntheticProfileHit,
|
||||
resolveSyntheticBackendMode
|
||||
} from "./synthetic-control";
|
||||
import {
|
||||
DEFAULT_API_CORS_ORIGINS,
|
||||
createCorsPreflightResponse,
|
||||
parseCorsAllowedOrigins,
|
||||
withCorsHeaders
|
||||
} from "./cors";
|
||||
|
||||
const service = "api";
|
||||
const logger = createLogger({ service });
|
||||
|
|
@ -172,10 +178,12 @@ const envSchema = z.object({
|
|||
return value;
|
||||
}, z.boolean())
|
||||
.default(false),
|
||||
SYNTHETIC_ADMIN_TOKEN: z.string().default("")
|
||||
SYNTHETIC_ADMIN_TOKEN: z.string().default(""),
|
||||
API_CORS_ORIGINS: z.string().default(DEFAULT_API_CORS_ORIGINS)
|
||||
});
|
||||
|
||||
const env = readEnv(envSchema);
|
||||
const corsAllowedOrigins = parseCorsAllowedOrigins(env.API_CORS_ORIGINS);
|
||||
|
||||
const state = {
|
||||
shuttingDown: false,
|
||||
|
|
@ -1363,11 +1371,16 @@ const run = async () => {
|
|||
hostname: env.API_HOST,
|
||||
port: env.API_PORT,
|
||||
fetch: async (req: Request, serverRef: any) => {
|
||||
const url = new URL(req.url);
|
||||
const handleApiRequest = async (): Promise<Response> => {
|
||||
const url = new URL(req.url);
|
||||
|
||||
if (req.method === "GET" && url.pathname === "/health") {
|
||||
return jsonResponse({ status: "ok" });
|
||||
}
|
||||
if (req.method === "OPTIONS") {
|
||||
return createCorsPreflightResponse(req, corsAllowedOrigins);
|
||||
}
|
||||
|
||||
if (req.method === "GET" && url.pathname === "/health") {
|
||||
return jsonResponse({ status: "ok" });
|
||||
}
|
||||
|
||||
if (req.method === "GET" && url.pathname === "/admin/synthetic/status") {
|
||||
const authError = authenticateSyntheticAdminRequest(req);
|
||||
|
|
@ -1951,7 +1964,11 @@ const run = async () => {
|
|||
return jsonResponse({ error: "websocket upgrade failed" }, 400);
|
||||
}
|
||||
|
||||
return jsonResponse({ error: "not found" }, 404);
|
||||
return jsonResponse({ error: "not found" }, 404);
|
||||
};
|
||||
|
||||
const response = await handleApiRequest();
|
||||
return withCorsHeaders(req, response, corsAllowedOrigins);
|
||||
},
|
||||
websocket: {
|
||||
open: (socket: any) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue