opencodex OAuth 刷新单飞(singleflight)与 Kiro CLI SQLite 令牌重载机制详解

【免费下载链接】opencodex

Universal provider proxy for OpenAI Codex & Claude Code — use any LLM (Claude, Gemini, Grok, DeepSeek, Ollama…) with Codex CLI, App, SDK, and Claude Code

项目地址: https://gitcode.com/gh_mirrors/ope/opencodex
点击查看 免费下载

本篇技术指南围绕 opencodex 在 Kiro(CodeWhisperer)网关对齐工程中的 Phase 90(P0-3)OAuth 刷新加固展开:通过按 provider 的刷新单飞(singleflight)消除并发临近过期请求导致的重复刷新与凭证竞写,并在刷新前后对本地 Kiro CLI SQLite 令牌缓存做重载以优先采用更新的外部会话。读完本文,你将掌握 opencodex OAuth 凭据解析链(getValidAccessToken → resolveAccessSnapshotForAccount → refreshAndPersistAccessToken)的完整调用结构、REFRESH_SKEW_MS 等关键常量与容量/陈旧淘汰策略,以及如何在隔离环境中以 bun:sqlite 种子夹具复现并验证这些竞态场景。

一、安全边界:OAuth 刷新为何需要专项加固

本阶段设计文档 90_phase_oauth_singleflight_reload.md 首先界定了安全边界(trust boundary):

本地凭据存储(OPENCODEX_HOME/auth.json)+ 导入的 Kiro CLI SQLite 令牌缓存 → 出站 Kiro 运行时 Authorization 头。

该链路上被保护的对象是 OAuth access/refresh 令牌与正确的持久化凭据状态,而主要故障模式(main failure mode)有三类:

  1. 并发临近过期请求用同一个旧 refresh token 刷新——多个请求同时发现 access token 即将过期,各自独立发起刷新,上游可能因 refresh token 复用被吊销(refresh-token reuse revocation),或刷新出的新令牌互相覆盖;
  2. 对 auth.json 的竞态写入——同一 provider 的多路刷新各自 saveCredential,后写覆盖先写,丢失更新;
  3. 错过更新的 Kiro CLI 令牌——外部 kiro-cli 进程已经刷新出更新的令牌,而 opencodex 仍基于自己存储的旧快照走桌面刷新端点,白白产生一次网络往返且可能引入过期路由元数据。

REFRESH_SKEW_MS(预过期倾斜窗口)是本链路的核心标尺,在 src/oauth/index.ts 中定义为 60_000 毫秒:expires > Date.now() + REFRESH_SKEW_MS 即视为有效,其余为临近过期/过期,需要进入刷新路径。

二、范围界定:本次加固做什么、不做什么

设计文档明确了"在 opencodex 当前所有权内正确实现的加固",三项必做:

项内容源码落点
1通用 per-provider 刷新单飞:围绕 getValidAccessToken 的刷新工作做去重tokenRefreshes Map + resolveAccessSnapshotForAccount(src/oauth/index.ts)
2Kiro 专属刷新前 SQLite 重载:若已安装的 Kiro CLI 有新鲜令牌,持久化/使用它,而不是打桌面刷新端点readKiroCliSqliteCredential(src/oauth/kiro-credentials.ts)
3Kiro 专属刷新失败后恢复:若外部 Kiro CLI 在我们失败期间/之后已刷新,则导入它完成恢复matchingRotatedKiroCliCredential(src/oauth/kiro.ts)

明确不在范围内(out of scope)的包括:

  • AWS SSO OIDC / device-registration 刷新路径——refreshAwsSsoOidcToken(src/oauth/kiro.ts)属于另一套 client-pair 换发机制,不在本次单飞改造内;
  • 多账户故障转移(multi-account failover)——在 00_plan.md 的差距矩阵中已说明:opencodex 在更上层拥有自己的 Codex 多账户池(codexAccounts),Kiro 单凭据导入是既定设计,非适配器差距;
  • 改变登录时的 auth-source 优先级——本次只修"已有 opencodex Kiro 凭据后的陈旧刷新竞态",绝不削弱登录导入的优先级次序。

