Skip to content

Commit 6f52ef8

Browse files
committed
feat(api-tab): show which key did what, and say what each endpoint accepts
The API tab described its own auth rules from memory and got them wrong: the subtitle advertised `Authorization: Bearer` for every endpoint, which /v1/responses and /v1/chat/completions reject. The rules now ship from the server as an auth matrix and the page only names them, so the table cannot drift from the code that enforces it. An empty or malformed matrix is a load failure, never a rendered empty table. Keys gained a detail pane with attributed usage, a rename editor, and a delete that waits for its own request instead of navigating away from a failure. `attributionSince` is a dataset-level fact: a key with zero requests reads as zero, and a proxy with nothing attributable yet says so — those were the same blank before. Malformed `usage` from either the network or the session cache is now rejected rather than coerced to zeroes, which used to assert "never used" about data we could not read. The per-model test button became one chip per protocol, each posting the body its own wire expects; a chat body sent to Responses proved nothing about Responses. Each chip carries the one-time key in `x-opencodex-api-key` and is disabled without one, because on a loopback bind an unauthenticated request passes whether or not the key works. Delete and rename hold the pane's navigation lock while in flight, so both are bounded at 15s — a connection that never answers used to leave the rail, Back, and Overview disabled until a reload. api-keys-panels.tsx was 551 lines covering three subjects; the copy helpers and the endpoint/auth surface now live in their own files. Tests: 456 pass across 91 files, including a wire test that mounts the real page and reads the requests that actually left `fetch`.
1 parent 332c2f1 commit 6f52ef8

25 files changed

Lines changed: 1892 additions & 337 deletions

docs-site/src/content/docs/ja/reference/configuration.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,21 @@ token を入れる必要があります。
137137
x-opencodex-api-key: your-secret-token
138138
```
139139

140-
`Authorization: Bearer …` ヘッダーも許可します。起動後はダッシュボードで生成した `apiKeys` を環境変数
141-
token の代わりに使えます。すべての候補は timing side channel を防ぐため定数時間
142-
`timingSafeEqual`)で比較します。
140+
受け付けるヘッダーはエンドポイントごとに異なります。常に使えるのは `x-opencodex-api-key` です:
141+
142+
| エンドポイント | `Authorization: Bearer` | `x-opencodex-api-key` | `x-api-key` |
143+
|---|---|---|---|
144+
| `/v1/responses` | 不可 | **必須** | 不可 |
145+
| `/v1/chat/completions` | 不可 | **必須** | 不可 |
146+
| `/v1/messages` ||||
147+
| `/v1/models` ||||
148+
149+
Responses と Chat Completions が専用ヘッダーのみを受け付けるのは、その 2 経路の `Authorization`
150+
Codex Direct パススルーのものである可能性があり、2 つの bearer ドメインを混同できないためです。
151+
ダッシュボードの API タブもこの表をサーバーから受け取って描画するため、コードとずれません。
152+
153+
起動後はダッシュボードで生成した `apiKeys` を環境変数 token の代わりに使えます。すべての候補は
154+
timing side channel を防ぐため定数時間(`timingSafeEqual`)で比較します。
143155

144156
:::caution[LAN 公開]
145157
`0.0.0.0` にバインドするとプロキシと設定されたすべてのプロバイダー認証情報がローカルネットワークにさらされます。

docs-site/src/content/docs/ko/reference/configuration.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,21 @@ token을 넣어야 합니다.
144144
x-opencodex-api-key: your-secret-token
145145
```
146146

147-
`Authorization: Bearer …` 헤더도 허용합니다. 시작 후에는 대시보드에서 생성한 `apiKeys`를 환경 변수
148-
token 대신 쓸 수 있습니다. 모든 후보는 timing side channel을 막기 위해 상수 시간
149-
(`timingSafeEqual`)으로 비교합니다.
147+
받는 헤더는 엔드포인트마다 다릅니다. 항상 되는 건 `x-opencodex-api-key` 하나입니다:
148+
149+
| 엔드포인트 | `Authorization: Bearer` | `x-opencodex-api-key` | `x-api-key` |
150+
|---|---|---|---|
151+
| `/v1/responses` | 안 됨 | **필수** | 안 됨 |
152+
| `/v1/chat/completions` | 안 됨 | **필수** | 안 됨 |
153+
| `/v1/messages` | 가능 | 가능 | 가능 |
154+
| `/v1/models` | 가능 | 가능 | 가능 |
155+
156+
Responses와 Chat Completions가 전용 헤더만 받는 이유는, 그 두 경로의 `Authorization`이 Codex Direct
157+
패스스루의 것일 수 있어서입니다. 두 bearer 영역이 헷갈리면 안 됩니다. 대시보드 API 탭도 이 표를
158+
서버에서 받아 그리기 때문에 코드와 어긋날 수 없습니다.
159+
160+
시작 후에는 대시보드에서 생성한 `apiKeys`를 환경 변수 token 대신 쓸 수 있습니다. 모든 후보는
161+
timing side channel을 막기 위해 상수 시간(`timingSafeEqual`)으로 비교합니다.
150162

151163
:::caution[LAN 노출]
152164
`0.0.0.0`에 바인드하면 프록시와 설정된 모든 프로바이더 자격 증명이 로컬 네트워크에 노출됩니다.

