Skip to content

fix(codex): resolve client_id and account_id handling in token refresh and file listing#2153

Open
GakkiNoOne wants to merge 2 commits intorouter-for-me:mainfrom
GakkiNoOne:fix/codex-client-id-account-id-refresh
Open

fix(codex): resolve client_id and account_id handling in token refresh and file listing#2153
GakkiNoOne wants to merge 2 commits intorouter-for-me:mainfrom
GakkiNoOne:fix/codex-client-id-account-id-refresh

Conversation

@GakkiNoOne
Copy link

@GakkiNoOne GakkiNoOne commented Mar 15, 2026

本次PR的目的主要是解决 不同来源的 chatgpt账号 导入的时候 怎么能兼容变成 codex的账号

  1. 很多不同来源的账号的rt对应的clientId不同 ,refresh的接口是需要clientid和rt对应的,否则无法刷新(目前项目写死clientid)
  2. chatgpt普号刷新后 idtoken里其实是没有 chatgpt_account_id和plan_type的(目前codex解析出来的是有的,chatgpt普号是没的)
  3. chatgpt账号的accountId是可能有多个的,比如普号一个accountid,team,plus都有分别的accountid

这3个问题的解决方案

  1. 导入的时候 json文件里直接支持传入clientid 和 chatgpt_account_id 和 plan_type 让用户自己控制即可
  2. 刷新的rt的时候 优先从用户传入的clientid,其次再从idtoken里解析,再其次用写死的
  3. accountid理论上是支持刷新的,但是懒的弄刷新覆盖的逻辑了(刷新覆盖的逻辑 可以看我写的测试文件里的逻辑,这个逻辑理论上 是需要在刷新rt后覆盖json配置文件里的accountid的,避免普号升级后变成team 或者 team账号到期了,导致accountid的变化,但是我也不知道这里是要执行一次 获取accountid的接口好 还是不好)
    获取accountid的接口是有风控的,需要指纹请求

这个PR改动的地方主要几个

  1. 管理页面导入文件的接口需要的文件格式 新增 clientid chatgpt_accountid plan_type3个字段 支持用户自己定义,顺便解决 不同clientid 刷新rt的问题

  2. 前端获取accountid进行 额度刷新的时候,支持优先获取json中配置的accountid

  3. 获取accountid的接口openai是有单独的,这个接口我写到了测试文件里,是否要合并到 rt刷新的逻辑里,作者自己判断下
    其他:

  4. 没考虑到的地方 作者再兼容下,我review的需要clientid、accountid 主要就是这些地方

----- ----------- 下面是CC 写的commit msg -----------------

改动说明

问题背景

  1. client_id 硬编码问题:刷新 token 时 client_id 写死为 app_EMoamEEZ73f0CkXaXp7hrann,但不同渠道登录拿到的 id_token.aud[0] 可能是不同的 client_id,导致刷新失败
  2. account_id 刷新后丢失问题:刷新后返回的新 id_token 有时不含 chatgpt_account_id 字段,而 GET /auth-files 接口只从 id_token 解析,导致刷新后 account_id 显示为空
  3. plan_type 来源问题plan_type 由文件监听器合成到 Attributes,而非存储在 Metadata,原代码只从 id_token 解析,未能利用已有数据

具体改动

internal/auth/codex/jwt_parser.go

  • 新增 GetClientID() 方法,返回 id_token.aud[0],即 token 颁发时使用的 OAuth client_id

internal/auth/codex/openai_auth.go

  • RefreshTokensRefreshTokensWithRetry 新增 clientID string 参数
  • clientID 为空则自动回退到硬编码的默认值,保持向后兼容

internal/runtime/executor/codex_executor.go

  • Refresh() 方法新增 client_id 解析逻辑:
    1. 优先读取 Metadata["client_id"](JSON 文件中显式存储的值)
    2. 若无,则解析 id_token.aud[0]
    3. 将解析到的 clientID 传入 RefreshTokensWithRetry

internal/api/handlers/management/auth_files.go

  • extractCodexIDTokenClaims 重构优先级逻辑:
    1. chatgpt_account_id:优先读 Metadata["account_id"](JSON 文件中的直接字段),缺失才解析 id_token
    2. plan_type:优先读 Attributes["plan_type"](文件监听器合成的值),缺失才解析 id_token
    3. 订阅日期字段仍从 id_token 解析

internal/runtime/executor/codex_executor_account_id_test.go(新增)

  • 使用 utls + Chrome TLS 指纹 + HTTP CONNECT 代理,测试 accounts/check API
  • 新增 pickBestAccountID() 辅助函数:从 $.accounts map 中按 team > plus > free 优先级选出最优 account_id