三、核心机制一:按 provider 的刷新单飞

设计文档规划了模块级 const tokenRefreshes = new Map<string, Promise<string>>()。当前源码中该结构已演化为携带飞行元数据的 OAuthTokenRefreshFlight(src/oauth/index.ts):

const MAX_OAUTH_TOKEN_REFRESH_FLIGHTS = 32;
const OAUTH_TOKEN_REFRESH_FLIGHT_STALE_MS = 120_000;
interface OAuthRefreshFlightEvidence { flightId: string; dispatched: boolean }
interface OAuthTokenRefreshFlight extends OAuthRefreshFlightEvidence {
  promise: Promise<OAuthAccessSnapshot>;
  startedAt: number;
  abort: AbortController;
}
const tokenRefreshes = new Map<string, OAuthTokenRefreshFlight>();

Map 的键是 ${provider}\u0000${accountId}——按 (provider, account) 粒度去重,这正是多账户(multiauth)场景下"守护刷新后台账户时绝不切换活跃账户"的保证基础。getValidAccessToken(src/oauth/index.ts)的完整决策流程如下:

resolveAccessSnapshotForAccount(provider, accountId)
  ├─ 读取账户凭据(getAccountCredentialWithStatus)
  ├─ 若 expires > now + REFRESH_SKEW_MS → 直接返回快照(快路径,不触碰 SQLite/fetch)
  ├─ 若已有进行中的 flight 且未超时(startedAt ≤ 120s)→ 返回 existing.promise(加入同一次刷新)
  ├─ 若已有 flight 但已陈旧 → abort 旧 flight(OAuthTokenRefreshStaleError),删除后重建
  ├─ 若 flights 数量已达 32 → 抛 OAuthTokenRefreshBusyError(retryable)
  └─ 否则新建 flight,在 finally 中仅当 map 中仍是自己时 delete

三个关键设计点值得展开:

  • 快路径优先:凭据仍有效时不进入任何刷新/重载逻辑,既不读 SQLite 也不发 fetch,保证"已有有效凭据保持快速路径"这一验收标准;
  • 陈旧飞行替换:OAUTH_TOKEN_REFRESH_FLIGHT_STALE_MS = 120_000 界定一次刷新飞行的存活上限,超时的 owner 会被 OAuthTokenRefreshStaleError 中止并以新 flight 替换,配合 OAuthTokenRefreshBusyError(retryable = true)形成有界的并发控制;
  • finally 幂等清理:if (tokenRefreshes.get(key) === flight) tokenRefreshes.delete(key)——单飞 Map 在成功/失败后都必须清空,避免内存泄漏与后续请求错误地加入一个已终结的 promise。

forceRefreshOAuthAccessSnapshot(src/oauth/index.ts)定义了强制刷新 provider 白名单 FORCE_REFRESH_PROVIDERS = { xai, github-copilot, kiro, google-antigravity, orcarouter-oauth }——上游 401 重放路径可携带被拒快照的 generation 进入同一单飞解析流程。

四、核心机制二:Kiro 刷新前的 SQLite 重载

设计文档为 refreshAndPersistAccessToken(provider, def, cred) 规划的 Kiro 分支逻辑为:

  1. provider === "kiro" 时先调用 readKiroCliSqlite();
  2. 若导入的令牌有效期超过 REFRESH_SKEW_MS,则 saveCredential(provider, imported) 并直接返回 imported.access,完全跳过桌面刷新端点;
  3. 否则才调用 def.refresh(cred.refresh),持久化并返回;
  4. 若 refresh 抛错且 provider 为 Kiro,再次重读 SQLite,若此时已有有效令牌则采用之,否则原样抛出。

当前源码中该分派函数为 refreshAndPersistAccessToken,按 provider 分发到带锁刷新器:

