fix: accept RT-exchanged tokens missing chatgpt_account_id claim#674
fix: accept RT-exchanged tokens missing chatgpt_account_id claim#674xiaoliu10 wants to merge 4 commits into
Conversation
55bf4e7 to
1c17a34
Compare
icebear0828
left a comment
There was a problem hiding this comment.
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. addAccount 对 accountId = null 的账户去重失效
当 RT 兑换返回无 chatgpt_account_id 的 token 时,addAccount 去重键退化为 token 字符串本身。token 被 refresh 后内容改变,下次 addAccount 会创建重复条目,两个 entry 指向同一个 OpenAI 账户。
此 PR 让这类账户进入系统但未处理后续 token 刷新的去重,需要一并修复或明确标注 // KNOWN LIMITATION + 开 issue 追踪。
🟡 Medium
canAcceptRtExchangeToken 与 validateManualToken 大量逻辑重复
建议在 chatgpt-oauth.ts 提取 validateTokenStructure(token) 基础函数,两处共享,后续改动只需修改一处:
export function validateTokenStructure(token: string): { valid: boolean; error?: string } {
// 只做:非空 + 可解析 + 未过期
}
// validateManualToken 在此基础上加 accountId 检查rt as string 类型断言(L182/185/189/212)
项目规范禁止 as any,as 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。
|
感谢 PR!代码逻辑已经很完整了—— 唯一需要补充的:CHANGELOG 可以在 补好后可以 approve ✅ |
icebear0828
left a comment
There was a problem hiding this comment.
Review
感谢修复!方向正确,但有几个问题需要在合并前解决:
🔴 与 #675 实现冲突(必须解决)
两个 PR 都修改了 account-import.ts 里的 canAcceptRtExchangeToken 和 accounts-import-export.test.ts,且实现逻辑不同,无法同时合并。需要与 #675 作者协调,统一采用一种方式。
🔴 错误字符串匹配脆弱
return (
validationError === "Token missing chatgpt_account_id claim" &&
!isTokenExpired(accessToken)
);这段逻辑强依赖 validateToken 返回的错误消息文字精确匹配。任何拼写调整都会静默失效(绕过失效,token 被拒绝,用户无感知)。建议改为:
- 引入错误码枚举(如
ValidationErrorCode.MISSING_ACCOUNT_ID),或 - 像 #675 那样直接 bypass
validateToken,重新 decode token 做最小校验(只检查过期)。
🟡 缺少 E2E 验证
涉及 RT exchange 这类外部服务交互,按项目规范 mock 绿 ≠ 完成,需要真实服务连续调用 ≥3 次全部成功。
🟢 其他做得好的地方
createValidJwt用hasOwnProperty判断是否显式传accountId: undefined是正确的extractCodexTokenMetadata的 id_token fallback 逻辑清晰,测试覆盖到位rt空值提前 return 修掉了一个隐性 bug
…g match Replace the fragile canAcceptRtExchangeToken() check (which matched on the exact "Token missing chatgpt_account_id claim" error string) with a dedicated validateRtExchangeToken() that only verifies the token is decodable and not expired. RT-exchanged tokens legitimately omit chatgpt_account_id, so the strict validateToken path should not gate them at all. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
补充:新增了一个重构 commit 原先的实现用 改为:RT 交换路径直接跳过 行为不变,相关单测(account-import / accounts-import-export / jwt-utils)全部通过。 |
Summary
修复 Refresh Token 换取的 access token 缺少
chatgpt_account_idclaim 时导入失败的问题。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_tokensrc/auth/account-registry.ts:addAccount()新增可选metadata参数,当extractChatGptAccountId返回 null 时回退到metadata.accountId,email/planType 同理src/auth/account-pool.ts:透传metadata参数到 registrysrc/auth/oauth-pkce.ts:refreshAccessToken返回类型增加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补齐extractCodexTokenMetadatamockTest Plan
npm test— 251 files, 2452 passed, 1 skippednpx tsc --noEmit— zero errors