Test plan

  • go build ./... 通过
  • go test ./internal/auth/codex/... -v 单元测试通过
  • 手动测试:上传含 account_id 的 codex JSON 文件,GET /auth-files 能正确返回 chatgpt_account_id
  • 手动测试:token 刷新后 account_id 不丢失

🤖 Generated with Claude Code

…h and file listing

- Add JWTClaims.GetClientID() to extract client_id from id_token.aud[0]
- RefreshTokens and RefreshTokensWithRetry now accept a clientID param;
  falls back to the hardcoded constant when empty, fixing token refresh
  for accounts whose client_id differs from the default app
- Codex executor Refresh() now resolves clientID from Metadata["client_id"]
  first, then falls back to parsing id_token.aud[0] before calling refresh
- extractCodexIDTokenClaims checks Metadata["account_id"] and
  Attributes["plan_type"] first before parsing id_token, so the GET
  /auth-files response stays accurate after a refresh whose new id_token
  no longer carries chatgpt_account_id
- Add codex_executor_account_id_test.go: utls+Chrome-fingerprint test for
  the accounts/check API with pickBestAccountID helper (team > plus > free)

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses critical authentication and data retrieval issues within the Codex system. It refines how client_id, account_id, and plan_type are handled during token refresh and file listing operations. The changes ensure that the correct client_id is used for token refreshes, prevent the loss of account_id after refresh, and correctly source plan_type from available metadata, improving the robustness and accuracy of user authentication and data display.

Highlights

  • Client ID Handling: Resolved an issue where the client_id was hardcoded during token refresh, leading to failures for different login channels. The system now dynamically retrieves the client_id from id_token.aud[0] or Metadata.
  • Account ID Persistence: Fixed the problem of account_id being lost after token refresh by prioritizing Metadata["account_id"] for retrieval before falling back to parsing the id_token.
  • Plan Type Source: Corrected the plan_type retrieval logic to first check Attributes["plan_type"] (synthesized by the file watcher) before parsing the id_token.
  • New Client ID Method: Introduced a GetClientID() method in jwt_parser.go to extract the client ID from JWT claims.
  • Token Refresh API Update: Modified RefreshTokens and RefreshTokensWithRetry in openai_auth.go to accept a clientID parameter, ensuring correct client identification during refresh.
  • New Account Check Test: Added a new test file codex_executor_account_id_test.go to verify the accounts/check API using utls and a pickBestAccountID helper function.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • internal/api/handlers/management/auth_files.go
    • Refactored extractCodexIDTokenClaims to prioritize Metadata["account_id"] and Attributes["plan_type"] before parsing the id_token for these fields.
    • Subscription date fields continue to be parsed from the id_token.
  • internal/auth/codex/jwt_parser.go
    • Added a GetClientID() method to JWTClaims to return the first audience value from the JWT, representing the OAuth client ID.
  • internal/auth/codex/openai_auth.go
    • Updated RefreshTokens and RefreshTokensWithRetry functions to accept an optional clientID string parameter.
    • If clientID is empty, it falls back to the default hardcoded ClientID for backward compatibility.
  • internal/auth/codex/openai_auth_test.go
    • Modified the TestRefreshTokensWithRetry_NonRetryableOnlyAttemptsOnce test to pass the clientID argument.
  • internal/runtime/executor/codex_executor.go
    • Implemented logic in the Refresh() method to determine the clientID by first checking Metadata["client_id"] and then parsing id_token.aud[0].
    • The determined clientID is now passed to RefreshTokensWithRetry.
  • internal/runtime/executor/codex_executor_account_id_test.go
    • Added a new test file.
    • Introduced utlsTransport for Chrome-fingerprint TLS transport with HTTP CONNECT proxy support.
    • Added planTypePriority and pickBestAccountID helper functions to select the best account ID based on plan type (team > plus > free).
    • Included TestCodexAccountCheck to test the accounts/check API using a real access token and utls.
Activity
  • No human activity (comments, reviews, etc.) has been recorded for this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

本次代码审查主要关注 client_idaccount_id 在令牌刷新和文件列表中的处理逻辑。整体改动方向正确,解决了硬编码 client_id 和刷新后 account_id 丢失的问题。

我提出了两点建议:

  1. 高危:修复 auth_files.go 中的一个逻辑缺陷。当前实现在特定条件下会跳过对 id_token 的解析,导致无法获取订阅日期信息。
  2. 严重:移除新测试文件 codex_executor_account_id_test.go 中硬编码的访问令牌和代理URL。将凭证等敏感信息提交到代码库会带来安全风险。

除了以上两点,其余代码实现良好,符合PR的目标。