if (provider === "xai") return refreshXaiAccountWithLock(...);
if (provider === "anthropic") return refreshAnthropicAccountWithLock(...);
return refreshGenericAccountWithLock(provider, accountId, def, cred, { signal });

每个带锁刷新器都通过 createOAuthRefreshIntentLock(provider, accountId).acquire() 取得意图锁,并基于 captureConfigGeneration() 的快照执行"读取→刷新→mergeAccountCredential(compare-and-swap)→记录审计事件"的原子序列;merged()(src/oauth/index.ts)在合并时保留旧凭据中缺失的 projectId / apiBaseUrl / email / accountId / kiro 字段,防止轮换后路由元数据丢失。

而"读取 Kiro CLI SQLite"的真实导出函数名为 readKiroCliSqliteCredential()(设计文档写作 readKiroCliSqlite,实际实现见 src/oauth/kiro-credentials.ts),其底层 inspectKiroCliSqliteSources(同文件 L444-L447)只读 SQLite 缓存(不触碰 JSON 凭据文件),按平台解析本机会话库路径(resolveKiroCliNativeSessionEntries,L156-L170):

平台Kiro CLI 会话库路径
Windows%LOCALAPPDATA%\Kiro-Cli\data.sqlite3(LOCALAPPDATA 缺失时回退 %USERPROFILE%\AppData\Local,再回退平台原生 home)
macOS~/Library/Application Support/kiro-cli/data.sqlite3
Linux~/.local/share/kiro-cli/data.sqlite3

此外还支持 KIROCLI_DB_PATH / KIRO_CLI_DB_FILE 环境变量显式指定数据库,以及 KIROCLI_TOKEN_KEY 在多令牌库中选定键;令牌键按 kirocli:odic:token、kirocli:oidc:token、kirocli:social:token、codewhisperer:odic:token 的顺序择优,并为每个位置产出结构化的 KiroImportDiagnostic(missing / unreadable / schema_mismatch / token_ambiguous / token_found 等)供诊断。

五、核心机制三:刷新失败后的 SQLite 恢复

当桌面/SSO 刷新端点失败而外部 Kiro CLI 恰好完成了一次自己的刷新时,单飞内的恢复逻辑在 src/oauth/kiro.ts 的 matchingRotatedKiroCliCredential 中落地:

function matchingRotatedKiroCliCredential(refresh, credential?) {
  const storedIdentities = [credential?.kiro?.profileArn, credential?.accountId].filter(Boolean);
  if (storedIdentities.length === 0 || new Set(storedIdentities).size !== 1) return undefined;
  const local = readKiroCliSqliteCredential();
  if (!local?.profileArn || storedIdentities.some(id => id !== local.profileArn)) return undefined;
  if (!local.refresh || local.refresh === refresh) return undefined; // 不是新轮换
  return local;
}

其安全性要点:只有本机 SQLite 中持有与存储账户相同的 profileArn、且 refresh token 与旧值不同时才判定为"外部已轮换",从而避免借用无关账户的会话。随后 metadataForRotatedKiroCliCredential(L633-L655)在保留存储账户路由元数据(ssoRegion 等)的前提下合并本地新元数据,仅在 aws_sso_oidc 类型下补入本地 client pair。刷新请求本身带 30 秒超时(kiroRefreshSignal,L556-L559),并把失败响应映射为携带 httpStatus 与白名单 oauthError 的 KiroTokenRefreshError(L59),供上层 terminal() 判断"凭据是否已死"。

值得说明:刷新前重载分支在 refreshGenericAccountWithLock 中通过 readImportedKiroCredential()/detectGrokCliToken() 等先例已有"本地优先"思想,而 Kiro 专属的"刷新前重载 + 失败后恢复"双保险正是 Phase 90 引入的差异点。

六、登录侧不变的优先级契约

设计文档特别强调:Kiro 重载路径绝不削弱登录时的凭据优先级。查看 loginKiro(src/oauth/kiro.ts)可见完整导入优先级为:

  1. kiro-cli SQLite 导入(readImportedKiroCredential)→ 2. KIRO_ACCESS_TOKEN 环境变量 → 3. 手动粘贴(仅 CLI)→ 4. forceLogin 时先对 CLI 会话做快照备份(inspectKiroCliSessionSnapshot)、再 logout/login、最后回读新令牌并登记恢复事务。

