@bitkyc08/opencodex 2.6.23 → 2.6.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/gui/dist/index.html
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
} catch (e) {}
|
|
17
17
|
})();
|
|
18
18
|
</script>
|
|
19
|
-
<script type="module" crossorigin src="/assets/index-
|
|
19
|
+
<script type="module" crossorigin src="/assets/index-Ci7RsPIr.js"></script>
|
|
20
20
|
<link rel="stylesheet" crossorigin href="/assets/index-BcHhxo1I.css">
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
package/package.json
CHANGED
package/src/server.ts
CHANGED
|
@@ -1480,9 +1480,8 @@ function isLoopbackRequestHost(value: string | null): boolean {
|
|
|
1480
1480
|
function isLoopbackOriginValue(value: string): boolean {
|
|
1481
1481
|
try {
|
|
1482
1482
|
const parsed = new URL(value);
|
|
1483
|
-
if (parsed.protocol !== "http:") return false;
|
|
1484
|
-
|
|
1485
|
-
return parsed.port === configuredPort();
|
|
1483
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return false;
|
|
1484
|
+
return isLoopbackHostname(parsed.hostname);
|
|
1486
1485
|
} catch {
|
|
1487
1486
|
return false;
|
|
1488
1487
|
}
|
|
@@ -1497,12 +1496,22 @@ function isSameOriginAsRequest(req: Request, origin: string): boolean {
|
|
|
1497
1496
|
}
|
|
1498
1497
|
|
|
1499
1498
|
function isAllowedRequestOrigin(req: Request, config: OcxConfig): boolean {
|
|
1499
|
+
function isExtraAllowedOrigin(origin: string, cfg: OcxConfig): boolean {
|
|
1500
|
+
if (!cfg.corsAllowOrigins?.length) return false;
|
|
1501
|
+
return cfg.corsAllowOrigins.some(allowed => {
|
|
1502
|
+
try {
|
|
1503
|
+
return new URL(allowed).origin === new URL(origin).origin;
|
|
1504
|
+
} catch {
|
|
1505
|
+
return allowed === origin;
|
|
1506
|
+
}
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1500
1509
|
const origin = req.headers.get("Origin");
|
|
1501
1510
|
if (!isApiAuthRequired(config)) {
|
|
1502
1511
|
if (!isLoopbackRequestHost(req.headers.get("Host"))) return false;
|
|
1503
|
-
return !origin || isLoopbackOriginValue(origin);
|
|
1512
|
+
return !origin || isLoopbackOriginValue(origin) || isExtraAllowedOrigin(origin, config);
|
|
1504
1513
|
}
|
|
1505
|
-
return !origin || isLoopbackOriginValue(origin) || isSameOriginAsRequest(req, origin);
|
|
1514
|
+
return !origin || isLoopbackOriginValue(origin) || isSameOriginAsRequest(req, origin) || isExtraAllowedOrigin(origin, config);
|
|
1506
1515
|
}
|
|
1507
1516
|
|
|
1508
1517
|
export function corsHeaders(req?: Request, config?: OcxConfig): Record<string, string> {
|
|
@@ -1557,13 +1566,27 @@ export function assertServerAuthConfig(config: OcxConfig): void {
|
|
|
1557
1566
|
|
|
1558
1567
|
export function hasValidApiAuth(req: Request, config: OcxConfig): boolean {
|
|
1559
1568
|
if (!isApiAuthRequired(config)) return true;
|
|
1569
|
+
const actual = req.headers.get("x-opencodex-api-key")?.trim()
|
|
1570
|
+
|| req.headers.get("authorization")?.replace(/^Bearer\s+/i, "").trim();
|
|
1571
|
+
if (!actual) return false;
|
|
1572
|
+
// Check env-based token
|
|
1560
1573
|
const expected = configuredApiAuthToken(config);
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1574
|
+
if (expected) {
|
|
1575
|
+
const enc = new TextEncoder();
|
|
1576
|
+
const expectedBytes = enc.encode(expected);
|
|
1577
|
+
const actualBytes = enc.encode(actual);
|
|
1578
|
+
if (expectedBytes.length === actualBytes.length && timingSafeEqual(actualBytes, expectedBytes)) return true;
|
|
1579
|
+
}
|
|
1580
|
+
// Check config-based API keys
|
|
1581
|
+
if (config.apiKeys?.length) {
|
|
1582
|
+
const enc = new TextEncoder();
|
|
1583
|
+
const actualBytes = enc.encode(actual);
|
|
1584
|
+
for (const k of config.apiKeys) {
|
|
1585
|
+
const keyBytes = enc.encode(k.key);
|
|
1586
|
+
if (keyBytes.length === actualBytes.length && timingSafeEqual(actualBytes, keyBytes)) return true;
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
return false;
|
|
1567
1590
|
}
|
|
1568
1591
|
|
|
1569
1592
|
function requireApiAuth(req: Request, config: OcxConfig, kind: "management" | "data-plane"): Response | null {
|
|
@@ -2070,6 +2093,37 @@ async function handleManagementAPI(req: Request, url: URL, config: OcxConfig): P
|
|
|
2070
2093
|
return jsonResponse({ success: true });
|
|
2071
2094
|
}
|
|
2072
2095
|
|
|
2096
|
+
// ---------------------------------------------------------------------------
|
|
2097
|
+
// API Keys management
|
|
2098
|
+
// ---------------------------------------------------------------------------
|
|
2099
|
+
if (url.pathname === "/api/keys" && req.method === "GET") {
|
|
2100
|
+
const keys = config.apiKeys ?? [];
|
|
2101
|
+
return jsonResponse({ keys: keys.map(k => ({ id: k.id, name: k.name, prefix: k.key.slice(0, 8) + "...", createdAt: k.createdAt })), endpoint: `http://${config.hostname ?? "127.0.0.1"}:${config.port ?? 10100}/v1/responses` }, 200, req, config);
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
if (url.pathname === "/api/keys" && req.method === "POST") {
|
|
2105
|
+
const body = await req.json() as { name?: string };
|
|
2106
|
+
const name = (body.name ?? "").trim() || "default";
|
|
2107
|
+
// Generate key from provider keys hash + random salt
|
|
2108
|
+
const providerKeys = Object.values(config.providers).map(p => p.apiKey ?? "").filter(Boolean).join("|");
|
|
2109
|
+
const salt = crypto.randomUUID();
|
|
2110
|
+
const hashInput = `${providerKeys}|${salt}|${Date.now()}`;
|
|
2111
|
+
const hashBuf = new Bun.CryptoHasher("sha256").update(hashInput).digest();
|
|
2112
|
+
const key = "ocx_" + Buffer.from(hashBuf).toString("hex").slice(0, 40);
|
|
2113
|
+
const entry = { id: crypto.randomUUID(), name, key, createdAt: new Date().toISOString() };
|
|
2114
|
+
config.apiKeys = [...(config.apiKeys ?? []), entry];
|
|
2115
|
+
saveConfig(config);
|
|
2116
|
+
return jsonResponse({ id: entry.id, name: entry.name, key: entry.key, createdAt: entry.createdAt }, 201, req, config);
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2119
|
+
if (url.pathname === "/api/keys" && req.method === "DELETE") {
|
|
2120
|
+
const body = await req.json() as { id?: string };
|
|
2121
|
+
if (!body.id) return jsonResponse({ error: "id required" }, 400, req, config);
|
|
2122
|
+
config.apiKeys = (config.apiKeys ?? []).filter(k => k.id !== body.id);
|
|
2123
|
+
saveConfig(config);
|
|
2124
|
+
return jsonResponse({ success: true }, 200, req, config);
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2073
2127
|
if (url.pathname === "/api/stop" && req.method === "POST") {
|
|
2074
2128
|
const { restoreNativeCodex } = await import("./codex-inject");
|
|
2075
2129
|
const { stopServiceIfInstalled } = await import("./service");
|
package/src/types.ts
CHANGED
|
@@ -251,6 +251,8 @@ export interface OcxConfig {
|
|
|
251
251
|
shutdownTimeoutMs?: number;
|
|
252
252
|
/** Advertise supports_websockets so Codex opens the WS endpoint. Default false; set true to opt in. */
|
|
253
253
|
websockets?: boolean;
|
|
254
|
+
/** Generated API keys for external access to the proxy's /v1/responses endpoint. */
|
|
255
|
+
apiKeys?: Array<{ id: string; name: string; key: string; createdAt: string }>;
|
|
254
256
|
/** Auto-start/sync the proxy from the Codex shim before launching Codex. Default true. */
|
|
255
257
|
codexAutoStart?: boolean;
|
|
256
258
|
/**
|
|
@@ -278,6 +280,8 @@ export interface OcxConfig {
|
|
|
278
280
|
upstreamFailoverThreshold?: number;
|
|
279
281
|
/** Background proactive token refresh ("Token Guardian"). Off by default; see OcxTokenGuardianConfig. */
|
|
280
282
|
tokenGuardian?: OcxTokenGuardianConfig;
|
|
283
|
+
/** Additional origins allowed for CORS (e.g. ["https://clisu-oracle.tail19a2d7.ts.net"]). Loopback origins are always allowed. */
|
|
284
|
+
corsAllowOrigins?: string[];
|
|
281
285
|
}
|
|
282
286
|
|
|
283
287
|
/**
|