Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs-site/src/content/docs/ja/reference/management-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ Authorization: Bearer <admin-token>

|メソッドとパス |目的 |注目すべきエラー |
| --- | --- | --- |
| `GET, POST, DELETE /api/codex-auth/accounts` | Codex アカウントの一覧表示/更新、必要に応じてインポート、削除。成功した POST/DELETE は `catalogRefreshPending` を返します。 | 400 無効な入力。手動インポートは無効にすることができます。 |
| `GET, POST, DELETE /api/codex-auth/accounts` | Codex アカウントの一覧表示/更新または削除。POST は無効化された互換エンドポイントとしてのみ残り、成功した DELETE は `catalogRefreshPending` を返します。 | POST は常に 403 `manual_import_disabled`。DELETE の入力が無効な場合は 400。 |
| `PUT /api/codex-auth/accounts/alias` |アカウント エイリアスの設定またはクリア | 400 無効なアカウント/エイリアス |
| `PUT /api/codex-auth/accounts/pause` | 1 つのアカウントを一時停止または再開する | 400 無効なアカウント/状態。 404 アカウントが見つかりません |
| `PUT /api/codex-auth/accounts/pause-exhausted` |クォータを使い果たしたアカウントを一時停止する |ミューテーションロックの失敗は 503 になります |
Expand All @@ -223,8 +223,8 @@ Authorization: Bearer <admin-token>
| `POST /api/codex-auth/login/cancel` | Codex ログイン フローをキャンセルする | — |
| `GET /api/codex-auth/login-status` |フローまたはアカウントのログイン状態をポーリングする。新規アカウント完了時は回復が必要な場合だけ `catalogRefreshPending: true` を含みます。 |不明なフローは `expired` を報告します。アクティブなフローは `idle` を報告しません |

新規 account の config row は保存されたものの credential setup を完了できない場合、manual POST は
HTTP 500 を返し、OAuth の `login-status` は `status: "error"` を報告します。どちらも
新規 account の config row は保存されたものの credential setup を完了できない場合、OAuth の
`login-status` は `status: "error"`
`code: "codex_credential_persistence_failed"`、`accountId`、`needsReauth: true`、必要に応じて
`catalogRefreshPending: true` を含み、storage error の詳細は公開しません。account row は保存済みなので、
account 作成を再試行する前に再認証するか削除してください。
Expand Down
6 changes: 3 additions & 3 deletions docs-site/src/content/docs/ko/reference/management-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ Authorization: Bearer <admin-token>

| Method and path | 목적 | 주요 오류 |
| --- | --- | --- |
| `GET, POST, DELETE /api/codex-auth/accounts` | Codex account를 나열/갱신, 선택적으로 가져오기, 또는 삭제합니다. 성공한 POST/DELETE는 `catalogRefreshPending`를 포함합니다. | 400 잘못된 입력; 수동 가져오기를 비활성화할 수 있음 |
| `GET, POST, DELETE /api/codex-auth/accounts` | Codex account를 나열/갱신하거나 삭제합니다. POST는 비활성화된 호환성 endpoint로만 유지되며, 성공한 DELETE는 `catalogRefreshPending`를 포함합니다. | POST는 항상 403 `manual_import_disabled`; DELETE 입력이 잘못되면 400 |
| `PUT /api/codex-auth/accounts/alias` | 계정 alias를 설정하거나 지웁니다 | 400 잘못된 account/alias |
| `PUT /api/codex-auth/accounts/pause` | 계정 하나를 일시 중지하거나 재개합니다 | 400 잘못된 account/state; 404 누락된 account |
| `PUT /api/codex-auth/accounts/pause-exhausted` | quota가 소진된 account를 일시 중지합니다 | mutation-lock 실패는 503이 됩니다 |
Expand All @@ -223,8 +223,8 @@ Authorization: Bearer <admin-token>
| `POST /api/codex-auth/login/cancel` | Codex 로그인 흐름을 취소합니다 | — |
| `GET /api/codex-auth/login-status` | 흐름 또는 account 로그인 상태를 조회합니다. 새 계정 완료 시 복구가 필요할 때만 `catalogRefreshPending: true`를 포함합니다. | 알 수 없는 흐름은 `expired`로 보고되며, 활성 흐름이 없으면 `idle`로 보고됩니다 |

