feat(openai-reauth): 新增 OpenAI OAuth 重新授权独立流程#292
Conversation
- flows/openai-reauth/: 独立 flow definition + workflow + mail-rules - background/oauth-client.js: PKCE 生成 + URL 构造 + Token 交换 (fetchImpl 注入) - background/cookie-cleanup.js: 清 OpenAI/ChatGPT cookies (复用 step1 模式) - background/steps/: 4 个 step executor (prepare → submit-email → fetch-code → capture-callback) - 注册到 flows/index.js + manifest.json + background.js (importScripts/mailRuleRegistry/stepExecutorsByKey) - 复用 oauth-login content handler、pollFlowVerificationCode、FILL_CODE - 测试: oauth-client (14) / mail-rules (10) / cookie-cleanup (6) / capture-callback (8) = 38 个新测试 - 全量回归 1307/1307 通过 注: sidepanel UI 留待下一期 (需要 message-router 与 sidepanel 主 UI 配合升级)
- .omc/: oh-my-claudecode 状态目录 - /tmp/: 本地实验脚本与含真实 token 的账号 JSON 临时存放区,不入库
- flows/openai-reauth/index.js: settingsGroups 加 reauth-input + baseGroups 追加 - flows/openai-reauth/reauth-account-validator.js: 独立校验函数模块(新建,可单测) - sidepanel/sidepanel.html: 新增 row-reauth-account-json 和 row-reauth-result(含 textarea/pre/复制按钮) - sidepanel/sidepanel.css: textarea / readonly pre 样式 - sidepanel/sidepanel.js: DOM 常量、ensurePendingReauthAccount、renderReauthResultAccount、step 按钮 payload 注入、复制按钮绑定 - background/message-router.js: EXECUTE_NODE case 内提取 reauthInputAccount 写入 state - tests/openai-reauth-oauth-client.test.js: 用 node:crypto webcrypto 替代 globalThis.crypto,修复 Node 18 sandbox - tests/openai-reauth-validate-account-json.test.js: 13 个校验函数边界测试(新建) reauth 测试 50/50 通过;全量回归 1316/1320,4 个失败是 master 原本的 kiro Node 18 sandbox 问题,与 P2 无关
1. row 的 hidden 属性会被 [hidden] !important CSS 锁死,改用 inline style=display:none - applyFlowSettingsGroupVisibility 用 style.display 切换,无法盖过 [hidden] !important 2. NODE_STATUS_CHANGED handler 完成时没刷新 reauthResultAccount - step 4 跑完后 sidepanel 不会渲染新 JSON 到结果区 review 后端到端集成验证全通,reauth 50/50 测试全绿
- reauth-account-validator.js: 重写为 parseAccountsFromJson + buildResolvedAccount 支持单账号 / accounts 数组 / sub2api 整文件三种输入形态 不再要求 JSON 含 mailProvider 字段(改由 UI 注入) - sidepanel.html: 加邮箱来源下拉(7 个 provider)+ 多账号下拉选择器 - sidepanel.js: 改 ensurePendingReauthAccount 为多账号选择 + provider 注入 account 选择器仅在 accounts.length > 1 时显示 - tests: 重写 15 个用例覆盖整文件 / 单账号 / 多账号 / 边界 reauth 测试 52/52 通过
复盘:截图显示 sidepanel 已识别账号但 step 1 仍报"缺少待重新授权的账号 JSON" 根因:用户点的是"自动"按钮走 AUTO_RUN 消息,本仙女只在 step 按钮 (EXECUTE_NODE) 路径上注入了 reauthInputAccount 修复: - sidepanel.js: AUTO_RUN 启动前若 activeFlowId === 'openai-reauth' 调 ensurePendingReauthAccount() 解析当前选中账号 + provider 注入 payload 校验失败时恢复按钮状态并中止 - background/message-router.js: AUTO_RUN case 内提取 reauthInputAccount 写入 session state(与 EXECUTE_NODE case 同步行为)
师兄反馈三件事:
1. JSON 输入改为文件选择器(后续批量也是导入文件)
2. 不需要邮箱服务区(reauth 自己有邮箱来源下拉,凭证仍在 OpenAI flow 配)
3. 不需要来源选择器和账户密码 row
实现:
- flows/openai-reauth/index.js: services 改 [] 不再自动加 service-account/email
- sidepanel.html: textarea 替换为 input[type=file] + 校验按钮删除(选文件即触发)
- sidepanel.js:
* inputReauthAccountFile change handler: FileReader 读 → 缓存 lastReauthFileText → 解析
* lastReauthFileText 缓存供 step 按钮 / AUTO_RUN 复用
* applyOpenAiReauthRowVisibility: 切到 reauth 时隐藏 row-source-selector 和 row-custom-password
(用 dataset.reauthHidden 标记,切回其他 flow 时恢复)
reauth 测试 52/52 通过
- 复用注册流程 step9 的多策略点击编排(content / debugger)+ 多轮重试 + 刷新降级 - capture-reauth-callback 新增 drivePrimaryContinueClick 并行任务,与 localhost 回调监听并行运行 - 守护 consentClickEnabled:依赖未注入时优雅降级为原"干等回调"行为 - background.js 装配处注入 16 个 deps(10 函数 + 4 常量 + 2 工具),全部复用 step9 已有实现 - 修复 codex_cli_simplified_flow=true 并不会让 OAuth 跳过同意页的假设破产 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- REAUTH_HIDDEN_ROW_IDS 扩展隐藏 row-mail-provider / row-email-generator / row-auto-run-controls - 新增 syncReauthMailProviderToGlobal:reauth 邮箱来源选择同步到全局 mailProvider,复用 provider 配置卡片可见性 - 拆分 persist 参数:用户主动 change 时 persist=true 写 storage,UI 刷新调用时 persist=false 仅同步显示 - 修复 applyOpenAiReauthRowVisibility → syncReauthMailProviderToGlobal → saveSettings → STATE_CHANGED 广播回环导致的 SAVE_SETTING 死循环刷屏 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- fetch-reauth-code 包装 pollFlowVerificationCode 为 4 轮 poll+resend 循环(最多 3 次主动重发) - 失败后通过 sendToContentScriptResilient 复用 openai-auth 的 RESEND_VERIFICATION_CODE handler - 2925 单轮 poll attempts 从默认 15 缩到 6(90s),让 resend 在 ~1.5 分钟即可介入 - 错误处理:stop 信号秒抛、resend 失败不中断主轮询、用完所有轮 throw 最终 error - background.js 注入 sleepWithStop 让 cooldown 等待可被 stop 中断 - 新增 tests/openai-reauth-fetch-code.test.js 11 个用例,覆盖 skip/首轮成功/二轮 resend/全失败/stop/payload overrides/FILL_CODE 错误 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- 新建 flows/openai-reauth/background/batch-runner.js,按队列依次跑每个账号 step1→step4 - 单账号失败默认自动跳过(skipOnFailure=true),累计到 failed 数组不中断 - 维护 reauthBatchRunning / reauthBatchProgress / reauthBatchResult 三个 state 字段 - mergeBatchResultsIntoFile 支持 sub2api 整文件 / accounts 数组 / 顶层数组 / 单账号 4 种 schema - 失败账号保留原 entry,成功账号 merge 新 token 字段(保留 priority / concurrency 等 metadata) - background.js 加载 batch-runner.js,通过 getReauthBatchRunner getter 解决与 messageRouter 的依赖时序 - message-router 新增 START_REAUTH_BATCH case,fire-and-forget 后台执行,进度通过 setState 广播 - flows/openai-reauth/index.js settingsGroups.reauth-input.rowIds 增加 row-reauth-batch-progress - manifest.json 新增 downloads 权限,为 sidepanel 一键下载整文件 JSON 做准备 - STOP 复用现有 STOP_FLOW + throwIfStopped 路径,batch-runner 内部 stop 信号秒抛 + 写 aborted=true Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- sidepanel.html 加批量模式 toggle(>=2 账号时可见),开启后隐藏单账号下拉 - 新增 row-reauth-batch-progress 行,实时显示「[当前/总数] 邮箱 状态」与 summary - 加「开始批量授权」「停止」「下载文件」三个按钮,按批量模式动态显示 - sidepanel.js 新增 applyReauthBatchUiVisibility / applyReauthBatchProgress / applyReauthBatchResult - buildReauthBatchAccounts 把 pendingReauthAccounts 注入 mailProvider 后构造 batch 队列 - handleReauthBatchStart 发送 START_REAUTH_BATCH 消息(fire-and-forget) - handleReauthBatchStop 复用 STOP_FLOW 消息 - handleReauthBatchDownload 优先用 chrome.downloads.download 弹保存对话框,失败 fallback 到剪贴板 - 文件名格式:sub2api-reauth-YYYYMMDD-HHmmss.json - GET_STATE / NODE_STATUS_CHANGED 路径都调用 applyReauthBatchProgress / Result,保持 UI 与 storage 同步 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…nelModeUI 测试 - tests/openai-reauth-batch-runner.test.js 覆盖 extractAccountEmail / mergeBatchResultsIntoFile 纯函数 - 覆盖 executeReauthBatch 主流程:顺序 3 账号全成功 / 中间失败 skip 继续 / skipOnFailure=false 中断 - 覆盖 stop 信号立即终止并写 aborted=true & stopReason=user_stop - 覆盖 progress currentStatus 流转、runSingleAccount 注入 mailProvider、每账号 reset state - 覆盖 interAccountDelayMs > 0 时账号间 sleep(最后一个不 sleep) - 修复 sidepanel-flow-source-registry.test.js 在 sandbox 中漏 mock applyOpenAiReauthRowVisibility - 同步 assert 链增加 reauth-visibility 调用顺序检查 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- 拆分 reauth-input 节点 rowIds,引入 mode-picker / provider-picker / batch-actions 三个新行 - 重组 reauth UI 容器:账号文件 / 处理模式 / 邮箱来源 / 批量操作 / 进度 / 结果分区 - 新增 reauth 专属样式:status-chip / mode-zone / progress-bar / result-summary Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- background.setState 写入 chrome.storage.session 后广播 STATE_PATCH,让 sidepanel 即时同步增量 - sidepanel onMessage 新增 STATE_PATCH 处理,按字段 dispatch 到 applyReauthBatchProgress / applyReauthBatchResult - 修复 batch 完成时下载按钮卡 disabled / summary 卡「等待…」/ 进度 badge 卡「处理中」的 race condition - 移除 reauth 流程 UI 残留 emoji(✅ ❌ ⚠) - 同步重组后的 reauth DOM 引用与批量模式/邮箱来源/批量操作 handler Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- S1: MAIL_PROVIDER_OPTIONS 双端单一数据源,sidepanel 动态渲染 - S2: fetch-reauth-code totalRounds 计算逻辑注释 - S3: openai-reauth 版本公告草稿 - S5: .gitattributes 强制 LF 行尾
…H 日志 / consentClick 日志 / CLIENT_ID 可注入 - batch-runner 改用 validator 统一 extractAccountEmail,保留 fallback - STATE_PATCH 广播 catch 区分「无接收方」与真实错误 - capture-reauth-callback 在 deps 校验处输出 consentClick 能力日志 - oauth-client 的 buildAuthorizeUrl / exchangeAuthorizationCode / buildUpdatedAccount 接受可选 clientId 参数
检测调用从 waitForStep8Ready(30s超时) 之后移到之前,新增 tab 激活后立即预检。
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR introduces a new "OpenAI 重新授权" (reauth) flow that allows users to re-authorize OpenAI accounts whose refresh_token has been revoked. The flow runs a full OAuth PKCE handshake, captures the localhost callback, and updates account credentials. It supports single-account and batch modes with downloadable JSON output.
Changes:
- New
flows/openai-reauth/module with 4 steps (prepare → submit email → fetch code → capture callback), OAuth client, cookie cleanup, batch runner, mail rules, and account validator - Sidepanel UI for file upload, mode/provider selection, progress display, and result download; new
STATE_PATCHbroadcast for incremental UI updates - Manifest gains
downloadspermission and registers the new flow scripts; message router addsSTART_REAUTH_BATCHhandler
Reviewed changes
Copilot reviewed 29 out of 30 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| flows/openai-reauth/index.js | Flow definition (id, sources, nodes, settings groups) |
| flows/openai-reauth/workflow.js | Static step variant definitions |
| flows/openai-reauth/reauth-account-validator.js | JSON parsing + email/provider validation |
| flows/openai-reauth/mail-rules.js | Mail polling rule builder for reauth code |
| flows/openai-reauth/background/oauth-client.js | PKCE / authorize URL / token exchange / account update |
| flows/openai-reauth/background/cookie-cleanup.js | Clear OpenAI cookies across stores |
| flows/openai-reauth/background/batch-runner.js | Sequentially run reauth for multiple accounts, merge into file |
| flows/openai-reauth/background/steps/*.js | Four node executors driving the OAuth flow |
| flows/openai/content/openai-auth.js | New DETECT_ACCOUNT_BANNED content-script command |
| flows/index.js | Register openai-reauth flow in registry |
| background.js | Import reauth scripts, wire executors, broadcast STATE_PATCH from setState |
| background/message-router.js | New START_REAUTH_BATCH handler; pass reauthInputAccount in EXECUTE_NODE/AUTO_RUN |
| sidepanel/sidepanel.{html,css,js} | New reauth UI rows, progress bar, result/download controls and handlers |
| manifest.json | Add downloads permission, register reauth scripts in content_scripts |
| tests/*.test.js | Unit tests for validator, oauth-client, mail-rules, batch-runner, fetch-code, cookie-cleanup, capture-callback, and updated sidepanel-flow registry test |
| .gitignore / .gitattributes | Ignore .omc/ and /tmp/; enforce LF line endings |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // 建设性日志:标记 consent 主动点击能力是否就绪,方便排查步骤 4 行为差异。 | ||
| if (!consentClickEnabled) { | ||
| logStep('OAuth 同意页主动点击能力未注入(部分 step9 辅助函数缺失),步骤 4 将仅依赖 localhost 回调监听。', 'warn'); | ||
| } | ||
|
|
||
| function logStep(message, level = 'info') { |
| function extractAccountEmail(account = {}) { | ||
| // 优先走 validator 的统一实现,保持双端 email 提取逻辑一致。 | ||
| const validator = (typeof self !== 'undefined' ? self : globalThis) | ||
| .MultiPageOpenAiReauthAccountValidator; |
| onBeforeNavigate = handleNavigation; | ||
| onCommitted = handleNavigation; | ||
| onTabUpdated = handleTabUpdated; | ||
| chromeApi.webNavigation.onBeforeNavigate.addListener(onBeforeNavigate); | ||
| chromeApi.webNavigation.onCommitted.addListener(onCommitted); | ||
| chromeApi.tabs.onUpdated.addListener(onTabUpdated); |
| async function handleReauthBatchStop() { | ||
| try { | ||
| await chrome.runtime.sendMessage({ type: 'STOP_FLOW', source: 'sidepanel', payload: {} }); | ||
| setReauthJsonStatus('已请求停止批量授权...', 'warn'); |
| } catch (error) { | ||
| const stopped = isLikelyStopError(error); | ||
| await setState({ |
| if (!chromeApi?.webNavigation || !chromeApi?.tabs) { | ||
| throw new Error('capture-reauth-callback executor 需要 chrome.webNavigation / chrome.tabs。'); | ||
| } |
| const url = URL.createObjectURL(blob); | ||
| await chrome.downloads.download({ url, filename, saveAs: true }); | ||
| // 让浏览器自己处理 url 生命周期;保险起见 30 秒后 revoke | ||
| setTimeout(() => { try { URL.revokeObjectURL(url); } catch {} }, 30000); |
| const decoded = typeof atob === 'function' | ||
| ? atob(padded) | ||
| : Buffer.from(padded, 'base64').toString('binary'); |
| "debugger", | ||
| "browsingData", | ||
| "cookies", | ||
| "downloads", |
| const stores = chromeApi.cookies.getAllCookieStores | ||
| ? await chromeApi.cookies.getAllCookieStores() | ||
| : [{ id: undefined }]; |
- 接入批量授权账号邮箱提取的依赖注入,避免运行器隐式依赖全局校验器 - 修复批量终止状态写入、构造期日志、下载链接释放和状态样式清理的边界问题 - 补齐批量运行器与回调捕获执行器的防回归测试覆盖
- 识别 OAuth 重新授权后的手机验证页与手机号补充页 - 将手机验证要求转换为账号级 fatal 错误,批量流程可跳过当前账号继续执行 - 补充步骤 4 手机验证场景测试,覆盖监听器清理与不误完成节点
- 在步骤4监听回调前后增加认证页即时预检 - 通过标签页快照、登录状态消息和页面文本识别手机验证阻断 - 补充启动预检测试,确保不会等待 OAuth ready 超时才跳过账号
|
这个 PR 现在先不要继续合并,请先同步最新 我这边刚重新拉取并核对了一次:
请在你的功能分支上先执行一次同步,比如优先 rebase 到最新 git fetch origin
git rebase origin/dev
# 如有冲突,解决后继续 rebase,并重新跑相关测试
git push --force-with-lease origin feat/openai-reauth-flow如果不用 rebase,也可以 |
|
Closing this PR because it was based on |
Summary
openai-reauthflow:4 步 OAuth PKCE 重新授权流程(准备 → 提交邮箱 → 获取验证码 → 捕获回调换 Token)START_REAUTH_BATCH消息驱动,自动串行处理账号队列DETECT_ACCOUNT_BANNED消息 +chrome.scripting降级双通道,识别后自动跳过Key changes
flows/openai-reauth/**flows/openai/content/openai-auth.jsDETECT_ACCOUNT_BANNED等消息类型支持background.jssidepanel/sidepanel.jssidepanel/sidepanel.htmlTest plan