docs-site/src/content/docs/reference/configuration.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -252,9 +252,22 @@ systemd, or Task Scheduler receives it. Clients must include the token in every
252252
x-opencodex-api-key: your-secret-token
253253
```
254254

255-
An `Authorization: Bearer …` header is also accepted. Dashboard-generated `apiKeys` may be used in
256-
place of the environment token after startup; all candidates are compared in constant time
257-
(`timingSafeEqual`) to prevent timing side-channels.
255+
Which headers work depends on the endpoint, so `x-opencodex-api-key` is the one that always does:
256+
257+
| Endpoint | `Authorization: Bearer` | `x-opencodex-api-key` | `x-api-key` |
258+
|---|---|---|---|
259+
| `/v1/responses` | not accepted | **required** | not accepted |
260+
| `/v1/chat/completions` | not accepted | **required** | not accepted |
261+
| `/v1/messages` | accepted | accepted | accepted |
262+
| `/v1/models` | accepted | accepted | accepted |
263+
264+
Responses and Chat Completions accept only the dedicated header because `Authorization` on those
265+
transports may belong to Codex Direct passthrough, and the two bearer domains must not be
266+
confusable. The API tab in the dashboard renders this same table from the server, so it cannot
267+
drift from the code.
268+
269+
Dashboard-generated `apiKeys` may be used in place of the environment token after startup; all
270+
candidates are compared in constant time (`timingSafeEqual`) to prevent timing side-channels.
258271

259272
:::caution[LAN exposure]
260273
Binding to `0.0.0.0` exposes your proxy — and all configured provider credentials — to the local

docs-site/src/content/docs/ru/reference/configuration.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,22 @@ ocx start
167167
x-opencodex-api-key: your-secret-token
168168
```
169169

170-
Заголовок `Authorization: Bearer …` также принимается. Сгенерированные в дашборде `apiKeys`
171-
после запуска можно использовать вместо токена из окружения; все кандидаты сравниваются за
172-
константное время (`timingSafeEqual`) для защиты от атак по времени.
170+
Какие заголовки подходят, зависит от эндпоинта. Всегда работает только `x-opencodex-api-key`:
171+
172+
| Эндпоинт | `Authorization: Bearer` | `x-opencodex-api-key` | `x-api-key` |
173+
|---|---|---|---|
174+
| `/v1/responses` | не принимается | **обязателен** | не принимается |
175+
| `/v1/chat/completions` | не принимается | **обязателен** | не принимается |
176+
| `/v1/messages` | принимается | принимается | принимается |
177+
| `/v1/models` | принимается | принимается | принимается |
178+
179+
Responses и Chat Completions принимают только выделенный заголовок: `Authorization` на этих
180+
транспортах может принадлежать passthrough Codex Direct, и две области bearer нельзя путать.
181+
Вкладка API в дашборде рисует эту же таблицу по данным сервера, поэтому она не разойдётся с кодом.
182+
183+
Сгенерированные в дашборде `apiKeys` после запуска можно использовать вместо токена из
184+
окружения; все кандидаты сравниваются за константное время (`timingSafeEqual`) для защиты от
185+
атак по времени.
173186

174187
:::caution[Доступ по LAN]
175188
Привязка к `0.0.0.0` открывает ваш прокси — и все настроенные учётные данные провайдеров —

docs-site/src/content/docs/zh-cn/reference/configuration.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,21 @@ ocx start
136136
x-opencodex-api-key: your-secret-token
137137
```
138138

139-
也可以使用 `Authorization: Bearer …` header。启动后,仪表盘生成的 `apiKeys` 可代替环境 token。
140-
所有候选值均用常量时间(`timingSafeEqual`)比较,避免 timing side-channel。
139+
接受哪些 header 取决于端点,始终可用的只有 `x-opencodex-api-key`
140+
141+
| 端点 | `Authorization: Bearer` | `x-opencodex-api-key` | `x-api-key` |
142+
|---|---|---|---|
143+
| `/v1/responses` | 不接受 | **必需** | 不接受 |
144+
| `/v1/chat/completions` | 不接受 | **必需** | 不接受 |
145+
| `/v1/messages` | 可用 | 可用 | 可用 |
146+
| `/v1/models` | 可用 | 可用 | 可用 |
147+
148+
Responses 和 Chat Completions 只接受专用 header,因为这两条链路上的 `Authorization` 可能属于
149+
Codex Direct 透传,两个 bearer 域不能混淆。仪表盘的 API 标签页同样从服务端获取并渲染这张表,
150+
因此不会与代码脱节。
151+
152+
启动后,仪表盘生成的 `apiKeys` 可代替环境 token。所有候选值均用常量时间(`timingSafeEqual`
153+
比较,避免 timing side-channel。
141154

142155
:::caution[LAN 暴露]
143156
绑定到 `0.0.0.0` 会把代理和所有已配置 provider credential 暴露到本地网络。只应在可信网络中

gui/src/api-access-models.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ export interface ExternalModelRow {
77
custom?: boolean;
88
}
99

10+
/** The inbound wires a client can speak to this proxy. */
11+
export type GatewayInboundProtocol = "responses" | "chat" | "messages";
12+
1013
/** Inbound gateway protocols — not inferred from provider type. */
11-
export function gatewayInboundProtocols(claudeCodeEnabled: boolean): string[] {
14+
export function gatewayInboundProtocols(claudeCodeEnabled: boolean): GatewayInboundProtocol[] {
1215
return claudeCodeEnabled
1316
? ["responses", "chat", "messages"]
1417
: ["responses", "chat"];

0 commit comments

Comments
 (0)