새 account의 config row는 저장되었지만 credential setup을 완료하지 못하면 manual POST는 HTTP 500을
반환하고 OAuth `login-status`는 `status: "error"`를 보고합니다. 두 응답 모두
새 account의 config row는 저장되었지만 credential setup을 완료하지 못하면 OAuth `login-status`는
`status: "error"`를 보고하며
`code: "codex_credential_persistence_failed"`, `accountId`, `needsReauth: true`, 필요한 경우
`catalogRefreshPending: true`를 포함하며 storage error 세부 정보는 노출하지 않습니다. account row는
저장된 상태이므로 account 생성을 다시 시도하기 전에 재인증하거나 삭제하십시오.
Expand Down
6 changes: 3 additions & 3 deletions docs-site/src/content/docs/reference/management-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ manager. Its routes are:

| Method and path | Purpose | Notable errors |
| --- | --- | --- |
| `GET, POST, DELETE /api/codex-auth/accounts` | List/refresh, optionally import, or delete Codex accounts. Successful POST/DELETE responses include `catalogRefreshPending`. | 400 invalid input; manual import can be disabled |
| `GET, POST, DELETE /api/codex-auth/accounts` | List/refresh or delete Codex accounts. POST is retained as a disabled compatibility endpoint; successful DELETE responses include `catalogRefreshPending`. | POST always returns 403 `manual_import_disabled`; 400 invalid DELETE input |
| `PUT /api/codex-auth/accounts/alias` | Set or clear an account alias | 400 invalid account/alias |
| `PUT /api/codex-auth/accounts/pause` | Pause or resume one account | 400 invalid account/state; 404 missing account |
| `PUT /api/codex-auth/accounts/pause-exhausted` | Pause accounts whose quota is exhausted | Mutation-lock failures become 503 |
Expand All @@ -256,8 +256,8 @@ manager. Its routes are:
| `POST /api/codex-auth/login/cancel` | Cancel a Codex login flow | — |
| `GET /api/codex-auth/login-status` | Poll a flow or account login state. A completed new-account flow includes `catalogRefreshPending: true` only when recovery is needed. | Unknown flows report `expired`; no active flow reports `idle` |

If a new account config row is saved but credential setup cannot finish, the manual POST returns
HTTP 500 and OAuth `login-status` reports `status: "error"`. Both use
If a new account config row is saved but credential setup cannot finish, OAuth `login-status` reports
`status: "error"` with
`code: "codex_credential_persistence_failed"`, `accountId`, `needsReauth: true`, and optional
`catalogRefreshPending: true`; storage-error details are not exposed. The account row remains saved:
reauthenticate or delete it before retrying account creation.
Expand Down
6 changes: 3 additions & 3 deletions docs-site/src/content/docs/ru/reference/management-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ picker изменилась. `catalogRefreshPending: true` в успешном

| Метод и путь | Назначение | Особые ошибки |
| --- | --- | --- |
| `GET, POST, DELETE /api/codex-auth/accounts` | Показать/обновить список, по желанию импортировать, либо удалить аккаунты Codex. Успешные POST/DELETE включают `catalogRefreshPending`. | 400 invalid input; manual import can be disabled |
| `GET, POST, DELETE /api/codex-auth/accounts` | Показать/обновить список либо удалить аккаунты Codex. POST сохранён только как отключённый endpoint совместимости; успешный DELETE включает `catalogRefreshPending`. | POST всегда возвращает 403 `manual_import_disabled`; 400 при неверных данных DELETE |
| `PUT /api/codex-auth/accounts/alias` | Задать или очистить alias аккаунта | 400 invalid account/alias |
| `PUT /api/codex-auth/accounts/pause` | Поставить один аккаунт на паузу или снять её | 400 invalid account/state; 404 missing account |
| `PUT /api/codex-auth/accounts/pause-exhausted` | Поставить на паузу аккаунты с исчерпанной квотой | Сбои mutation-lock превращаются в 503 |
Expand All @@ -247,8 +247,8 @@ picker изменилась. `catalogRefreshPending: true` в успешном
| `POST /api/codex-auth/login/cancel` | Отменить login-flow Codex | — |
| `GET /api/codex-auth/login-status` | Опрашивать flow или login-state аккаунта. Завершение нового аккаунта включает `catalogRefreshPending: true` только при необходимости восстановления. | Неизвестные flow'ы сообщаются как `expired`; отсутствие активного flow — как `idle` |