Phase 90 的刷新重载只发生在"opencodex 已持有 Kiro 凭据之后的陈旧刷新竞态"场景,是刷新路径的优化而非登录路径的改写,因此不改变上述导入顺序,也不触发 kiro-cli 的登出/登入副作用。

七、测试与验证:如何复现这些竞态

设计文档规划了新增 tests/oauth-refresh.test.ts,四个核心用例与验收一一对应:

用例断言目标
并发过期的 Kiro 调用共享同一次刷新请求,两者返回同一个 access token(单飞去重)
SQLite 中有新鲜 Kiro 令牌刷新端点被调用前即完成导入(前置重载)
刷新失败但 SQLite 已有有效令牌恢复成功(失败后重载)
存储凭据未过期直接返回、不触发刷新(快路径)

关键测试基建(与既有 tests/providers/kiro/kiro-oauth.test.ts 同构):

  • 环境隔离:mkdtempSync 生成独立临时目录,同时改写 HOME、LOCALAPPDATA、USERPROFILE(Windows 原生解析器优先于 HOME,见该文件 L37-L54),afterEach 逐一还原并清理;
  • 种子夹具:seedKiroCliDb(L104-L121)用 bun:sqlite 按平台创建 auth_kv 表并写入 kirocli:social:token 等令牌键,可选 device-registration 与 state 表中的 profile 元数据——与 kiro-oauth 测试共用同一套 schema;
  • auth.json 播种:通过 saveCredential 写入本地凭据存储;
  • 网络打桩:mock globalThis.fetch 以断言"刷新端点是否被调用、调用几次"。

验证命令与项目 CI 纪律(源自 00_plan.md 的 PABCD 流程):

bun x tsc --noEmit
bun test tests/oauth-refresh.test.ts tests/providers/kiro/kiro-oauth.test.ts

设计文档还给出明确的验收标准(Acceptance):

  1. 任何路径不得记录令牌明文(No tokens are logged);
  2. 单飞 Map 在成功/失败后必须清空(map always clears);
  3. 已有有效凭据保持快速路径,不触碰 SQLite/fetch(existing valid credentials remain fast-path);
  4. Kiro 重载路径只在已有 opencodex Kiro 凭据后生效,绝不在登录时削弱凭据优先级。

对应的原子提交信息为:fix(oauth): singleflight refresh and reload Kiro CLI tokens。

八、总结

Phase 90 的本质是把"刷新"从各自为战的惰性操作收敛为有界、去重、可恢复的受控流程:tokenRefreshes 单飞 Map 在 (provider, account) 粒度上消除重复刷新与 auth.json 竞写;REFRESH_SKEW_MS、MAX_OAUTH_TOKEN_REFRESH_FLIGHTS、OAUTH_TOKEN_REFRESH_FLIGHT_STALE_MS 三个常量分别界定有效窗口、并发上限与陈旧淘汰;Kiro 刷新前/后的 SQLite 重载则让 opencodex 与外部 kiro-cli 会话始终保持一致,优先采用更新鲜的本地令牌、在失败时借道恢复。整套机制与登录导入优先级严格解耦,并有隔离化种子夹具与 bun test 用例逐条锁定验收标准——这对任何"代理端 OAuth 凭据管理"类系统都是可直接借鉴的加固范本。进一步深入可阅读 src/oauth/store.ts(凭据存储与意图锁)与 143_kiro-gateway-parity 系列其余阶段文档。

【免费下载链接】opencodex

Universal provider proxy for OpenAI Codex & Claude Code — use any LLM (Claude, Gemini, Grok, DeepSeek, Ollama…) with Codex CLI, App, SDK, and Claude Code

项目地址: https://gitcode.com/gh_mirrors/ope/opencodex
点击查看 免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值