fix(dev): share auth across dev frontend proxy

This commit is contained in:
Codex
2026-05-19 04:41:28 +00:00
parent dfb344ef0c
commit 0c833a7be2
9 changed files with 184 additions and 22 deletions
@@ -16,6 +16,7 @@ const providerHttpTunnelMaxAttempts = 3;
const microserviceForwardRequestHeaders = [
"accept",
"content-type",
"cookie",
"range",
"x-auth",
"x-requested-with",
@@ -28,6 +29,16 @@ const microserviceForwardRequestHeaders = [
"upload-metadata",
"upload-offset",
] as const;
const microserviceForwardResponseHeaders = [
"cache-control",
"content-disposition",
"etag",
"expires",
"last-modified",
"location",
"set-cookie",
"www-authenticate",
] as const;
// ---------------------------------------------------------------------------
// Internal helpers
@@ -140,6 +151,13 @@ function headersFromMicroserviceRequest(requestHeaders: Record<string, JsonValue
return headers;
}
function copyForwardedResponseHeaders(from: Headers, to: Headers): void {
for (const name of microserviceForwardResponseHeaders) {
const value = from.get(name);
if (value !== null && value.length > 0) to.set(name, value);
}
}
function boundedMicroserviceBodyText(
bodyText: string,
contentType: string,
@@ -622,15 +640,16 @@ async function directMicroserviceResponse(
});
const upstreamContentType = upstream.headers.get("content-type") ?? "text/plain; charset=utf-8";
if (upstreamContentType.toLowerCase().includes("text/event-stream")) {
const responseHeaders: Record<string, string> = {
const responseHeaders = new Headers({
"content-type": upstreamContentType,
"cache-control": upstream.headers.get("cache-control") || "no-store, no-transform",
"connection": "keep-alive",
"x-unidesk-proxy-mode": "direct",
"x-unidesk-response-truncated": "false",
};
});
const buffering = upstream.headers.get("x-accel-buffering");
if (buffering !== null) responseHeaders["x-accel-buffering"] = buffering;
if (buffering !== null) responseHeaders.set("x-accel-buffering", buffering);
copyForwardedResponseHeaders(upstream.headers, responseHeaders);
return new Response(upstream.body, { status: upstream.status, headers: responseHeaders });
}
const rawBodyText = await upstream.text();
@@ -641,13 +660,15 @@ async function directMicroserviceResponse(
status: upstream.status,
upstreamBodyBytes: rawBodyText.length,
});
const responseHeaders = new Headers({
"content-type": upstreamContentType,
"x-unidesk-proxy-mode": "direct",
"x-unidesk-response-truncated": bounded.truncated ? "true" : "false",
});
copyForwardedResponseHeaders(upstream.headers, responseHeaders);
return new Response(bounded.bodyText, {
status: upstream.status,
headers: {
"content-type": upstreamContentType,
"x-unidesk-proxy-mode": "direct",
"x-unidesk-response-truncated": bounded.truncated ? "true" : "false",
},
headers: responseHeaders,
});
} catch (error) {
return jsonResponse({ ok: false, error: "direct microservice proxy failed", serviceId: service.id, detail: errorToJson(error) }, 502);
@@ -35,6 +35,16 @@ const FORWARD_REQUEST_HEADERS: &[&str] = &[
"upload-metadata",
"upload-offset",
];
const FORWARD_RESPONSE_HEADERS: &[&str] = &[
"cache-control",
"content-disposition",
"etag",
"expires",
"last-modified",
"location",
"set-cookie",
"www-authenticate",
];
fn service_by_id(state: &Arc<AppState>, service_id: &str) -> Option<MicroserviceConfig> {
state
@@ -531,6 +541,44 @@ fn headers_from_microservice_request(request_headers: &Value) -> reqwest::header
headers
}
fn forwarded_response_headers_from_reqwest(
headers: &reqwest::header::HeaderMap,
) -> serde_json::Map<String, Value> {
let mut result = serde_json::Map::new();
for name in FORWARD_RESPONSE_HEADERS {
if let Some(value) = headers
.get(*name)
.and_then(|value| value.to_str().ok())
.filter(|value| !value.is_empty())
{
result.insert((*name).to_string(), Value::String(truncate_text(value, 8192)));
}
}
result
}
fn copy_forwarded_response_headers_from_json(response: &mut Response, result: &Value) {
let Some(headers) = result.get("responseHeaders").and_then(Value::as_object) else {
return;
};
for name in FORWARD_RESPONSE_HEADERS {
if let Some(value) = headers.get(*name).and_then(Value::as_str) {
add_header(response, name, value);
}
}
}
fn copy_forwarded_response_headers_from_reqwest(
response: &mut Response,
headers: &reqwest::header::HeaderMap,
) {
for name in FORWARD_RESPONSE_HEADERS {
if let Some(value) = headers.get(*name).and_then(|value| value.to_str().ok()) {
add_header(response, name, value);
}
}
}
fn content_type_is_json(content_type: &str) -> bool {
content_type.to_ascii_lowercase().contains("json")
}
@@ -644,6 +692,7 @@ fn response_from_provider_microservice_result(result: Value, proxy_mode: &str) -
"false"
},
);
copy_forwarded_response_headers_from_json(&mut response, &result);
response
}
@@ -705,6 +754,7 @@ async fn direct_microservice_response(
}
};
let status = response.status().as_u16();
let response_headers = response.headers().clone();
let content_type = response
.headers()
.get(reqwest::header::CONTENT_TYPE)
@@ -737,6 +787,7 @@ async fn direct_microservice_response(
"x-unidesk-response-truncated",
if truncated { "true" } else { "false" },
);
copy_forwarded_response_headers_from_reqwest(&mut response, &response_headers);
response
}