Если config row нового аккаунта сохранён, но credential setup не завершён, manual POST возвращает
HTTP 500, а OAuth `login-status` сообщает `status: "error"`. Оба ответа содержат
Если config row нового аккаунта сохранён, но credential setup не завершён, OAuth `login-status`
сообщает `status: "error"` и содержит
`code: "codex_credential_persistence_failed"`, `accountId`, `needsReauth: true` и при необходимости
`catalogRefreshPending: true`; детали storage error не раскрываются. Account row остаётся сохранённым:
перед повторным созданием аккаунта выполните reauthentication или удалите его.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ Authorization: Bearer <admin-token>

| 方法和路径 | 用途 | 典型错误 |
| --- | --- | --- |
| `GET, POST, DELETE /api/codex-auth/accounts` | 列出/刷新,可选导入,或删除 Codex 账户。成功的 POST/DELETE 响应包含 `catalogRefreshPending`。 | 400 输入无效;手动导入可能被禁用 |
| `GET, POST, DELETE /api/codex-auth/accounts` | 列出/刷新或删除 Codex 账户。POST 仅作为已禁用的兼容端点保留;成功的 DELETE 响应包含 `catalogRefreshPending`。 | POST 始终返回 403 `manual_import_disabled`;DELETE 输入无效时返回 400 |
| `PUT /api/codex-auth/accounts/alias` | 设置或清除账户别名 | 400 账户/别名无效 |
| `PUT /api/codex-auth/accounts/pause` | 暂停或恢复一个账户 | 400 账户/状态无效;404 缺少账户 |
| `PUT /api/codex-auth/accounts/pause-exhausted` | 暂停配额已耗尽的账户 | 变更锁失败会变成 503 |
Expand All @@ -225,8 +225,8 @@ Authorization: Bearer <admin-token>
| `POST /api/codex-auth/login/cancel` | 取消一个 Codex 登录流程 | — |
| `GET /api/codex-auth/login-status` | 轮询某个流程或账户登录状态。新账号流程完成时,仅在需要恢复时包含 `catalogRefreshPending: true`。 | 未知流程报告为 `expired`;没有活跃流程时报告为 `idle` |

如果新账号的 config row 已保存但 credential setup 未能完成,manual POST 会返回 HTTP 500,OAuth
`login-status` 会报告 `status: "error"`。两者都包含
如果新账号的 config row 已保存但 credential setup 未能完成,OAuth `login-status` 会报告
`status: "error"`,并包含
`code: "codex_credential_persistence_failed"`、`accountId`、`needsReauth: true`,并在需要时包含
`catalogRefreshPending: true`;底层 storage error 详情不会暴露。account row 会保持已保存状态;再次
创建账号前,请重新认证或删除该账号。
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ Session 簽發在需要 data-plane 認證時停用,這包含遠端綁定。遠

| 方法與路徑 | 用途 | Notable errors |
| --- | --- | --- |
| `GET, POST, DELETE /api/codex-auth/accounts` | 列出/重新整理、可選擇匯入或刪除 Codex 帳號 | 400 無效輸入;手動匯入可被停用 |
| `GET, POST, DELETE /api/codex-auth/accounts` | 列出/重新整理或刪除 Codex 帳號。POST 僅保留為已停用的相容 endpoint;成功的 DELETE 回應包含 `catalogRefreshPending`。 | POST 一律回傳 403 `manual_import_disabled`;DELETE 輸入無效時回傳 400 |
| `PUT /api/codex-auth/accounts/alias` | 設定或清除帳號別名 | 400 無效帳號/別名 |
| `PUT /api/codex-auth/accounts/pause` | 暫停或恢復一個帳號 | 400 無效帳號/狀態;404 缺失帳號 |
| `PUT /api/codex-auth/accounts/pause-exhausted` | 暫停配額耗盡的帳號 | 變更鎖失敗變為 503 |
Expand Down
76 changes: 2 additions & 74 deletions src/codex/auth-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export {
setAccountQuotaFromParsed,
updateAccountQuota,
} from "./quota";
import { extractAccountId, decodeJwtPayload } from "../oauth/chatgpt";
import { extractAccountId } from "../oauth/chatgpt";
import { getMainAccountPlan, MAIN_CODEX_ACCOUNT_ID, setMainAccountPlan } from "./main-account";
import { captureConfigGeneration, registerStateSweepAfterTick } from "../lib/state-store-sweeper";
import { reconcileLiveStateStores } from "../lib/state-store-registrations";
Expand Down Expand Up @@ -147,7 +147,6 @@ function nativeMainProfileBusyResponse(): Response {
return response;
}

