Skip to content

feat: add Codex auth login and export flow#93

Merged
cnlimiter merged 2 commits intocnlimiter:masterfrom
haq426-163:copilot/codex-auth-f4d0327
Mar 25, 2026
Merged

feat: add Codex auth login and export flow#93
cnlimiter merged 2 commits intocnlimiter:masterfrom
haq426-163:copilot/codex-auth-f4d0327

Conversation

@haq426-163
Copy link

Summary

  • add account-management Codex Auth login entrypoints for single-account and batch flows
  • persist refreshed Codex-compatible tokens and workspace metadata for later export
  • add auth.json export support plus regression coverage for the login/export flow

Testing

  • Unable to run pytest in this environment because the required test dependencies are not installed locally.

Add Codex Auth support in account management so selected accounts can
complete a Codex-compatible OAuth login flow and export usable auth.json
files.

This commit includes:
- account-management UI entrypoints for Codex Auth login and auth.json download
- backend SSE routes for single-account and batch Codex Auth login execution
- persistence of freshly returned Codex-compatible tokens back into the account database
- Codex auth export support for direct auth.json download and batch zip packaging
- tests covering the Codex Auth login flow and export behavior

The OTP verification failure was caused by manually sending a second OTP after
password verification. The flow now reuses the existing proven login path:
login re-entry, password verification, automatic OTP reception, consent page
handling, workspace selection, and OAuth callback exchange.

Successful logins now also persist workspace_id together with the refreshed
Codex-compatible tokens, making later re-export of auth.json possible without
requiring the browser-downloaded file to still exist locally.

Change-Id: I59df518ef4dc05f8bc52c734dd1b738fcb0b7a4e
Copilot AI review requested due to automatic review settings March 25, 2026 12:04
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds “Codex Auth” login and export capabilities to the account-management UI/API, allowing users to generate Codex CLI-compatible auth.json (single or batch), persist the refreshed tokens/workspace metadata, and export them later.

Changes:

  • Added a CodexAuthEngine that reuses the existing RegistrationEngine OAuth/login chain to generate Codex-compatible auth.json.
  • Added account-management routes/UI for “Codex Auth 登录” (SSE streaming logs/results) and a new export format (/export/codex_auth) with ZIP support for multiple accounts.
  • Added regression tests covering the Codex auth flow, persistence marker, and export behavior; updated existing failover tests for the RegistrationEngine constructor signature.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/core/codex_auth.py New engine to produce Codex CLI auth.json by reusing existing login/OAuth steps.
src/core/openai/oauth.py Adds optional originator parameter to OAuth URL generation/manager.
src/web/routes/accounts.py Adds Codex Auth login (SSE), persistence helpers, and codex_auth export endpoint.
static/js/accounts.js Adds UI handlers for Codex Auth login modal + export error parsing improvements.
templates/accounts.html Adds Codex Auth button/modal and export menu item.
src/config/constants.py Adds Codex OAuth redirect URI/scope/originator constants.
README.md Documents that Codex Auth export requires generating via “Codex Auth 登录” first.
tests/test_codex_auth_flow.py New unit tests for Codex auth run behavior + workspace resolution fallback.
tests/test_codex_auth_export_route.py New tests for auth.json export (single) and ZIP export (multi) + persistence marker.
tests/test_registration_*_failover.py Updates fake engine constructors to match new init signature.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

scope=self.scope,
client_id=self.client_id
client_id=self.client_id,
originator=self.originator
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

OAuthManager.start_oauth() ignores the instance’s auth_url (and any value coming from settings) and always builds the authorize URL from the module-level OAUTH_AUTH_URL. This makes the auth_url constructor parameter effectively dead and can break deployments that override the auth endpoint. Consider passing auth_url into generate_oauth_url() (or constructing the URL directly from self.auth_url) so OAuthManager(auth_url=...) is actually respected.

Suggested change
originator=self.originator
originator=self.originator,
auth_url=self.auth_url,