Comment on lines +134 to +135
accessToken := "eyJhbGciOiJSUzI1NiIsImtpZCI6IjE5MzQ0ZTY1LWJiYzktNDRkMS1hOWQwLWY5NTdiMDc5YmQwZSIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiaHR0cHM6Ly9hcGkub3BlbmFpLmNvbS92MSJdLCJjbGllbnRfaWQiOiJhcHBfWDh6WTZ2VzJwUTl0UjNkRTduSzFqTDVnSCIsImV4cCI6MTc3NDQ1NTUyNSwiaHR0cHM6Ly9hcGkub3BlbmFpLmNvbS9hdXRoIjp7ImNoYXRncHRfYWNjb3VudF9pZCI6Ijg0YzNjZGM2LWFiZWQtNDlhNy1iY2RlLWZmMjQwMjE3NmZkYiIsImNoYXRncHRfYWNjb3VudF91c2VyX2lkIjoidXNlci00MXlCY3M0STZscEJBVEh5R2lTNG9wTUNfXzg0YzNjZGM2LWFiZWQtNDlhNy1iY2RlLWZmMjQwMjE3NmZkYiIsImNoYXRncHRfY29tcHV0ZV9yZXNpZGVuY3kiOiJub19jb25zdHJhaW50IiwiY2hhdGdwdF9wbGFuX3R5cGUiOiJmcmVlIiwiY2hhdGdwdF91c2VyX2lkIjoidXNlci00MXlCY3M0STZscEJBVEh5R2lTNG9wTUMiLCJ1c2VyX2lkIjoidXNlci00MXlCY3M0STZscEJBVEh5R2lTNG9wTUMifSwiaHR0cHM6Ly9hcGkub3BlbmFpLmNvbS9wcm9maWxlIjp7ImVtYWlsIjoiZ3VzLmxhcnNvbjJAZDQuemh1ZmFkYS5kZSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlfSwiaWF0IjoxNzczNTkxNTI1LCJpc3MiOiJodHRwczovL2F1dGgub3BlbmFpLmNvbSIsImp0aSI6IjYzZDgwY2M3LWI1NzEtNDk3Yi05ZmI2LTZiZTE2MDE1ZjI4MiIsIm5iZiI6MTc3MzU5MTUyNSwicHdkX2F1dGhfdGltZSI6MTc3MzU5MTUyNDA3NCwic2NwIjpbIm9wZW5pZCIsImVtYWlsIiwicHJvZmlsZSIsIm9mZmxpbmVfYWNjZXNzIiwibW9kZWwucmVxdWVzdCIsIm1vZGVsLnJlYWQiLCJvcmdhbml6YXRpb24ucmVhZCIsIm9yZ2FuaXphdGlvbi53cml0ZSJdLCJzZXNzaW9uX2lkIjoiYXV0aHNlc3NfWTFwQWh2VWRXQVVNb1pPVzN0OEllNjFKIiwic2wiOnRydWUsInN1YiI6ImF1dGgwfHhJcnpFSTlvV0xBUUtMMW4wR2tKenBETiJ9.f8tBrHuYqZtVpSY3cxf0NOrEgGHVZlhSQhz4_aMngNIq8_O1oY6ajyWoJpdqtf_m-luzRswZMgA-fKGiEbKu-LqqFiCHnNOFkK5ymdAoXFLsHWEX-BFS5wqTKJ6_nphrqLgVMAaA1mwuWQZ3PD2mCMJ_eErFhFPGlOCBR1TyDSMhJhvDMHB81sqJbxJBpkQNV3J1GDcvvUaNiQebAs4LNOhNaQfYTxJQqJZiGCnwjeHWql_aSSKv4y1vEXSLwH-GEqfjlpHYZqhYTHpdr_PzzOOIWq_X9ScedMOy699UYwyQa7IKcwCw6ZqaVbR_WjAdHunWi8yOl5C7JFuUA2xhrfMOQUgg86vv5oBw_OYTHzX51Dimh_SHhLaUCNC0-SPRZ-IiYz91MveiR_QCSHvU_ZXJO-FY8Xqa6NEdLZ8AbrOb81dxnV8DOABxolHVMtuxINPQuzHAEKxAyNGQkwgo7_O4TAVUycpv4b3LMIoSdCHy7F7q9Dh1UR8jTugM0Zqor6bD0XEwdFL5KxONZk-alHAo93IrDS9D8L0bjp0cMl7A1ZyjYjOMpw8Liq_b6V6uyPPJsw0DY1q-LtrQXN05W1La5bOX-J0yjtEnRrKtm5mGZ8cT2RnKYJJFFSckbMY2EvKuSEMKh0T5YoqPx-LZsXRViscbGfLBh8e2gzvXlq8"
proxyURL := "http://127.0.0.1:7890"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

