import { readBody, sendJson } from "./server-http-utils.ts"; const serviceId = "hwlab-project-management"; export async function handleProjectManagementProxyHttp(request, response, url, options = {}) { const env = options.env ?? process.env; const baseUrl = String(env.HWLAB_PROJECT_MANAGEMENT_URL ?? "").trim(); if (!baseUrl) { sendJson(response, 503, { ok: false, error: { code: "project_management_service_unconfigured", message: "HWLAB_PROJECT_MANAGEMENT_URL is required to route project management API requests" }, valuesRedacted: true }); return; } if (!isAllowedProjectManagementMethod(request.method, url.pathname)) { sendJson(response, 405, { ok: false, error: { code: "method_not_allowed", message: "Project management API only accepts read requests plus Workbench link writes" } }); return; } const auth = await options.accessController.authenticate(request, { required: true }); if (!auth.ok) { sendJson(response, auth.status, auth); return; } let upstreamUrl; try { upstreamUrl = new URL(`${url.pathname}${url.search}`, normalizedBaseUrl(baseUrl)); } catch { sendJson(response, 503, { ok: false, error: { code: "project_management_service_url_invalid", message: "HWLAB_PROJECT_MANAGEMENT_URL is invalid" }, valuesRedacted: true }); return; } const headers = bridgeHeaders(request, auth); const timeoutMs = positiveInteger(env.HWLAB_PROJECT_MANAGEMENT_PROXY_TIMEOUT_MS, 30000); try { const body = request.method === "GET" || request.method === "HEAD" ? undefined : await readBody(request, options.bodyLimitBytes); const upstream = await fetch(upstreamUrl, { method: request.method, headers, body, signal: AbortSignal.timeout(timeoutMs) }); const text = await upstream.text(); response.writeHead(upstream.status, { "content-type": upstream.headers.get("content-type") || "application/json; charset=utf-8", "cache-control": upstream.headers.get("cache-control") || "no-store", "x-hwlab-proxy-service": serviceId }); response.end(text || "\n"); } catch (error) { sendJson(response, 502, { ok: false, error: { code: "project_management_proxy_failed", message: error?.message ?? "Project management proxy request failed" }, valuesRedacted: true }); } } function isAllowedProjectManagementMethod(method, pathname) { if (method === "GET" || method === "HEAD") return true; return method === "POST" && pathname === "/v1/project-management/workbench-links"; } function bridgeHeaders(request, auth) { const headers = new Headers(); for (const name of ["accept", "x-request-id", "traceparent", "tracestate"]) { const value = request.headers[name]; if (Array.isArray(value)) headers.set(name, value[0]); else if (value) headers.set(name, value); } headers.set("x-hwlab-bridge-service", "hwlab-cloud-api"); if (auth.actor?.id) headers.set("x-hwlab-actor-id", auth.actor.id); if (auth.actor?.role) headers.set("x-hwlab-actor-role", auth.actor.role); const authMethod = auth.authMethod ?? (auth.apiKey ? "api-key" : auth.session ? "web-session" : "unknown"); headers.set("x-hwlab-auth-method", authMethod); return headers; } function normalizedBaseUrl(value) { return value.endsWith("/") ? value : `${value}/`; } function positiveInteger(value, fallback) { const number = Number(value); return Number.isInteger(number) && number > 0 ? number : fallback; }