Copilot uses AI. Check for mistakes.
Comment on lines +650 to +661
def build_auth_json(acc):
return {
"auth_mode": "chatgpt",
"OPENAI_API_KEY": None,
"tokens": {
"id_token": acc.id_token or "",
"access_token": acc.access_token or "",
"refresh_token": acc.refresh_token or "",
"account_id": acc.account_id or ""
},
"last_refresh": acc.last_refresh.isoformat() if acc.last_refresh else ""
}
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

export_accounts_codex_auth() formats last_refresh using datetime.isoformat() (no timezone / Z), while CodexAuthEngine emits RFC3339 with Z. For Codex compatibility (and to keep a single canonical format), export should use the same UTC/Z formatting as the generated auth.json (or persist/serialize the engine’s last_refresh value consistently).

Copilot uses AI. Check for mistakes.
Comment on lines +96 to +104
"access_token": tokens.get("access_token", ""),
"refresh_token": tokens.get("refresh_token", ""),
"id_token": tokens.get("id_token", ""),
"last_refresh": datetime.utcnow(),
"extra_data": _build_codex_auth_extra_data(
_get_account_extra_data(account),
workspace_id=workspace_id,
),
}
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

_persist_codex_auth_result() writes token fields using tokens.get(..., ""), which will overwrite existing DB tokens with empty strings if the auth payload is missing a field (e.g., refresh_token not returned). Consider only updating each token column when the corresponding value is present/non-empty in auth_json["tokens"], to avoid accidental token loss.

Suggested change
"access_token": tokens.get("access_token", ""),
"refresh_token": tokens.get("refresh_token", ""),
"id_token": tokens.get("id_token", ""),
"last_refresh": datetime.utcnow(),
"extra_data": _build_codex_auth_extra_data(
_get_account_extra_data(account),
workspace_id=workspace_id,
),
}
"last_refresh": datetime.utcnow(),
"extra_data": _build_codex_auth_extra_data(
_get_account_extra_data(account),
workspace_id=workspace_id,
),
}
# Only update token fields when a non-empty value is provided,
# to avoid overwriting existing DB tokens with empty strings.
for token_field in ("access_token", "refresh_token", "id_token"):
raw_value = tokens.get(token_field)
if isinstance(raw_value, str):
raw_value = raw_value.strip()
if raw_value:
update_kwargs[token_field] = raw_value

Copilot uses AI. Check for mistakes.

yield f"data: {json.dumps({'type': 'result', **msg_data}, ensure_ascii=False)}\n\n"
break
except Exception:
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

The SSE event_generator() swallows all exceptions (except Exception: break), which can cause the stream to terminate without sending a final error/result event, and also prevents proper cancellation handling on client disconnect. Consider handling asyncio.CancelledError separately (re-raise), logging unexpected exceptions, and emitting a terminal result/error SSE event so the UI can surface failures deterministically.

Suggested change
except Exception:
except asyncio.CancelledError:
# 客户端断开连接时,正确传播取消异常
logger.info("Codex auth login SSE stream cancelled by client")
raise
except Exception as e:
# 记录异常并发送终止错误事件,避免静默中断
logger.exception("Unexpected error in Codex auth login SSE stream")
error_event = {
"type": "error",
"message": "internal server error during Codex auth login",
}
yield f"data: {json.dumps(error_event, ensure_ascii=False)}\n\n"

Copilot uses AI. Check for mistakes.
Comment on lines +846 to +847
逐个执行登录,通过 SSE 推送每个账号的进度和结果。
全部完成后打包下载。
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

The batch login endpoint docstring says “全部完成后打包下载”, but the implementation streams progress/results over SSE and does not create or return a downloadable archive. Please update the docstring to match actual behavior (or implement the described packaging/download) to avoid misleading API consumers.

Suggested change
逐个执行登录通过 SSE 推送每个账号的进度和结果
全部完成后打包下载
逐个执行登录通过 SSEtext/event-stream推送每个账号的进度和结果
客户端需监听事件流如最终的 batch_done 事件以获取所有账号的处理结果

Copilot uses AI. Check for mistakes.
@cnlimiter cnlimiter merged commit 08327b4 into cnlimiter:master Mar 25, 2026
1 check passed
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.

4 participants