const MANUAL_IMPORT_ENV = "OPENCODEX_ENABLE_UNVERIFIED_CODEX_IMPORT";
const CODEX_CREDENTIAL_PERSISTENCE_ERROR = "Account was saved, but credential setup did not complete. Reauthenticate or remove the account.";
const CODEX_CREDENTIAL_PERSISTENCE_CODE = "codex_credential_persistence_failed";

Expand Down Expand Up @@ -375,10 +374,6 @@ async function readResetCreditJson(
}
}

export function isUnverifiedCodexImportEnabled(): boolean {
return process.env[MANUAL_IMPORT_ENV] === "1";
}

function manualImportDisabledResponse(): Response {
return jsonResponse({
error: "Manual Codex account import is disabled. Use OAuth login to add a pool account.",
Expand Down Expand Up @@ -1368,74 +1363,7 @@ export async function handleCodexAuthAPI(
}

if (url.pathname === "/api/codex-auth/accounts" && req.method === "POST") {
if (!isUnverifiedCodexImportEnabled()) return manualImportDisabledResponse();

let body: { id: string; email: string; plan?: unknown; accessToken: string; refreshToken: string; chatgptAccountId: string };
try { body = (await req.json()) as typeof body; } catch { return jsonResponse({ error: "Invalid JSON" }, 400); }
if (!body.id || !body.email || !body.accessToken || !body.refreshToken || !body.chatgptAccountId) {
return jsonResponse({ error: "Missing required fields" }, 400);
}
if (!isValidCodexAccountId(body.id)) {
return jsonResponse({ error: "Invalid account id format" }, 400);
}
if (body.accessToken.length > 10_000 || body.refreshToken.length > 10_000) {
return jsonResponse({ error: "Input too large" }, 400);
}
const runtimeConfig = getRuntimeConfig(config);
const preflightConflict = codexAccountPersistenceConflict(runtimeConfig, body.id, "create");
if (preflightConflict) return jsonResponse({ error: preflightConflict }, 400);
// 1.1: Duplicate check is scoped by personal vs workspace plan bucket.
const plan = codexPlanValue(body.plan);
const derivedAccountId = extractAccountId(undefined, body.accessToken) ?? body.chatgptAccountId;
const collision = checkAccountIdCollision(derivedAccountId, body.email, plan);
if (collision.collision) {
return jsonResponse({ error: collision.reason }, 400);
}
// 4.2: use JWT exp for expiresAt instead of hardcoded 1 hour
const payload = decodeJwtPayload(body.accessToken);
const exp = typeof payload?.exp === "number" ? payload.exp * 1000 : Date.now() + 3600_000;
const warmup = await verifyCodexAccountWarmup(body.id, body.accessToken, derivedAccountId);
if (!warmup.ok) return warmup.response;
const latestConfig = getRuntimeConfig(config);
const commitConflict = codexAccountPersistenceConflict(latestConfig, body.id, "create");
if (commitConflict) return jsonResponse({ error: commitConflict }, 400);
const addedAccount = withCodexAccountLogLabel(
{
id: body.id,
email: body.email,
...(plan !== undefined ? { plan } : {}),
isMain: false,
},
latestConfig.codexAccounts ?? [],
);
const persistence = persistNewCodexAccount(
config,
latestConfig,
addedAccount,
{
credential: {
accessToken: body.accessToken,
refreshToken: body.refreshToken,
expiresAt: exp,
chatgptAccountId: derivedAccountId,
},
validatedAt: warmup.validatedAt,
},
);
reconcileLiveStateStores();
if (persistence.status === "publication-failed") markAccountNeedsReauth(body.id);
const catalogRefresh = await convergeAccountNamespaceCatalog(
latestConfig,
persistence.pickerVisibilityChanged,
convergeCodexCatalog,
);
if (persistence.status === "publication-failed") {
return jsonResponse({
ok: false,
...codexCredentialPersistenceFailure(body.id, catalogRefresh.catalogRefreshPending === true),
}, 500);
}
return jsonResponse({ ok: true, ...catalogRefresh });
return manualImportDisabledResponse();
}

if (url.pathname === "/api/codex-auth/accounts" && req.method === "DELETE") {
Expand Down
Loading
Loading