-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathauth-proxy.mjs
More file actions
99 lines (86 loc) · 3.39 KB
/
Copy pathauth-proxy.mjs
File metadata and controls
99 lines (86 loc) · 3.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
// Local auth-injection proxy for Azure OpenAI managed-identity auth.
//
// OpenClaw points OPENAI_BASE_URL at this proxy; the proxy attaches a fresh
// Entra ID bearer token to every forwarded request. getBearerTokenProvider
// caches tokens and only refreshes when near expiry, so the SDK gets a valid
// token forever without restarts.
//
// Why a proxy and not env-var refresh?
// process.env mutations in a parent never propagate to spawned children, and
// SIGHUP on OpenClaw triggers a config reload but does not re-read env. The
// only reliable refresh path is at request time.
//
// Optional escape hatch: setting AOAI_DEFAULT_API_VERSION (e.g. when an
// adapter needs the classic `/openai/deployments/...` surface that requires
// `?api-version=...`) makes the proxy append that query value to any
// `/openai/...` request that doesn't already carry one. Off by default —
// today's baseUrl points at the AOAI v1 endpoint which doesn't need it.
import http from "node:http";
import { request as httpsRequest } from "node:https";
import { DefaultAzureCredential, getBearerTokenProvider } from "@azure/identity";
const UPSTREAM = process.env.AOAI_UPSTREAM_URL;
const PORT = Number(process.env.AUTH_PROXY_PORT || 18790);
const SCOPE = "https://cognitiveservices.azure.com/.default";
const DEFAULT_API_VERSION = (process.env.AOAI_DEFAULT_API_VERSION || "").trim();
if (!UPSTREAM) {
console.error("[auth-proxy] AOAI_UPSTREAM_URL is required (e.g. https://<name>.openai.azure.com)");
process.exit(2);
}
const upstream = new URL(UPSTREAM);
const credential = new DefaultAzureCredential();
const tokenProvider = getBearerTokenProvider(credential, SCOPE);
function ensureApiVersion(requestUrl) {
if (!DEFAULT_API_VERSION) return requestUrl;
if (!requestUrl || !requestUrl.startsWith("/openai/")) return requestUrl;
const qIndex = requestUrl.indexOf("?");
const query = qIndex === -1 ? "" : requestUrl.slice(qIndex + 1);
if (/(^|&)api-version=/.test(query)) return requestUrl;
const sep = qIndex === -1 ? "?" : "&";
return `${requestUrl}${sep}api-version=${encodeURIComponent(DEFAULT_API_VERSION)}`;
}
const server = http.createServer(async (req, res) => {
let token;
try {
token = await tokenProvider();
} catch (err) {
console.error("[auth-proxy] token-fetch failed:", err.message);
res.writeHead(502, { "content-type": "text/plain" });
res.end("auth-proxy: token-fetch-failed");
return;
}
const headers = { ...req.headers };
delete headers.host;
delete headers["api-key"];
headers.authorization = `Bearer ${token}`;
headers.host = upstream.host;
const forwardPath = ensureApiVersion(req.url);
const upReq = httpsRequest(
{
hostname: upstream.hostname,
port: upstream.port || 443,
method: req.method,
path: forwardPath,
headers,
},
(upRes) => {
res.writeHead(upRes.statusCode ?? 502, upRes.headers);
upRes.pipe(res);
},
);
upReq.on("error", (err) => {
console.error("[auth-proxy] upstream error:", err.message);
if (!res.headersSent) {
res.writeHead(502, { "content-type": "text/plain" });
}
res.end("auth-proxy: upstream-error");
});
req.pipe(upReq);
});
server.listen(PORT, "127.0.0.1", () => {
console.log(`[auth-proxy] listening on 127.0.0.1:${PORT} -> ${UPSTREAM}`);
});
for (const sig of ["SIGTERM", "SIGINT"]) {
process.on(sig, () => {
server.close(() => process.exit(0));
});
}