此测试用例中硬编码了访问令牌(accessToken)和代理URL(proxyURL)。将敏感信息(如令牌)直接提交到代码库中会带来严重的安全风险,即使是测试令牌。此外,硬编码代理URL会降低测试的灵活性。

建议修改此测试,从环境变量中读取这些值,并在令牌不存在时跳过测试。这符合测试注释中描述的预期行为。

请确保在文件顶部添加 import "os"

Suggested change
accessToken := "eyJhbGciOiJSUzI1NiIsImtpZCI6IjE5MzQ0ZTY1LWJiYzktNDRkMS1hOWQwLWY5NTdiMDc5YmQwZSIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiaHR0cHM6Ly9hcGkub3BlbmFpLmNvbS92MSJdLCJjbGllbnRfaWQiOiJhcHBfWDh6WTZ2VzJwUTl0UjNkRTduSzFqTDVnSCIsImV4cCI6MTc3NDQ1NTUyNSwiaHR0cHM6Ly9hcGkub3BlbmFpLmNvbS9hdXRoIjp7ImNoYXRncHRfYWNjb3VudF9pZCI6Ijg0YzNjZGM2LWFiZWQtNDlhNy1iY2RlLWZmMjQwMjE3NmZkYiIsImNoYXRncHRfYWNjb3VudF91c2VyX2lkIjoidXNlci00MXlCY3M0STZscEJBVEh5R2lTNG9wTUNfXzg0YzNjZGM2LWFiZWQtNDlhNy1iY2RlLWZmMjQwMjE3NmZkYiIsImNoYXRncHRfY29tcHV0ZV9yZXNpZGVuY3kiOiJub19jb25zdHJhaW50IiwiY2hhdGdwdF9wbGFuX3R5cGUiOiJmcmVlIiwiY2hhdGdwdF91c2VyX2lkIjoidXNlci00MXlCY3M0STZscEJBVEh5R2lTNG9wTUMiLCJ1c2VyX2lkIjoidXNlci00MXlCY3M0STZscEJBVEh5R2lTNG9wTUMifSwiaHR0cHM6Ly9hcGkub3BlbmFpLmNvbS9wcm9maWxlIjp7ImVtYWlsIjoiZ3VzLmxhcnNvbjJAZDQuemh1ZmFkYS5kZSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlfSwiaWF0IjoxNzczNTkxNTI1LCJpc3MiOiJodHRwczovL2F1dGgub3BlbmFpLmNvbSIsImp0aSI6IjYzZDgwY2M3LWI1NzEtNDk3Yi05ZmI2LTZiZTE2MDE1ZjI4MiIsIm5iZiI6MTc3MzU5MTUyNSwicHdkX2F1dGhfdGltZSI6MTc3MzU5MTUyNDA3NCwic2NwIjpbIm9wZW5pZCIsImVtYWlsIiwicHJvZmlsZSIsIm9mZmxpbmVfYWNjZXNzIiwibW9kZWwucmVxdWVzdCIsIm1vZGVsLnJlYWQiLCJvcmdhbml6YXRpb24ucmVhZCIsIm9yZ2FuaXphdGlvbi53cml0ZSJdLCJzZXNzaW9uX2lkIjoiYXV0aHNlc3NfWTFwQWh2VWRXQVVNb1pPVzN0OEllNjFKIiwic2wiOnRydWUsInN1YiI6ImF1dGgwfHhJcnpFSTlvV0xBUUtMMW4wR2tKenBETiJ9.f8tBrHuYqZtVpSY3cxf0NOrEgGHVZlhSQhz4_aMngNIq8_O1oY6ajyWoJpdqtf_m-luzRswZMgA-fKGiEbKu-LqqFiCHnNOFkK5ymdAoXFLsHWEX-BFS5wqTKJ6_nphrqLgVMAaA1mwuWQZ3PD2mCMJ_eErFhFPGlOCBR1TyDSMhJhvDMHB81sqJbxJBpkQNV3J1GDcvvUaNiQebAs4LNOhNaQfYTxJQqJZiGCnwjeHWql_aSSKv4y1vEXSLwH-GEqfjlpHYZqhYTHpdr_PzzOOIWq_X9ScedMOy699UYwyQa7IKcwCw6ZqaVbR_WjAdHunWi8yOl5C7JFuUA2xhrfMOQUgg86vv5oBw_OYTHzX51Dimh_SHhLaUCNC0-SPRZ-IiYz91MveiR_QCSHvU_ZXJO-FY8Xqa6NEdLZ8AbrOb81dxnV8DOABxolHVMtuxINPQuzHAEKxAyNGQkwgo7_O4TAVUycpv4b3LMIoSdCHy7F7q9Dh1UR8jTugM0Zqor6bD0XEwdFL5KxONZk-alHAo93IrDS9D8L0bjp0cMl7A1ZyjYjOMpw8Liq_b6V6uyPPJsw0DY1q-LtrQXN05W1La5bOX-J0yjtEnRrKtm5mGZ8cT2RnKYJJFFSckbMY2EvKuSEMKh0T5YoqPx-LZsXRViscbGfLBh8e2gzvXlq8"
proxyURL := "http://127.0.0.1:7890"
accessToken := os.Getenv("CODEX_ACCESS_TOKEN")
if accessToken == "" {
t.Skip("skipping test: environment variable CODEX_ACCESS_TOKEN is not set")
}
proxyURL := os.Getenv("CODEX_PROXY_URL")

