feat: add Bearer token authentication via PROXY_API_KEY (v1.7.0)

- Add optional PROXY_API_KEY env var for Bearer token auth
- Return 401 for missing/invalid token on all endpoints except /health
- Skip auth when PROXY_API_KEY is not set (backwards compatible)
- Log auth status on startup
This commit is contained in:
2026-03-19 17:45:01 +10:00
parent ff05657c94
commit 2fa7991d6e
2 changed files with 13 additions and 1 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "openclaw-claude-proxy", "name": "openclaw-claude-proxy",
"version": "1.6.0", "version": "1.7.0",
"description": "OpenAI-compatible proxy that routes requests through Claude CLI — use your Claude Pro/Max subscription as an OpenClaw model provider", "description": "OpenAI-compatible proxy that routes requests through Claude CLI — use your Claude Pro/Max subscription as an OpenClaw model provider",
"type": "module", "type": "module",
"bin": { "bin": {
+12
View File
@@ -15,6 +15,7 @@
* CLAUDE_BIN — path to claude binary (default: "claude") * CLAUDE_BIN — path to claude binary (default: "claude")
* CLAUDE_TIMEOUT — per-request timeout in ms (default: 120000) * CLAUDE_TIMEOUT — per-request timeout in ms (default: 120000)
* CLAUDE_POOL_SIZE — warm process pool size per model (default: 1) * CLAUDE_POOL_SIZE — warm process pool size per model (default: 1)
* PROXY_API_KEY — Bearer token for API authentication (optional, if unset auth is disabled)
*/ */
import { createServer } from "node:http"; import { createServer } from "node:http";
import { spawn } from "node:child_process"; import { spawn } from "node:child_process";
@@ -31,6 +32,7 @@ const CLAUDE = process.env.CLAUDE_BIN || "claude";
const TIMEOUT = parseInt(process.env.CLAUDE_TIMEOUT || "300000", 10); const TIMEOUT = parseInt(process.env.CLAUDE_TIMEOUT || "300000", 10);
const POOL_SIZE = parseInt(process.env.CLAUDE_POOL_SIZE || "1", 10); const POOL_SIZE = parseInt(process.env.CLAUDE_POOL_SIZE || "1", 10);
const POOL_MAX_IDLE = parseInt(process.env.CLAUDE_POOL_MAX_IDLE || "60000", 10); // max idle time before recycle const POOL_MAX_IDLE = parseInt(process.env.CLAUDE_POOL_MAX_IDLE || "60000", 10); // max idle time before recycle
const PROXY_API_KEY = process.env.PROXY_API_KEY || "";
const VERSION = _pkg.version; const VERSION = _pkg.version;
const START_TIME = Date.now(); const START_TIME = Date.now();
@@ -399,6 +401,15 @@ const server = createServer(async (req, res) => {
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
if (req.method === "OPTIONS") { res.writeHead(204); res.end(); return; } if (req.method === "OPTIONS") { res.writeHead(204); res.end(); return; }
// Bearer token auth (skip for /health and when PROXY_API_KEY is not set)
if (PROXY_API_KEY && req.url !== "/health") {
const auth = req.headers["authorization"] || "";
const token = auth.startsWith("Bearer ") ? auth.slice(7) : "";
if (token !== PROXY_API_KEY) {
return jsonResponse(res, 401, { error: { message: "Unauthorized: invalid or missing Bearer token", type: "auth_error" } });
}
}
// GET /v1/models // GET /v1/models
if (req.url === "/v1/models" && req.method === "GET") { if (req.url === "/v1/models" && req.method === "GET") {
return jsonResponse(res, 200, { return jsonResponse(res, 200, {
@@ -455,4 +466,5 @@ server.listen(PORT, "0.0.0.0", () => {
console.log(`Claude binary: ${CLAUDE}`); console.log(`Claude binary: ${CLAUDE}`);
console.log(`Timeout: ${TIMEOUT}ms`); console.log(`Timeout: ${TIMEOUT}ms`);
console.log(`Pool size: ${POOL_SIZE} per model, max idle: ${POOL_MAX_IDLE / 1000}s`); console.log(`Pool size: ${POOL_SIZE} per model, max idle: ${POOL_MAX_IDLE / 1000}s`);
console.log(`Auth: ${PROXY_API_KEY ? "enabled (PROXY_API_KEY set)" : "disabled (no PROXY_API_KEY)"}`);
}); });