Skip to content

fix: accept RT-exchanged tokens missing chatgpt_account_id claim#674

Open
xiaoliu10 wants to merge 1 commit into
icebear0828:devfrom
xiaoliu10:fix/rt-import-missing-account-id
Open

fix: accept RT-exchanged tokens missing chatgpt_account_id claim#674
xiaoliu10 wants to merge 1 commit into
icebear0828:devfrom
xiaoliu10:fix/rt-import-missing-account-id

Conversation

@xiaoliu10

Copy link
Copy Markdown

Summary

修复 Refresh Token 换取的 access token 缺少 chatgpt_account_id claim 时导入失败的问题。OpenAI RT exchange 返回的 access token 有时不携带 chatgpt_account_id,导致 validateManualToken 拒绝该 token,RT-only 导入无法完成。

Changes

  • src/auth/jwt-utils.ts:新增 extractCodexTokenMetadata() 从 access_token + id_token 中提取 accountId / userId / email / planType,兼容 claim 在任一 token 中的情况
  • src/services/account-import.ts:RT exchange 后用 extractCodexTokenMetadata 提取元数据传给 pool;新增 canAcceptRtExchangeToken() 允许缺少 chatgpt_account_id 但未过期的 RT 换取 token 通过验证;ImportDeps.refreshToken 返回类型增加 id_token
  • src/auth/account-registry.tsaddAccount() 新增可选 metadata 参数,当 extractChatGptAccountId 返回 null 时回退到 metadata.accountId,email/planType 同理
  • src/auth/account-pool.ts:透传 metadata 参数到 registry
  • src/auth/oauth-pkce.tsrefreshAccessToken 返回类型增加 id_token 字段
  • CHANGELOG.md[Unreleased] → Fixed 补充条目
  • 测试jwt-utils.test.ts 补充 extractCodexTokenMetadata 用例;account-import.test.ts 补充 RT exchange 缺 claim 场景;accounts-import-export.test.ts / rt-reuse-race.test.ts 补齐 extractCodexTokenMetadata mock

Test Plan

  • npm test — 251 files, 2452 passed, 1 skipped
  • npx tsc --noEmit — zero errors

Copilot AI review requested due to automatic review settings June 10, 2026 07:58

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@xiaoliu10 xiaoliu10 force-pushed the fix/rt-import-missing-account-id branch from 55bf4e7 to 1c17a34 Compare June 10, 2026 08:01

@icebear0828 icebear0828 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Review

总体评价

修复方向正确,validateManualToken 对无 chatgpt_account_id 的 JWT 拒绝确实是兼容性问题。但存在两个 blocking 问题。


🔴 High

1. 缺少直接覆盖 bug 场景的测试(违反 TDD 规范)

所有测试用例均使用带 accountId 的 JWT,修复前也能通过。需补覆盖核心场景的用例:

it("accepts RT-exchanged token with no chatgpt_account_id claim", async () => {
  const jwtWithoutAccountId = createValidJwt({ accountId: undefined });
  const svc = new AccountImportService(pool, scheduler, makeDeps({
    refreshToken: async () => ({ access_token: jwtWithoutAccountId, refresh_token: "new_rt" }),
  }));
  const result = await svc.importMany([{ refreshToken: "some_rt" }]);
  expect(result.added).toBe(1);
  expect(result.failed).toBe(0); // 老代码此处 failed=1
});

2. addAccountaccountId = null 的账户去重失效

当 RT 兑换返回无 chatgpt_account_id 的 token 时,addAccount 去重键退化为 token 字符串本身。token 被 refresh 后内容改变,下次 addAccount 会创建重复条目,两个 entry 指向同一个 OpenAI 账户。

此 PR 让这类账户进入系统但未处理后续 token 刷新的去重,需要一并修复或明确标注 // KNOWN LIMITATION + 开 issue 追踪。


🟡 Medium

canAcceptRtExchangeTokenvalidateManualToken 大量逻辑重复

建议在 chatgpt-oauth.ts 提取 validateTokenStructure(token) 基础函数,两处共享,后续改动只需修改一处:

export function validateTokenStructure(token: string): { valid: boolean; error?: string } {
  // 只做:非空 + 可解析 + 未过期
}
// validateManualToken 在此基础上加 accountId 检查

rt as string 类型断言(L182/185/189/212)

项目规范禁止 as anyas string 同样是类型欺骗。在入口加显式 null guard:

if (!rt) return { ok: false, error: "Refresh token is required", kind: "validation" };

🟢 Low

  • 返回 token 前应统一 trim()token: tokens.access_token.trim()
  • CHANGELOG.md [Unreleased] 缺本次修复条目
  • L194 错误消息的 kind: "validation" 语义不够精确,可考虑 kind: "exchange_invalid"

结论

Request changes — 两个 High 问题(补 regression test + 修 null-accountId 去重)解决后可 approve。

@icebear0828

Copy link
Copy Markdown
Owner

感谢 PR!代码逻辑已经很完整了——id_token 回填 + 无 id_token 时允许 accountId=null 的降级策略,以及两个 regression test 都到位。

唯一需要补充的:CHANGELOG [Unreleased] 缺少本次修复的记录。项目约定每个功能/修复都要更新。

可以在 CHANGELOG.md## [Unreleased] 下加一个 ### Fixed 块,内容大概:

### Fixed

- RT-only 导入兼容性:Refresh Token 兑换的 access token 缺少 `chatgpt_account_id` 时,
  优先从 `id_token` 回填账号元数据;若仍缺失但 token 未过期也允许导入继续(accountId 置 null)。
  新增 `extractCodexTokenMetadata()` 统一提取双 token 元数据。(#674)
- OAuth refresh 请求新增 `scope` 参数确保服务端返回 `id_token`。(#674)

补好后可以 approve ✅

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.

3 participants