Comment on lines 444 to 471
needsIDToken := result["chatgpt_account_id"] == nil || result["plan_type"] == nil
if needsIDToken {
idTokenRaw, ok := auth.Metadata["id_token"].(string)
if ok {
idToken := strings.TrimSpace(idTokenRaw)
if idToken != "" {
claims, err := codex.ParseJWTToken(idToken)
if err == nil && claims != nil {
if result["chatgpt_account_id"] == nil {
if v := strings.TrimSpace(claims.CodexAuthInfo.ChatgptAccountID); v != "" {
result["chatgpt_account_id"] = v
}
}
if result["plan_type"] == nil {
if v := strings.TrimSpace(claims.CodexAuthInfo.ChatgptPlanType); v != "" {
result["plan_type"] = v
}
}
if v := claims.CodexAuthInfo.ChatgptSubscriptionActiveStart; v != nil {
result["chatgpt_subscription_active_start"] = v
}
if v := claims.CodexAuthInfo.ChatgptSubscriptionActiveUntil; v != nil {
result["chatgpt_subscription_active_until"] = v
}
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

当前逻辑存在一个缺陷:当 chatgpt_account_idplan_type 都已经从 MetadataAttributes 中获取时,needsIDToken 会为 false,导致代码块被跳过,从而无法解析 id_token 中包含的订阅日期字段(chatgpt_subscription_active_startchatgpt_subscription_active_until)。根据 PR 描述,这些日期字段应该总是从 id_token 中解析。

为了修复此问题并简化代码,建议重构逻辑,移除 needsIDToken 条件,始终尝试解析 id_token(如果存在),并将其用作补充字段和获取订阅日期的来源。

	if idTokenRaw, ok := auth.Metadata["id_token"].(string); ok {
		idToken := strings.TrimSpace(idTokenRaw)
		if idToken != "" {
			claims, err := codex.ParseJWTToken(idToken)
			if err == nil && claims != nil {
				if result["chatgpt_account_id"] == nil {
					if v := strings.TrimSpace(claims.CodexAuthInfo.ChatgptAccountID); v != "" {
						result["chatgpt_account_id"] = v
					}
				}
				if result["plan_type"] == nil {
					if v := strings.TrimSpace(claims.CodexAuthInfo.ChatgptPlanType); v != "" {
						result["plan_type"] = v
					}
				}
				if v := claims.CodexAuthInfo.ChatgptSubscriptionActiveStart; v != nil {
					result["chatgpt_subscription_active_start"] = v
				}
				if v := claims.CodexAuthInfo.ChatgptSubscriptionActiveUntil; v != nil {
					result["chatgpt_subscription_active_until"] = v
				}
			}
		}
	}

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8590e4497b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +133 to +135
func TestCodexAccountCheck(t *testing.T) {
accessToken := "eyJhbGciOiJSUzI1NiIsImtpZCI6IjE5MzQ0ZTY1LWJiYzktNDRkMS1hOWQwLWY5NTdiMDc5YmQwZSIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiaHR0cHM6Ly9hcGkub3BlbmFpLmNvbS92MSJdLCJjbGllbnRfaWQiOiJhcHBfWDh6WTZ2VzJwUTl0UjNkRTduSzFqTDVnSCIsImV4cCI6MTc3NDQ1NTUyNSwiaHR0cHM6Ly9hcGkub3BlbmFpLmNvbS9hdXRoIjp7ImNoYXRncHRfYWNjb3VudF9pZCI6Ijg0YzNjZGM2LWFiZWQtNDlhNy1iY2RlLWZmMjQwMjE3NmZkYiIsImNoYXRncHRfYWNjb3VudF91c2VyX2lkIjoidXNlci00MXlCY3M0STZscEJBVEh5R2lTNG9wTUNfXzg0YzNjZGM2LWFiZWQtNDlhNy1iY2RlLWZmMjQwMjE3NmZkYiIsImNoYXRncHRfY29tcHV0ZV9yZXNpZGVuY3kiOiJub19jb25zdHJhaW50IiwiY2hhdGdwdF9wbGFuX3R5cGUiOiJmcmVlIiwiY2hhdGdwdF91c2VyX2lkIjoidXNlci00MXlCY3M0STZscEJBVEh5R2lTNG9wTUMiLCJ1c2VyX2lkIjoidXNlci00MXlCY3M0STZscEJBVEh5R2lTNG9wTUMifSwiaHR0cHM6Ly9hcGkub3BlbmFpLmNvbS9wcm9maWxlIjp7ImVtYWlsIjoiZ3VzLmxhcnNvbjJAZDQuemh1ZmFkYS5kZSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlfSwiaWF0IjoxNzczNTkxNTI1LCJpc3MiOiJodHRwczovL2F1dGgub3BlbmFpLmNvbSIsImp0aSI6IjYzZDgwY2M3LWI1NzEtNDk3Yi05ZmI2LTZiZTE2MDE1ZjI4MiIsIm5iZiI6MTc3MzU5MTUyNSwicHdkX2F1dGhfdGltZSI6MTc3MzU5MTUyNDA3NCwic2NwIjpbIm9wZW5pZCIsImVtYWlsIiwicHJvZmlsZSIsIm9mZmxpbmVfYWNjZXNzIiwibW9kZWwucmVxdWVzdCIsIm1vZGVsLnJlYWQiLCJvcmdhbml6YXRpb24ucmVhZCIsIm9yZ2FuaXphdGlvbi53cml0ZSJdLCJzZXNzaW9uX2lkIjoiYXV0aHNlc3NfWTFwQWh2VWRXQVVNb1pPVzN0OEllNjFKIiwic2wiOnRydWUsInN1YiI6ImF1dGgwfHhJcnpFSTlvV0xBUUtMMW4wR2tKenBETiJ9.f8tBrHuYqZtVpSY3cxf0NOrEgGHVZlhSQhz4_aMngNIq8_O1oY6ajyWoJpdqtf_m-luzRswZMgA-fKGiEbKu-LqqFiCHnNOFkK5ymdAoXFLsHWEX-BFS5wqTKJ6_nphrqLgVMAaA1mwuWQZ3PD2mCMJ_eErFhFPGlOCBR1TyDSMhJhvDMHB81sqJbxJBpkQNV3J1GDcvvUaNiQebAs4LNOhNaQfYTxJQqJZiGCnwjeHWql_aSSKv4y1vEXSLwH-GEqfjlpHYZqhYTHpdr_PzzOOIWq_X9ScedMOy699UYwyQa7IKcwCw6ZqaVbR_WjAdHunWi8yOl5C7JFuUA2xhrfMOQUgg86vv5oBw_OYTHzX51Dimh_SHhLaUCNC0-SPRZ-IiYz91MveiR_QCSHvU_ZXJO-FY8Xqa6NEdLZ8AbrOb81dxnV8DOABxolHVMtuxINPQuzHAEKxAyNGQkwgo7_O4TAVUycpv4b3LMIoSdCHy7F7q9Dh1UR8jTugM0Zqor6bD0XEwdFL5KxONZk-alHAo93IrDS9D8L0bjp0cMl7A1ZyjYjOMpw8Liq_b6V6uyPPJsw0DY1q-LtrQXN05W1La5bOX-J0yjtEnRrKtm5mGZ8cT2RnKYJJFFSckbMY2EvKuSEMKh0T5YoqPx-LZsXRViscbGfLBh8e2gzvXlq8"
proxyURL := "http://127.0.0.1:7890"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Gate live account-check test behind explicit opt-in

TestCodexAccountCheck runs as a normal unit test but performs a real network call through a fixed local proxy (127.0.0.1:7890) and immediately Fatalfs on connection errors, so go test fails in any environment without that proxy (including typical CI). The comment says env vars should control execution, but the implementation does not check them or skip, making the test suite non-hermetic and brittle.

Useful? React with 👍 / 👎.

// CODEX_ACCESS_TOKEN=eyJ... go test ./internal/runtime/executor/... -run TestCodexAccountCheck -v
// CODEX_ACCESS_TOKEN=eyJ... CODEX_PROXY_URL=http://127.0.0.1:7890 go test ./internal/runtime/executor/... -run TestCodexAccountCheck -v
func TestCodexAccountCheck(t *testing.T) {
accessToken := "eyJhbGciOiJSUzI1NiIsImtpZCI6IjE5MzQ0ZTY1LWJiYzktNDRkMS1hOWQwLWY5NTdiMDc5YmQwZSIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiaHR0cHM6Ly9hcGkub3BlbmFpLmNvbS92MSJdLCJjbGllbnRfaWQiOiJhcHBfWDh6WTZ2VzJwUTl0UjNkRTduSzFqTDVnSCIsImV4cCI6MTc3NDQ1NTUyNSwiaHR0cHM6Ly9hcGkub3BlbmFpLmNvbS9hdXRoIjp7ImNoYXRncHRfYWNjb3VudF9pZCI6Ijg0YzNjZGM2LWFiZWQtNDlhNy1iY2RlLWZmMjQwMjE3NmZkYiIsImNoYXRncHRfYWNjb3VudF91c2VyX2lkIjoidXNlci00MXlCY3M0STZscEJBVEh5R2lTNG9wTUNfXzg0YzNjZGM2LWFiZWQtNDlhNy1iY2RlLWZmMjQwMjE3NmZkYiIsImNoYXRncHRfY29tcHV0ZV9yZXNpZGVuY3kiOiJub19jb25zdHJhaW50IiwiY2hhdGdwdF9wbGFuX3R5cGUiOiJmcmVlIiwiY2hhdGdwdF91c2VyX2lkIjoidXNlci00MXlCY3M0STZscEJBVEh5R2lTNG9wTUMiLCJ1c2VyX2lkIjoidXNlci00MXlCY3M0STZscEJBVEh5R2lTNG9wTUMifSwiaHR0cHM6Ly9hcGkub3BlbmFpLmNvbS9wcm9maWxlIjp7ImVtYWlsIjoiZ3VzLmxhcnNvbjJAZDQuemh1ZmFkYS5kZSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlfSwiaWF0IjoxNzczNTkxNTI1LCJpc3MiOiJodHRwczovL2F1dGgub3BlbmFpLmNvbSIsImp0aSI6IjYzZDgwY2M3LWI1NzEtNDk3Yi05ZmI2LTZiZTE2MDE1ZjI4MiIsIm5iZiI6MTc3MzU5MTUyNSwicHdkX2F1dGhfdGltZSI6MTc3MzU5MTUyNDA3NCwic2NwIjpbIm9wZW5pZCIsImVtYWlsIiwicHJvZmlsZSIsIm9mZmxpbmVfYWNjZXNzIiwibW9kZWwucmVxdWVzdCIsIm1vZGVsLnJlYWQiLCJvcmdhbml6YXRpb24ucmVhZCIsIm9yZ2FuaXphdGlvbi53cml0ZSJdLCJzZXNzaW9uX2lkIjoiYXV0aHNlc3NfWTFwQWh2VWRXQVVNb1pPVzN0OEllNjFKIiwic2wiOnRydWUsInN1YiI6ImF1dGgwfHhJcnpFSTlvV0xBUUtMMW4wR2tKenBETiJ9.f8tBrHuYqZtVpSY3cxf0NOrEgGHVZlhSQhz4_aMngNIq8_O1oY6ajyWoJpdqtf_m-luzRswZMgA-fKGiEbKu-LqqFiCHnNOFkK5ymdAoXFLsHWEX-BFS5wqTKJ6_nphrqLgVMAaA1mwuWQZ3PD2mCMJ_eErFhFPGlOCBR1TyDSMhJhvDMHB81sqJbxJBpkQNV3J1GDcvvUaNiQebAs4LNOhNaQfYTxJQqJZiGCnwjeHWql_aSSKv4y1vEXSLwH-GEqfjlpHYZqhYTHpdr_PzzOOIWq_X9ScedMOy699UYwyQa7IKcwCw6ZqaVbR_WjAdHunWi8yOl5C7JFuUA2xhrfMOQUgg86vv5oBw_OYTHzX51Dimh_SHhLaUCNC0-SPRZ-IiYz91MveiR_QCSHvU_ZXJO-FY8Xqa6NEdLZ8AbrOb81dxnV8DOABxolHVMtuxINPQuzHAEKxAyNGQkwgo7_O4TAVUycpv4b3LMIoSdCHy7F7q9Dh1UR8jTugM0Zqor6bD0XEwdFL5KxONZk-alHAo93IrDS9D8L0bjp0cMl7A1ZyjYjOMpw8Liq_b6V6uyPPJsw0DY1q-LtrQXN05W1La5bOX-J0yjtEnRrKtm5mGZ8cT2RnKYJJFFSckbMY2EvKuSEMKh0T5YoqPx-LZsXRViscbGfLBh8e2gzvXlq8"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove committed bearer token from test source

This test embeds a full bearer token directly in source control, which is a credential exposure risk and also leaks account-identifying claim data if the token is still valid (or present in history). Tests that require real credentials should read them from environment variables at runtime instead of committing secrets.

Useful? React with 👍 / 👎.

Comment on lines +444 to +445
needsIDToken := result["chatgpt_account_id"] == nil || result["plan_type"] == nil
if needsIDToken {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Parse ID token before populating subscription fields

The new needsIDToken gate only parses id_token when chatgpt_account_id or plan_type is missing, but chatgpt_subscription_active_start and chatgpt_subscription_active_until are also populated only inside that block. In cases where metadata already has account ID and attributes already has plan type, subscription dates are silently dropped even when id_token is present, which regresses /auth-files response completeness.

Useful? React with 👍 / 👎.

@GakkiNoOne
Copy link
Author

本次PR的目的主要是解决 不同来源的 chatgpt账号 导入的时候 怎么能兼容变成 codex的账号

  1. 很多不同来源的账号的rt对应的clientId不同 ,refresh的接口是需要clientid和rt对应的,否则无法刷新
  2. chatgpt普号刷新后 idtoken里其实是没有 chatgpt_account_id和plan_type的
  3. chatgpt账号的accountId是可能有多个的,比如普号一个accountid,team,plus都有分别的accountid

这3个问题的解决方案

  1. 导入的时候 json文件里直接支持传入clientid 和 chatgpt_account_id 和 plan_type 让用户自己控制即可
  2. 刷新的rt的时候 优先从用户传入的clientid,其次再从idtoken里解析,再其次用写死的
  3. accountid理论上是支持刷新的,但是懒的弄刷新覆盖的逻辑了(刷新覆盖的逻辑 可以看我写的测试文件里的逻辑,这个逻辑理论上 是需要在刷新rt后覆盖json配置文件里的accountid的,避免普号升级后变成team 或者 team账号到期了,导致accountid的变化,但是我也不知道这里是要执行一次 获取accountid的接口好 还是不好)
  4. 获取accountid的接口是有风控的,需要指纹请求

这个PR改动的地方主要几个

  1. 管理页面导入文件的接口需要的文件格式 新增 clientid chatgpt_accountid plan_type3个字段 支持用户自己定义,顺便解决 不同clientid 刷新rt的问题
  2. 前端获取accountid进行 额度刷新的时候,支持优先获取json中配置的accountid
  3. 获取accountid的接口openai是有单独的,这个接口我写到了测试文件里,是否要合并到 rt刷新的逻辑里,作者自己判断下

其他:

  1. 没考虑到的地方 作者再兼容下,我review的需要clientid、accountid 主要就是这些地方

Copy link
Collaborator

@luispater luispater left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary:
This is directionally fixing the right Codex refresh/account-id problems, but I do not think it is ready to merge yet. There are still correctness gaps in /auth-files, and the new test introduces both a security issue and an unstable test dependency.

Blocking findings:

  • internal/api/handlers/management/auth_files.go: the new needsIDToken gate means id_token is no longer parsed once chatgpt_account_id and plan_type are already present from metadata/attributes. That silently drops chatgpt_subscription_active_start and chatgpt_subscription_active_until, which were returned before.
  • internal/api/handlers/management/auth_files.go + internal/watcher/synthesizer/file.go: the PR description says users can import JSON with an explicit plan_type, but the implementation still only consumes auth.Attributes["plan_type"]. In the current code, that attribute is synthesized from id_token, not from top-level JSON metadata, so an imported plan_type still does not flow through.
  • internal/runtime/executor/codex_executor_account_id_test.go: this test checks in a bearer token and fixed proxy URL, and it runs a real network call during normal test execution. That should be removed or rewritten to read opt-in environment variables and skip by default.

Test plan:

  • Reviewed PR metadata, changed files, full diff, inline review comments, and CI status via gh.
  • Read the relevant local code paths in internal/api/handlers/management/auth_files.go, internal/watcher/synthesizer/file.go, internal/auth/codex/*, and related Codex auth record construction paths.
  • Did not run checkout-based validation or modify the working tree.
  • Observed CI: build passed. translator-path-guard was ignored per request.

Follow-up:

  • After the blocking issues are fixed, I would add small unit tests that cover:
    • metadata-provided client_id
    • metadata-provided account_id
    • metadata-provided plan_type
    • subscription fields still being returned when account ID and plan type are already populated

- extractCodexIDTokenClaims: restore unconditional id_token parsing so
  subscription date fields are always populated; override chatgpt_account_id
  and plan_type afterwards with explicit values from Metadata if present,
  covering both the post-refresh case and imported JSON files that carry
  these fields at the top level
- codex_executor_account_id_test: remove hardcoded bearer token and proxy
  URL; read from CODEX_ACCESS_TOKEN / CODEX_PROXY_URL env vars and skip
  when unset so the test suite remains hermetic in CI

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@GakkiNoOne
Copy link
Author

issue fixed

@GakkiNoOne
Copy link
Author

@tianyicui Can you review the code again?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants