mirror of
https://github.com/openai/codex.git
synced 2026-09-28 08:43:01 +08:00
Honor configured proxy routes for auth refreshes (#34655)
## Why ChatGPT token refresh requests need to follow the same configured routing policy as other authentication traffic, including environments that use the system proxy. ## What changed - Require an `AuthRouteConfig` throughout login, logout, token refresh, personal access token, and agent identity flows. - Build auth HTTP clients directly from that configuration instead of falling back to a default HTTP client when routing configuration is absent. ## Testing - Add an integration test that refreshes a token through a cached system-proxy route and verifies the proxy receives the expected request. GitOrigin-RevId: 7d54ab3219939a49921c51faf08cd4c2eabca51c
This commit is contained in:
@@ -464,7 +464,7 @@ impl AccountRequestProcessor {
|
||||
config.forced_chatgpt_workspace_id.clone(),
|
||||
config.cli_auth_credentials_store_mode,
|
||||
config.auth_keyring_backend_kind(),
|
||||
Some(config.auth_route_config()),
|
||||
config.auth_route_config(),
|
||||
)
|
||||
};
|
||||
#[cfg(debug_assertions)]
|
||||
|
||||
+12
-12
@@ -120,7 +120,7 @@ async fn clear_existing_auth_before_login(
|
||||
codex_home: &Path,
|
||||
auth_credentials_store_mode: AuthCredentialsStoreMode,
|
||||
auth_keyring_backend_kind: AuthKeyringBackendKind,
|
||||
auth_route_config: Option<&AuthRouteConfig>,
|
||||
auth_route_config: &AuthRouteConfig,
|
||||
) {
|
||||
if let Err(err) = logout_with_revoke(
|
||||
codex_home,
|
||||
@@ -139,13 +139,13 @@ pub async fn login_with_chatgpt(
|
||||
forced_chatgpt_workspace_id: Option<Vec<String>>,
|
||||
cli_auth_credentials_store_mode: AuthCredentialsStoreMode,
|
||||
auth_keyring_backend_kind: AuthKeyringBackendKind,
|
||||
auth_route_config: Option<AuthRouteConfig>,
|
||||
auth_route_config: AuthRouteConfig,
|
||||
) -> std::io::Result<()> {
|
||||
clear_existing_auth_before_login(
|
||||
&codex_home,
|
||||
cli_auth_credentials_store_mode,
|
||||
auth_keyring_backend_kind,
|
||||
auth_route_config.as_ref(),
|
||||
&auth_route_config,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -180,7 +180,7 @@ pub async fn run_login_with_chatgpt(cli_config_overrides: CliConfigOverrides) ->
|
||||
forced_chatgpt_workspace_id,
|
||||
config.cli_auth_credentials_store_mode,
|
||||
config.auth_keyring_backend_kind(),
|
||||
Some(config.auth_route_config()),
|
||||
config.auth_route_config(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -246,7 +246,7 @@ pub async fn run_login_with_access_token(
|
||||
config.forced_chatgpt_workspace_id.as_deref(),
|
||||
Some(&config.chatgpt_base_url),
|
||||
config.auth_keyring_backend_kind(),
|
||||
Some(&auth_route_config),
|
||||
&auth_route_config,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -320,7 +320,7 @@ pub async fn run_login_with_device_code(
|
||||
&config.codex_home,
|
||||
config.cli_auth_credentials_store_mode,
|
||||
config.auth_keyring_backend_kind(),
|
||||
Some(&auth_route_config),
|
||||
&auth_route_config,
|
||||
)
|
||||
.await;
|
||||
let forced_chatgpt_workspace_id = config.forced_chatgpt_workspace_id.clone();
|
||||
@@ -330,7 +330,7 @@ pub async fn run_login_with_device_code(
|
||||
forced_chatgpt_workspace_id,
|
||||
config.cli_auth_credentials_store_mode,
|
||||
config.auth_keyring_backend_kind(),
|
||||
Some(auth_route_config),
|
||||
auth_route_config,
|
||||
);
|
||||
if let Some(iss) = issuer_base_url {
|
||||
opts.issuer = iss;
|
||||
@@ -368,7 +368,7 @@ pub async fn run_login_with_device_code_fallback_to_browser(
|
||||
&config.codex_home,
|
||||
config.cli_auth_credentials_store_mode,
|
||||
config.auth_keyring_backend_kind(),
|
||||
Some(&auth_route_config),
|
||||
&auth_route_config,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -379,7 +379,7 @@ pub async fn run_login_with_device_code_fallback_to_browser(
|
||||
forced_chatgpt_workspace_id,
|
||||
config.cli_auth_credentials_store_mode,
|
||||
config.auth_keyring_backend_kind(),
|
||||
Some(auth_route_config),
|
||||
auth_route_config,
|
||||
);
|
||||
if let Some(iss) = issuer_base_url {
|
||||
opts.issuer = iss;
|
||||
@@ -430,7 +430,7 @@ pub async fn run_login_status(cli_config_overrides: CliConfigOverrides) -> ! {
|
||||
config.cli_auth_credentials_store_mode,
|
||||
Some(&config.chatgpt_base_url),
|
||||
config.auth_keyring_backend_kind(),
|
||||
Some(&auth_route_config),
|
||||
&auth_route_config,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -484,7 +484,7 @@ pub async fn run_logout(cli_config_overrides: CliConfigOverrides) -> ! {
|
||||
&config.codex_home,
|
||||
config.cli_auth_credentials_store_mode,
|
||||
config.auth_keyring_backend_kind(),
|
||||
Some(&auth_route_config),
|
||||
&auth_route_config,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -557,7 +557,7 @@ mod tests {
|
||||
codex_home.path(),
|
||||
AuthCredentialsStoreMode::File,
|
||||
AuthKeyringBackendKind::default(),
|
||||
/*auth_route_config*/ None,
|
||||
&codex_login::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await;
|
||||
|
||||
|
||||
@@ -1756,7 +1756,7 @@ async fn load_exec_server_remote_auth_provider(
|
||||
let auth = CodexAuth::from_agent_identity_jwt(
|
||||
&agent_identity_jwt,
|
||||
Some(&config.chatgpt_base_url),
|
||||
Some(&auth_route_config),
|
||||
&auth_route_config,
|
||||
)
|
||||
.await?;
|
||||
return Ok(codex_model_provider::auth_provider_from_auth(&auth));
|
||||
|
||||
@@ -610,7 +610,7 @@ pub(crate) async fn load_cli_auth_mode(config: &Config) -> Option<AuthMode> {
|
||||
config.cli_auth_credentials_store_mode,
|
||||
Some(&config.chatgpt_base_url),
|
||||
config.auth_keyring_backend_kind(),
|
||||
Some(&auth_route_config),
|
||||
&auth_route_config,
|
||||
)
|
||||
.await
|
||||
.ok()
|
||||
|
||||
@@ -113,7 +113,7 @@ async fn auth_manager_with_agent_identity_business_plan() -> Arc<AuthManager> {
|
||||
task_id: Some("task-123".to_string()),
|
||||
},
|
||||
"https://auth.openai.com/api/accounts",
|
||||
/*auth_route_config*/ None,
|
||||
&codex_login::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await
|
||||
.expect("agent identity record should be complete"),
|
||||
|
||||
@@ -1715,7 +1715,7 @@ async fn prefers_apikey_when_config_prefers_apikey_even_with_chatgpt_tokens() {
|
||||
AuthCredentialsStoreMode::File,
|
||||
/*chatgpt_base_url*/ None,
|
||||
AuthKeyringBackendKind::default(),
|
||||
/*auth_route_config*/ None,
|
||||
&codex_login::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await
|
||||
.expect("Failed to load CodexAuth")
|
||||
|
||||
@@ -589,7 +589,7 @@ async fn remote_compact_uses_agent_identity_assertion() -> Result<()> {
|
||||
task_id: Some("task-compact".to_string()),
|
||||
},
|
||||
"https://auth.openai.com/api/accounts",
|
||||
/*auth_route_config*/ None,
|
||||
&codex_login::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await?,
|
||||
)),
|
||||
|
||||
@@ -36,6 +36,8 @@ pub use crate::outbound_proxy::HttpClientFactory;
|
||||
pub use crate::outbound_proxy::OutboundProxyPolicy;
|
||||
pub use crate::outbound_proxy::OutboundProxyRoute;
|
||||
pub use crate::outbound_proxy::RouteFailureClass;
|
||||
#[doc(hidden)]
|
||||
pub use crate::outbound_proxy::cache_system_proxy_route_for_test;
|
||||
pub use crate::request::EncodedJsonBody;
|
||||
pub use crate::request::PreparedRequestBody;
|
||||
pub use crate::request::Request;
|
||||
|
||||
@@ -544,7 +544,6 @@ fn cached_system_proxy_decision_from_cache(
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn cache_system_proxy_decision(request_url: &str, decision: SystemProxyDecision) {
|
||||
let cache = SYSTEM_PROXY_CACHE.get_or_init(|| Mutex::new(HashMap::new()));
|
||||
if let Ok(mut cache) = cache.lock() {
|
||||
@@ -553,6 +552,14 @@ fn cache_system_proxy_decision(request_url: &str, decision: SystemProxyDecision)
|
||||
}
|
||||
}
|
||||
|
||||
/// Primes one proxy decision for cross-crate integration tests.
|
||||
///
|
||||
/// This is public only so tests in HTTP-client consumers can exercise system-proxy routing
|
||||
/// deterministically on every supported platform.
|
||||
pub fn cache_system_proxy_route_for_test(request_url: &str, proxy_url: String) {
|
||||
cache_system_proxy_decision(request_url, SystemProxyDecision::Proxy { url: proxy_url });
|
||||
}
|
||||
|
||||
fn insert_system_proxy_cache_entry(
|
||||
cache: &mut HashMap<String, CachedSystemProxyDecision>,
|
||||
cache_key: &str,
|
||||
|
||||
@@ -102,7 +102,7 @@ impl AgentIdentityAuth {
|
||||
pub async fn from_record(
|
||||
mut record: AgentIdentityAuthRecord,
|
||||
agent_identity_authapi_base_url: &str,
|
||||
auth_route_config: Option<&AuthRouteConfig>,
|
||||
auth_route_config: &AuthRouteConfig,
|
||||
) -> std::io::Result<Self> {
|
||||
public_key_ssh_from_private_key_pkcs8_base64(&record.agent_private_key)
|
||||
.map_err(std::io::Error::other)?;
|
||||
@@ -125,7 +125,7 @@ impl AgentIdentityAuth {
|
||||
jwt: &str,
|
||||
chatgpt_base_url: &str,
|
||||
agent_identity_authapi_base_url: &str,
|
||||
auth_route_config: Option<&AuthRouteConfig>,
|
||||
auth_route_config: &AuthRouteConfig,
|
||||
) -> std::io::Result<Self> {
|
||||
let record = verified_record_from_jwt(jwt, chatgpt_base_url, auth_route_config).await?;
|
||||
Self::from_record(record, agent_identity_authapi_base_url, auth_route_config).await
|
||||
@@ -175,7 +175,7 @@ pub(super) async fn register_managed_chatgpt_agent_identity(
|
||||
binding: ManagedChatGptAgentIdentityBinding,
|
||||
agent_identity_authapi_base_url: &str,
|
||||
session_source: SessionSource,
|
||||
auth_route_config: Option<&AuthRouteConfig>,
|
||||
auth_route_config: &AuthRouteConfig,
|
||||
) -> std::io::Result<AgentIdentityAuth> {
|
||||
let key_material = generate_agent_key_material().map_err(std::io::Error::other)?;
|
||||
let registration_url = agent_registration_url(agent_identity_authapi_base_url);
|
||||
@@ -222,7 +222,7 @@ pub(super) async fn register_managed_chatgpt_agent_identity(
|
||||
pub(super) async fn verified_record_from_jwt(
|
||||
jwt: &str,
|
||||
chatgpt_base_url: &str,
|
||||
auth_route_config: Option<&AuthRouteConfig>,
|
||||
auth_route_config: &AuthRouteConfig,
|
||||
) -> std::io::Result<AgentIdentityAuthRecord> {
|
||||
AgentIdentityAuthRecord::from_agent_identity_jwt(jwt)?;
|
||||
let jwks_url = agent_identity_jwks_url(chatgpt_base_url);
|
||||
@@ -302,7 +302,7 @@ where
|
||||
async fn register_task_for_record_with_retries(
|
||||
record: &AgentIdentityAuthRecord,
|
||||
agent_identity_authapi_base_url: &str,
|
||||
auth_route_config: Option<&AuthRouteConfig>,
|
||||
auth_route_config: &AuthRouteConfig,
|
||||
) -> std::io::Result<String> {
|
||||
let task_registration_url =
|
||||
agent_task_registration_url(agent_identity_authapi_base_url, &record.agent_runtime_id);
|
||||
@@ -392,7 +392,7 @@ mod tests {
|
||||
let auth = AgentIdentityAuth::from_record(
|
||||
agent_identity_record_with_generated_key(),
|
||||
&server.uri(),
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -437,7 +437,7 @@ mod tests {
|
||||
&jwt,
|
||||
&format!("{}/backend-api", server.uri()),
|
||||
&server.uri(),
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -480,7 +480,7 @@ mod tests {
|
||||
let auth = AgentIdentityAuth::from_record(
|
||||
agent_identity_record_with_generated_key(),
|
||||
&server.uri(),
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ async fn login_with_access_token_writes_agent_identity_jwt() {
|
||||
/*forced_chatgpt_workspace_id*/ None,
|
||||
Some(&chatgpt_base_url),
|
||||
AuthKeyringBackendKind::default(),
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await
|
||||
.expect("login_with_access_token should succeed");
|
||||
@@ -182,7 +182,7 @@ async fn stored_agent_identity_jwt_keeps_auth_json_unchanged() -> anyhow::Result
|
||||
Some(&chatgpt_base_url),
|
||||
AuthKeyringBackendKind::Direct,
|
||||
Some(&authapi_base_url),
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await?
|
||||
.expect("auth should load");
|
||||
@@ -228,7 +228,7 @@ async fn login_with_access_token_writes_only_personal_access_token() {
|
||||
Some(&allowed_workspaces),
|
||||
/*chatgpt_base_url*/ None,
|
||||
AuthKeyringBackendKind::default(),
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await
|
||||
.expect("personal access token login should succeed");
|
||||
@@ -281,7 +281,7 @@ async fn login_with_access_token_rejects_personal_access_token_workspace_mismatc
|
||||
Some(&allowed_workspaces),
|
||||
/*chatgpt_base_url*/ None,
|
||||
AuthKeyringBackendKind::default(),
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await
|
||||
.expect_err("personal access token workspace mismatch should fail");
|
||||
@@ -314,7 +314,7 @@ async fn login_with_access_token_rejects_invalid_personal_access_token() {
|
||||
/*forced_chatgpt_workspace_id*/ None,
|
||||
/*chatgpt_base_url*/ None,
|
||||
AuthKeyringBackendKind::default(),
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await
|
||||
.expect_err("invalid personal access token should fail");
|
||||
@@ -338,7 +338,7 @@ async fn login_with_access_token_rejects_invalid_jwt() {
|
||||
/*forced_chatgpt_workspace_id*/ None,
|
||||
/*chatgpt_base_url*/ None,
|
||||
AuthKeyringBackendKind::default(),
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await
|
||||
.expect_err("invalid access token should fail");
|
||||
@@ -370,7 +370,7 @@ async fn chatgpt_auth_registers_agent_identity_when_enabled() -> anyhow::Result<
|
||||
/*chatgpt_base_url*/ None,
|
||||
AuthKeyringBackendKind::Direct,
|
||||
/*agent_identity_authapi_base_url*/ None,
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await?
|
||||
.expect("auth should load");
|
||||
@@ -380,7 +380,7 @@ async fn chatgpt_auth_registers_agent_identity_when_enabled() -> anyhow::Result<
|
||||
AgentIdentityAuthPolicy::JwtOnly,
|
||||
/*agent_identity_authapi_base_url*/ None,
|
||||
/*forced_chatgpt_workspace_id*/ None,
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
SessionSource::Cli,
|
||||
)
|
||||
.await?
|
||||
@@ -411,7 +411,7 @@ async fn chatgpt_auth_registers_agent_identity_when_enabled() -> anyhow::Result<
|
||||
AgentIdentityAuthPolicy::ChatGptAuth,
|
||||
Some(&server.uri()),
|
||||
/*forced_chatgpt_workspace_id*/ None,
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
SessionSource::Cli,
|
||||
)
|
||||
.await?
|
||||
@@ -421,7 +421,7 @@ async fn chatgpt_auth_registers_agent_identity_when_enabled() -> anyhow::Result<
|
||||
AgentIdentityAuthPolicy::ChatGptAuth,
|
||||
Some(&server.uri()),
|
||||
/*forced_chatgpt_workspace_id*/ None,
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
SessionSource::Cli,
|
||||
)
|
||||
.await?
|
||||
@@ -452,7 +452,7 @@ async fn chatgpt_auth_registers_agent_identity_when_enabled() -> anyhow::Result<
|
||||
/*chatgpt_base_url*/ None,
|
||||
AuthKeyringBackendKind::Direct,
|
||||
/*agent_identity_authapi_base_url*/ None,
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await?
|
||||
.expect("auth should reload");
|
||||
@@ -461,7 +461,7 @@ async fn chatgpt_auth_registers_agent_identity_when_enabled() -> anyhow::Result<
|
||||
AgentIdentityAuthPolicy::ChatGptAuth,
|
||||
Some(&server.uri()),
|
||||
/*forced_chatgpt_workspace_id*/ None,
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
SessionSource::Cli,
|
||||
)
|
||||
.await?
|
||||
@@ -494,7 +494,7 @@ async fn chatgpt_auth_retries_transient_agent_identity_registration() -> anyhow:
|
||||
/*chatgpt_base_url*/ None,
|
||||
AuthKeyringBackendKind::Direct,
|
||||
/*agent_identity_authapi_base_url*/ None,
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await?
|
||||
.expect("auth should load");
|
||||
@@ -523,7 +523,7 @@ async fn chatgpt_auth_retries_transient_agent_identity_registration() -> anyhow:
|
||||
AgentIdentityAuthPolicy::ChatGptAuth,
|
||||
Some(&server.uri()),
|
||||
/*forced_chatgpt_workspace_id*/ None,
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
SessionSource::Cli,
|
||||
)
|
||||
.await?
|
||||
@@ -560,7 +560,7 @@ async fn chatgpt_auth_registration_retry_exhaustion_is_fallback_eligible() -> an
|
||||
/*chatgpt_base_url*/ None,
|
||||
AuthKeyringBackendKind::Direct,
|
||||
/*agent_identity_authapi_base_url*/ None,
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await?
|
||||
.expect("auth should load");
|
||||
@@ -578,7 +578,7 @@ async fn chatgpt_auth_registration_retry_exhaustion_is_fallback_eligible() -> an
|
||||
AgentIdentityAuthPolicy::ChatGptAuth,
|
||||
Some(&server.uri()),
|
||||
/*forced_chatgpt_workspace_id*/ None,
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
SessionSource::Cli,
|
||||
)
|
||||
.await
|
||||
@@ -621,7 +621,7 @@ async fn chatgpt_auth_task_registration_retry_exhaustion_is_fallback_eligible()
|
||||
/*chatgpt_base_url*/ None,
|
||||
AuthKeyringBackendKind::Direct,
|
||||
/*agent_identity_authapi_base_url*/ None,
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await?
|
||||
.expect("auth should load");
|
||||
@@ -642,7 +642,7 @@ async fn chatgpt_auth_task_registration_retry_exhaustion_is_fallback_eligible()
|
||||
AgentIdentityAuthPolicy::ChatGptAuth,
|
||||
Some(&server.uri()),
|
||||
/*forced_chatgpt_workspace_id*/ None,
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
SessionSource::Cli,
|
||||
)
|
||||
.await
|
||||
@@ -677,7 +677,7 @@ async fn chatgpt_auth_non_retryable_registration_error_is_hard_failure() -> anyh
|
||||
/*chatgpt_base_url*/ None,
|
||||
AuthKeyringBackendKind::Direct,
|
||||
/*agent_identity_authapi_base_url*/ None,
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await?
|
||||
.expect("auth should load");
|
||||
@@ -695,7 +695,7 @@ async fn chatgpt_auth_non_retryable_registration_error_is_hard_failure() -> anyh
|
||||
AgentIdentityAuthPolicy::ChatGptAuth,
|
||||
Some(&server.uri()),
|
||||
/*forced_chatgpt_workspace_id*/ None,
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
SessionSource::Cli,
|
||||
)
|
||||
.await
|
||||
@@ -737,7 +737,7 @@ async fn agent_identity_jwt_task_registration_retry_exhaustion_is_strict() -> an
|
||||
&agent_identity,
|
||||
Some(&chatgpt_base_url),
|
||||
&authapi_base_url,
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await
|
||||
.expect_err("agent identity jwt task retry exhaustion should fail");
|
||||
@@ -768,7 +768,7 @@ async fn login_with_access_token_rejects_unsigned_jwt() {
|
||||
/*forced_chatgpt_workspace_id*/ None,
|
||||
Some(&chatgpt_base_url),
|
||||
AuthKeyringBackendKind::default(),
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await
|
||||
.expect_err("unsigned access token should fail");
|
||||
@@ -790,7 +790,7 @@ async fn missing_auth_json_returns_none() {
|
||||
AuthCredentialsStoreMode::File,
|
||||
/*chatgpt_base_url*/ None,
|
||||
AuthKeyringBackendKind::default(),
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await
|
||||
.expect("call should succeed");
|
||||
@@ -820,7 +820,7 @@ async fn pro_account_with_no_api_key_uses_chatgpt_auth() {
|
||||
/*chatgpt_base_url*/ None,
|
||||
AuthKeyringBackendKind::Direct,
|
||||
/*agent_identity_authapi_base_url*/ None,
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
@@ -882,7 +882,7 @@ async fn loads_api_key_from_auth_json() {
|
||||
/*chatgpt_base_url*/ None,
|
||||
AuthKeyringBackendKind::Direct,
|
||||
/*agent_identity_authapi_base_url*/ None,
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
@@ -978,7 +978,7 @@ async fn refresh_failure_is_scoped_to_the_matching_auth_snapshot() {
|
||||
/*chatgpt_base_url*/ None,
|
||||
AuthKeyringBackendKind::Direct,
|
||||
/*agent_identity_authapi_base_url*/ None,
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await
|
||||
.expect("load auth")
|
||||
@@ -999,7 +999,7 @@ async fn refresh_failure_is_scoped_to_the_matching_auth_snapshot() {
|
||||
/*chatgpt_base_url*/ None,
|
||||
AuthKeyringBackendKind::Direct,
|
||||
/*agent_identity_authapi_base_url*/ None,
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await
|
||||
.expect("updated auth should parse");
|
||||
@@ -1427,7 +1427,7 @@ async fn load_auth_reads_access_token_from_env() {
|
||||
Some(&chatgpt_base_url),
|
||||
AuthKeyringBackendKind::Direct,
|
||||
Some(&authapi_base_url),
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await
|
||||
.expect("env auth should load")
|
||||
@@ -1475,7 +1475,7 @@ async fn load_auth_reads_personal_access_token_from_env() {
|
||||
/*chatgpt_base_url*/ None,
|
||||
AuthKeyringBackendKind::default(),
|
||||
/*agent_identity_authapi_base_url*/ None,
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await
|
||||
.expect("env auth should load")
|
||||
@@ -1569,7 +1569,7 @@ async fn auth_manager_rejects_stored_personal_access_token_workspace_mismatch()
|
||||
/*forced_chatgpt_workspace_id*/ None,
|
||||
/*chatgpt_base_url*/ None,
|
||||
AuthKeyringBackendKind::default(),
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await
|
||||
.expect("personal access token login should succeed");
|
||||
@@ -1648,7 +1648,7 @@ async fn load_auth_keeps_codex_api_key_env_precedence() {
|
||||
/*chatgpt_base_url*/ None,
|
||||
AuthKeyringBackendKind::Direct,
|
||||
/*agent_identity_authapi_base_url*/ None,
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await
|
||||
.expect("env auth should load")
|
||||
@@ -1745,7 +1745,7 @@ async fn enforce_login_restrictions_logs_out_for_personal_access_token_workspace
|
||||
/*forced_chatgpt_workspace_id*/ None,
|
||||
/*chatgpt_base_url*/ None,
|
||||
AuthKeyringBackendKind::default(),
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await
|
||||
.expect("personal access token login should succeed");
|
||||
@@ -2134,7 +2134,7 @@ async fn assert_agent_identity_plan_alias(
|
||||
&jwt,
|
||||
Some(&chatgpt_base_url),
|
||||
&authapi_base_url,
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await
|
||||
.expect("agent identity auth");
|
||||
@@ -2166,7 +2166,7 @@ async fn plan_type_maps_known_plan() {
|
||||
/*chatgpt_base_url*/ None,
|
||||
AuthKeyringBackendKind::Direct,
|
||||
/*agent_identity_authapi_base_url*/ None,
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await
|
||||
.expect("load auth")
|
||||
@@ -2198,7 +2198,7 @@ async fn plan_type_maps_self_serve_business_usage_based_plan() {
|
||||
/*chatgpt_base_url*/ None,
|
||||
AuthKeyringBackendKind::Direct,
|
||||
/*agent_identity_authapi_base_url*/ None,
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await
|
||||
.expect("load auth")
|
||||
@@ -2233,7 +2233,7 @@ async fn plan_type_maps_enterprise_cbp_usage_based_plan() {
|
||||
/*chatgpt_base_url*/ None,
|
||||
AuthKeyringBackendKind::Direct,
|
||||
/*agent_identity_authapi_base_url*/ None,
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await
|
||||
.expect("load auth")
|
||||
@@ -2268,7 +2268,7 @@ async fn plan_type_maps_unknown_to_unknown() {
|
||||
/*chatgpt_base_url*/ None,
|
||||
AuthKeyringBackendKind::Direct,
|
||||
/*agent_identity_authapi_base_url*/ None,
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await
|
||||
.expect("load auth")
|
||||
@@ -2300,7 +2300,7 @@ async fn missing_plan_type_maps_to_unknown() {
|
||||
/*chatgpt_base_url*/ None,
|
||||
AuthKeyringBackendKind::Direct,
|
||||
/*agent_identity_authapi_base_url*/ None,
|
||||
/*auth_route_config*/ None,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await
|
||||
.expect("load auth")
|
||||
|
||||
@@ -299,31 +299,25 @@ fn build_default_client(builder: HttpClientBuilder) -> HttpClient {
|
||||
/// Builds an HTTP client for an auth endpoint without Codex default headers.
|
||||
pub(crate) fn create_raw_auth_client(
|
||||
endpoint: &str,
|
||||
auth_route_config: Option<&AuthRouteConfig>,
|
||||
auth_route_config: &AuthRouteConfig,
|
||||
) -> Result<HttpClient, BuildRouteAwareHttpClientError> {
|
||||
auth_http_client_factory(auth_route_config)
|
||||
auth_route_config
|
||||
.http_client_factory()
|
||||
.build_client_without_request_logging(endpoint, ClientRouteClass::Auth)
|
||||
}
|
||||
|
||||
/// Builds the default Codex HTTP client wrapper for an auth endpoint.
|
||||
pub(crate) fn create_default_auth_client(
|
||||
endpoint: &str,
|
||||
auth_route_config: Option<&AuthRouteConfig>,
|
||||
auth_route_config: &AuthRouteConfig,
|
||||
) -> Result<HttpClient, BuildRouteAwareHttpClientError> {
|
||||
create_client_for_route(
|
||||
&auth_http_client_factory(auth_route_config),
|
||||
auth_route_config.http_client_factory(),
|
||||
endpoint,
|
||||
ClientRouteClass::Auth,
|
||||
)
|
||||
}
|
||||
|
||||
fn auth_http_client_factory(auth_route_config: Option<&AuthRouteConfig>) -> HttpClientFactory {
|
||||
auth_route_config.map_or_else(
|
||||
|| HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
|
||||
|config| config.http_client_factory().clone(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn default_headers() -> HeaderMap {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert("originator", originator().header_value);
|
||||
|
||||
@@ -198,8 +198,11 @@ async fn raw_auth_client_does_not_log_sensitive_request_or_response_data() {
|
||||
let endpoint = format!(
|
||||
"http://auth-user:password-secret-value@{authority}/token?client_secret=query-secret-value"
|
||||
);
|
||||
let client = create_raw_auth_client(&endpoint, /*auth_route_config*/ None)
|
||||
.expect("raw auth client should build");
|
||||
let client = create_raw_auth_client(
|
||||
&endpoint,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.expect("raw auth client should build");
|
||||
let buffer = Arc::new(Mutex::new(Vec::new()));
|
||||
let subscriber = tracing_subscriber::registry().with(
|
||||
tracing_subscriber::fmt::layer()
|
||||
@@ -228,9 +231,11 @@ async fn raw_auth_client_does_not_log_sensitive_request_or_response_data() {
|
||||
let unresponsive_endpoint = format!(
|
||||
"http://auth-user:failure-password-secret-value@{unresponsive_addr}/token?client_secret=failure-query-secret-value"
|
||||
);
|
||||
let unresponsive_client =
|
||||
create_raw_auth_client(&unresponsive_endpoint, /*auth_route_config*/ None)
|
||||
.expect("raw auth client should build");
|
||||
let unresponsive_client = create_raw_auth_client(
|
||||
&unresponsive_endpoint,
|
||||
&crate::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.expect("raw auth client should build");
|
||||
let error = unresponsive_client
|
||||
.post(&unresponsive_endpoint)
|
||||
.header("x-sensitive-request", "failure-request-header-secret-value")
|
||||
|
||||
@@ -253,7 +253,7 @@ impl CodexAuth {
|
||||
chatgpt_base_url: Option<&str>,
|
||||
keyring_backend_kind: AuthKeyringBackendKind,
|
||||
agent_identity_authapi_base_url: Option<&str>,
|
||||
auth_route_config: Option<&AuthRouteConfig>,
|
||||
auth_route_config: &AuthRouteConfig,
|
||||
) -> std::io::Result<Self> {
|
||||
let auth_mode = auth_dot_json.resolved_mode();
|
||||
if auth_mode == AuthMode::ApiKey {
|
||||
@@ -353,7 +353,7 @@ impl CodexAuth {
|
||||
auth_credentials_store_mode: AuthCredentialsStoreMode,
|
||||
chatgpt_base_url: Option<&str>,
|
||||
keyring_backend_kind: AuthKeyringBackendKind,
|
||||
auth_route_config: Option<&AuthRouteConfig>,
|
||||
auth_route_config: &AuthRouteConfig,
|
||||
) -> std::io::Result<Option<Self>> {
|
||||
let agent_identity_authapi_base_url =
|
||||
agent_identity_authapi_base_url(chatgpt_base_url).ok();
|
||||
@@ -373,7 +373,7 @@ impl CodexAuth {
|
||||
pub async fn from_agent_identity_jwt(
|
||||
jwt: &str,
|
||||
chatgpt_base_url: Option<&str>,
|
||||
auth_route_config: Option<&AuthRouteConfig>,
|
||||
auth_route_config: &AuthRouteConfig,
|
||||
) -> std::io::Result<Self> {
|
||||
let agent_identity_authapi_base_url = agent_identity_authapi_base_url(chatgpt_base_url)?;
|
||||
Self::from_agent_identity_jwt_with_authapi_base_url(
|
||||
@@ -389,7 +389,7 @@ impl CodexAuth {
|
||||
jwt: &str,
|
||||
chatgpt_base_url: Option<&str>,
|
||||
agent_identity_authapi_base_url: &str,
|
||||
auth_route_config: Option<&AuthRouteConfig>,
|
||||
auth_route_config: &AuthRouteConfig,
|
||||
) -> std::io::Result<Self> {
|
||||
let base_url = chatgpt_base_url
|
||||
.unwrap_or(ChatGptEnvironment::default().chatgpt_base_url())
|
||||
@@ -408,7 +408,7 @@ impl CodexAuth {
|
||||
|
||||
pub async fn from_personal_access_token(
|
||||
access_token: &str,
|
||||
auth_route_config: Option<&AuthRouteConfig>,
|
||||
auth_route_config: &AuthRouteConfig,
|
||||
) -> std::io::Result<Self> {
|
||||
Ok(Self::PersonalAccessToken(
|
||||
PersonalAccessTokenAuth::load(access_token, auth_route_config).await?,
|
||||
@@ -632,7 +632,7 @@ impl CodexAuth {
|
||||
policy: AgentIdentityAuthPolicy,
|
||||
agent_identity_authapi_base_url: Option<&str>,
|
||||
forced_chatgpt_workspace_id: Option<Vec<String>>,
|
||||
auth_route_config: Option<&AuthRouteConfig>,
|
||||
auth_route_config: &AuthRouteConfig,
|
||||
session_source: SessionSource,
|
||||
) -> std::io::Result<Option<AgentIdentityAuth>> {
|
||||
match self {
|
||||
@@ -662,7 +662,7 @@ impl CodexAuth {
|
||||
&self,
|
||||
agent_identity_authapi_base_url: &str,
|
||||
forced_chatgpt_workspace_id: Option<Vec<String>>,
|
||||
auth_route_config: Option<&AuthRouteConfig>,
|
||||
auth_route_config: &AuthRouteConfig,
|
||||
session_source: SessionSource,
|
||||
) -> std::io::Result<AgentIdentityAuth> {
|
||||
let binding =
|
||||
@@ -880,7 +880,7 @@ pub async fn logout_with_revoke(
|
||||
codex_home: &Path,
|
||||
auth_credentials_store_mode: AuthCredentialsStoreMode,
|
||||
keyring_backend_kind: AuthKeyringBackendKind,
|
||||
auth_route_config: Option<&AuthRouteConfig>,
|
||||
auth_route_config: &AuthRouteConfig,
|
||||
) -> std::io::Result<bool> {
|
||||
let auth_dot_json = match load_auth_dot_json(
|
||||
codex_home,
|
||||
@@ -935,7 +935,7 @@ pub async fn login_with_access_token(
|
||||
forced_chatgpt_workspace_id: Option<&[String]>,
|
||||
chatgpt_base_url: Option<&str>,
|
||||
keyring_backend_kind: AuthKeyringBackendKind,
|
||||
auth_route_config: Option<&AuthRouteConfig>,
|
||||
auth_route_config: &AuthRouteConfig,
|
||||
) -> std::io::Result<()> {
|
||||
let auth_dot_json = match classify_codex_access_token(access_token) {
|
||||
CodexAccessToken::PersonalAccessToken(access_token) => {
|
||||
@@ -1073,7 +1073,7 @@ async fn enforce_login_restrictions_with_agent_identity_authapi_base_url(
|
||||
config.chatgpt_base_url.as_deref(),
|
||||
config.keyring_backend_kind,
|
||||
agent_identity_authapi_base_url,
|
||||
Some(&config.auth_route_config),
|
||||
&config.auth_route_config,
|
||||
)
|
||||
.await?
|
||||
else {
|
||||
@@ -1222,7 +1222,7 @@ async fn load_auth(
|
||||
chatgpt_base_url: Option<&str>,
|
||||
keyring_backend_kind: AuthKeyringBackendKind,
|
||||
agent_identity_authapi_base_url: Option<&str>,
|
||||
auth_route_config: Option<&AuthRouteConfig>,
|
||||
auth_route_config: &AuthRouteConfig,
|
||||
) -> std::io::Result<Option<CodexAuth>> {
|
||||
// API key via env var takes precedence over any other auth method.
|
||||
if enable_codex_api_key_env && let Some(api_key) = read_codex_api_key_from_env() {
|
||||
@@ -1859,7 +1859,7 @@ impl AuthManager {
|
||||
chatgpt_base_url.as_deref(),
|
||||
keyring_backend_kind,
|
||||
agent_identity_authapi_base_url.as_deref(),
|
||||
Some(&auth_route_config),
|
||||
&auth_route_config,
|
||||
)
|
||||
.await
|
||||
.ok()
|
||||
@@ -2080,7 +2080,7 @@ impl AuthManager {
|
||||
policy,
|
||||
self.agent_identity_authapi_base_url.as_deref(),
|
||||
forced_chatgpt_workspace_id,
|
||||
Some(&self.auth_route_config),
|
||||
&self.auth_route_config,
|
||||
session_source,
|
||||
)
|
||||
.await;
|
||||
@@ -2099,7 +2099,7 @@ impl AuthManager {
|
||||
policy,
|
||||
self.agent_identity_authapi_base_url.as_deref(),
|
||||
self.forced_chatgpt_workspace_id(),
|
||||
Some(&self.auth_route_config),
|
||||
&self.auth_route_config,
|
||||
session_source,
|
||||
)
|
||||
.await
|
||||
@@ -2218,7 +2218,7 @@ impl AuthManager {
|
||||
self.chatgpt_base_url.as_deref(),
|
||||
self.keyring_backend_kind,
|
||||
self.agent_identity_authapi_base_url.as_deref(),
|
||||
Some(&self.auth_route_config),
|
||||
&self.auth_route_config,
|
||||
)
|
||||
.await
|
||||
.ok()
|
||||
@@ -2478,8 +2478,7 @@ impl AuthManager {
|
||||
let auth_dot_json = self
|
||||
.auth_cached()
|
||||
.and_then(|auth| auth.get_current_auth_json());
|
||||
if let Err(err) =
|
||||
revoke_auth_tokens(auth_dot_json.as_ref(), Some(&self.auth_route_config)).await
|
||||
if let Err(err) = revoke_auth_tokens(auth_dot_json.as_ref(), &self.auth_route_config).await
|
||||
{
|
||||
tracing::warn!("failed to revoke auth tokens during logout: {err}");
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ impl fmt::Debug for PersonalAccessTokenAuth {
|
||||
impl PersonalAccessTokenAuth {
|
||||
pub(super) async fn load(
|
||||
access_token: &str,
|
||||
auth_route_config: Option<&AuthRouteConfig>,
|
||||
auth_route_config: &AuthRouteConfig,
|
||||
) -> std::io::Result<Self> {
|
||||
let authapi_base_url = env::var(CODEX_AUTHAPI_BASE_URL_ENV_VAR)
|
||||
.ok()
|
||||
|
||||
@@ -54,7 +54,7 @@ struct RevokeTokenRequest<'a> {
|
||||
|
||||
pub(super) async fn revoke_auth_tokens(
|
||||
auth_dot_json: Option<&AuthDotJson>,
|
||||
auth_route_config: Option<&AuthRouteConfig>,
|
||||
auth_route_config: &AuthRouteConfig,
|
||||
) -> Result<(), std::io::Error> {
|
||||
let Some((token, kind)) = auth_dot_json.and_then(revocable_token) else {
|
||||
return Ok(());
|
||||
|
||||
@@ -166,7 +166,7 @@ pub async fn request_device_code(opts: &ServerOptions) -> std::io::Result<Device
|
||||
let base_url = opts.issuer.trim_end_matches('/');
|
||||
// The route selected for the issuer is reused for all device-auth endpoint paths; the endpoint
|
||||
// paths are not resolved separately.
|
||||
let client = create_raw_auth_client(base_url, opts.auth_route_config.as_ref())?;
|
||||
let client = create_raw_auth_client(base_url, &opts.auth_route_config)?;
|
||||
let api_base_url = format!("{base_url}/api/accounts");
|
||||
let uc = request_user_code(&client, &api_base_url, &opts.client_id).await?;
|
||||
|
||||
@@ -183,7 +183,7 @@ pub async fn complete_device_code_login(
|
||||
device_code: DeviceCode,
|
||||
) -> std::io::Result<()> {
|
||||
let base_url = opts.issuer.trim_end_matches('/');
|
||||
let client = create_raw_auth_client(base_url, opts.auth_route_config.as_ref())?;
|
||||
let client = create_raw_auth_client(base_url, &opts.auth_route_config)?;
|
||||
let api_base_url = format!("{base_url}/api/accounts");
|
||||
|
||||
let code_resp = poll_for_token(
|
||||
@@ -207,7 +207,7 @@ pub async fn complete_device_code_login(
|
||||
&redirect_uri,
|
||||
&pkce,
|
||||
&code_resp.authorization_code,
|
||||
opts.auth_route_config.as_ref(),
|
||||
&opts.auth_route_config,
|
||||
)
|
||||
.await
|
||||
.map_err(|err| std::io::Error::other(format!("device code exchange failed: {err}")))?;
|
||||
|
||||
@@ -77,7 +77,7 @@ pub struct ServerOptions {
|
||||
pub login_success_page: LoginSuccessPage,
|
||||
pub cli_auth_credentials_store_mode: AuthCredentialsStoreMode,
|
||||
pub auth_keyring_backend_kind: AuthKeyringBackendKind,
|
||||
pub auth_route_config: Option<AuthRouteConfig>,
|
||||
pub auth_route_config: AuthRouteConfig,
|
||||
}
|
||||
|
||||
impl ServerOptions {
|
||||
@@ -88,7 +88,7 @@ impl ServerOptions {
|
||||
forced_chatgpt_workspace_id: Option<Vec<String>>,
|
||||
cli_auth_credentials_store_mode: AuthCredentialsStoreMode,
|
||||
auth_keyring_backend_kind: AuthKeyringBackendKind,
|
||||
auth_route_config: Option<AuthRouteConfig>,
|
||||
auth_route_config: AuthRouteConfig,
|
||||
) -> Self {
|
||||
Self {
|
||||
codex_home,
|
||||
@@ -387,7 +387,7 @@ async fn process_request(
|
||||
redirect_uri,
|
||||
pkce,
|
||||
&code,
|
||||
opts.auth_route_config.as_ref(),
|
||||
&opts.auth_route_config,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -409,7 +409,7 @@ async fn process_request(
|
||||
&opts.issuer,
|
||||
&opts.client_id,
|
||||
&tokens.id_token,
|
||||
opts.auth_route_config.as_ref(),
|
||||
&opts.auth_route_config,
|
||||
)
|
||||
.await
|
||||
.ok();
|
||||
@@ -789,7 +789,7 @@ pub(crate) async fn exchange_code_for_tokens(
|
||||
redirect_uri: &str,
|
||||
pkce: &PkceCodes,
|
||||
code: &str,
|
||||
auth_route_config: Option<&AuthRouteConfig>,
|
||||
auth_route_config: &AuthRouteConfig,
|
||||
) -> io::Result<ExchangedTokens> {
|
||||
#[derive(serde::Deserialize)]
|
||||
struct TokenResponse {
|
||||
@@ -1114,7 +1114,7 @@ pub(crate) async fn obtain_api_key(
|
||||
issuer: &str,
|
||||
client_id: &str,
|
||||
id_token: &str,
|
||||
auth_route_config: Option<&AuthRouteConfig>,
|
||||
auth_route_config: &AuthRouteConfig,
|
||||
) -> io::Result<String> {
|
||||
// Token exchange for an API key access token
|
||||
#[derive(serde::Deserialize)]
|
||||
|
||||
@@ -4,6 +4,9 @@ use base64::Engine;
|
||||
use chrono::Duration;
|
||||
use chrono::Utc;
|
||||
use codex_config::types::AuthCredentialsStoreMode;
|
||||
use codex_http_client::HttpClientFactory;
|
||||
use codex_http_client::OutboundProxyPolicy;
|
||||
use codex_http_client::cache_system_proxy_route_for_test;
|
||||
use codex_login::AuthDotJson;
|
||||
use codex_login::AuthKeyringBackendKind;
|
||||
use codex_login::AuthManager;
|
||||
@@ -21,7 +24,10 @@ use pretty_assertions::assert_eq;
|
||||
use serde::Serialize;
|
||||
use serde_json::json;
|
||||
use std::ffi::OsString;
|
||||
use std::net::TcpListener;
|
||||
use std::process::Command;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration as StdDuration;
|
||||
use tempfile::TempDir;
|
||||
use wiremock::Mock;
|
||||
use wiremock::MockServer;
|
||||
@@ -31,6 +37,147 @@ use wiremock::matchers::path;
|
||||
|
||||
const INITIAL_ACCESS_TOKEN: &str = "initial-access-token";
|
||||
const INITIAL_REFRESH_TOKEN: &str = "initial-refresh-token";
|
||||
const SYSTEM_PROXY_TEST_ENDPOINT: &str = "http://auth-proxy.invalid/oauth/token";
|
||||
const SYSTEM_PROXY_TEST_SUBPROCESS_ENV_VAR: &str = "CODEX_AUTH_SYSTEM_PROXY_TEST_SUBPROCESS";
|
||||
const SYSTEM_PROXY_TEST_PROXY_URL_ENV_VAR: &str = "CODEX_AUTH_SYSTEM_PROXY_TEST_PROXY_URL";
|
||||
const SYSTEM_PROXY_TEST_NAME: &str =
|
||||
"suite::auth_refresh::refresh_token_honors_respect_system_proxy";
|
||||
const PROXY_ENV_KEYS: [&str; 8] = [
|
||||
"HTTP_PROXY",
|
||||
"http_proxy",
|
||||
"HTTPS_PROXY",
|
||||
"https_proxy",
|
||||
"ALL_PROXY",
|
||||
"all_proxy",
|
||||
"NO_PROXY",
|
||||
"no_proxy",
|
||||
];
|
||||
|
||||
#[serial_test::serial(auth_env)]
|
||||
#[tokio::test]
|
||||
async fn refresh_token_honors_respect_system_proxy() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
if std::env::var_os(SYSTEM_PROXY_TEST_SUBPROCESS_ENV_VAR).is_none() {
|
||||
let response_body =
|
||||
r#"{"access_token":"new-access-token","refresh_token":"new-refresh-token"}"#;
|
||||
let listener = TcpListener::bind(("127.0.0.1", 0))?;
|
||||
let proxy_address = listener.local_addr()?;
|
||||
let proxy = tiny_http::Server::from_listener(listener, None)
|
||||
.map_err(|error| anyhow::anyhow!("failed to start auth proxy: {error}"))?;
|
||||
let proxy_thread = std::thread::spawn(move || {
|
||||
let mut request = proxy
|
||||
.recv_timeout(StdDuration::from_secs(30))
|
||||
.expect("proxy should receive an auth refresh request")
|
||||
.expect("proxy should receive a request before the timeout");
|
||||
let request_line = format!("{} {} HTTP/1.1", request.method(), request.url());
|
||||
let mut request_body = String::new();
|
||||
request
|
||||
.as_reader()
|
||||
.read_to_string(&mut request_body)
|
||||
.expect("proxy should read request body");
|
||||
let content_type = tiny_http::Header::from_bytes(
|
||||
b"Content-Type".as_slice(),
|
||||
b"application/json".as_slice(),
|
||||
)
|
||||
.expect("content type header should be valid");
|
||||
request
|
||||
.respond(tiny_http::Response::from_string(response_body).with_header(content_type))
|
||||
.expect("proxy should write response");
|
||||
(request_line, request_body)
|
||||
});
|
||||
|
||||
let proxy_url = format!("http://{proxy_address}");
|
||||
let mut command = Command::new(std::env::current_exe()?);
|
||||
command.arg("--exact").arg(SYSTEM_PROXY_TEST_NAME);
|
||||
for key in PROXY_ENV_KEYS {
|
||||
command.env_remove(key);
|
||||
}
|
||||
command
|
||||
.env(SYSTEM_PROXY_TEST_SUBPROCESS_ENV_VAR, "1")
|
||||
.env(SYSTEM_PROXY_TEST_PROXY_URL_ENV_VAR, proxy_url)
|
||||
.env(CLIENT_ID_OVERRIDE_ENV_VAR, "staging-client")
|
||||
.env_remove(REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR);
|
||||
|
||||
let output = command.output()?;
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"subprocess test `{SYSTEM_PROXY_TEST_NAME}` failed\nstdout:\n{}\nstderr:\n{}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr),
|
||||
);
|
||||
let (proxy_request_line, proxy_request_body) = proxy_thread
|
||||
.join()
|
||||
.expect("proxy thread should finish after the child test");
|
||||
assert_eq!(
|
||||
proxy_request_line,
|
||||
"POST http://auth-proxy.invalid/oauth/token HTTP/1.1"
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<serde_json::Value>(&proxy_request_body)?,
|
||||
json!({
|
||||
"client_id": "staging-client",
|
||||
"grant_type": "refresh_token",
|
||||
"refresh_token": INITIAL_REFRESH_TOKEN,
|
||||
})
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let codex_home = TempDir::new()?;
|
||||
let proxy_url = std::env::var(SYSTEM_PROXY_TEST_PROXY_URL_ENV_VAR)
|
||||
.context("proxy URL should be set in the auth refresh test subprocess")?;
|
||||
cache_system_proxy_route_for_test(SYSTEM_PROXY_TEST_ENDPOINT, proxy_url);
|
||||
let _endpoint_guard = EnvGuard::set(
|
||||
REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR,
|
||||
SYSTEM_PROXY_TEST_ENDPOINT.to_string(),
|
||||
);
|
||||
let auth_manager = AuthManager::shared(
|
||||
codex_home.path().to_path_buf(),
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
AuthCredentialsStoreMode::File,
|
||||
/*forced_chatgpt_workspace_id*/ None,
|
||||
/*chatgpt_base_url*/ None,
|
||||
AuthKeyringBackendKind::default(),
|
||||
/*auth_route_config*/
|
||||
codex_login::AuthRouteConfig::from_http_client_factory(HttpClientFactory::new(
|
||||
OutboundProxyPolicy::RespectSystemProxy,
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN);
|
||||
let initial_auth = AuthDotJson {
|
||||
auth_mode: Some(AuthMode::Chatgpt),
|
||||
openai_api_key: None,
|
||||
tokens: Some(initial_tokens.clone()),
|
||||
last_refresh: Some(Utc::now() - Duration::days(1)),
|
||||
agent_identity: None,
|
||||
personal_access_token: None,
|
||||
bedrock_api_key: None,
|
||||
};
|
||||
save_auth(
|
||||
codex_home.path(),
|
||||
&initial_auth,
|
||||
AuthCredentialsStoreMode::File,
|
||||
AuthKeyringBackendKind::default(),
|
||||
)?;
|
||||
auth_manager.reload().await;
|
||||
|
||||
auth_manager
|
||||
.refresh_token_from_authority()
|
||||
.await
|
||||
.context("refresh should succeed through the configured proxy")?;
|
||||
|
||||
let refreshed_auth = auth_manager.auth().await.context("auth should be cached")?;
|
||||
let expected_tokens = TokenData {
|
||||
access_token: "new-access-token".to_string(),
|
||||
refresh_token: "new-refresh-token".to_string(),
|
||||
..initial_tokens
|
||||
};
|
||||
assert_eq!(refreshed_auth.get_token_data()?, expected_tokens);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[serial_test::serial(auth_env)]
|
||||
#[tokio::test]
|
||||
|
||||
@@ -112,7 +112,7 @@ fn server_opts(
|
||||
/*forced_chatgpt_workspace_id*/ None,
|
||||
cli_auth_credentials_store_mode,
|
||||
AuthKeyringBackendKind::default(),
|
||||
/*auth_route_config*/ None,
|
||||
codex_login::test_support::transport_default_auth_route_config(),
|
||||
);
|
||||
opts.issuer = issuer;
|
||||
opts.open_browser = false;
|
||||
@@ -278,7 +278,7 @@ async fn device_code_login_integration_persists_without_api_key_on_exchange_fail
|
||||
/*forced_chatgpt_workspace_id*/ None,
|
||||
AuthCredentialsStoreMode::File,
|
||||
AuthKeyringBackendKind::default(),
|
||||
/*auth_route_config*/ None,
|
||||
codex_login::test_support::transport_default_auth_route_config(),
|
||||
);
|
||||
opts.issuer = issuer;
|
||||
opts.open_browser = false;
|
||||
@@ -334,7 +334,7 @@ async fn device_code_login_integration_handles_error_payload() -> anyhow::Result
|
||||
/*forced_chatgpt_workspace_id*/ None,
|
||||
AuthCredentialsStoreMode::File,
|
||||
AuthKeyringBackendKind::default(),
|
||||
/*auth_route_config*/ None,
|
||||
codex_login::test_support::transport_default_auth_route_config(),
|
||||
);
|
||||
opts.issuer = issuer;
|
||||
opts.open_browser = false;
|
||||
|
||||
@@ -125,7 +125,7 @@ async fn end_to_end_login_flow_persists_auth_json() -> Result<()> {
|
||||
let opts = ServerOptions {
|
||||
codex_home: server_home,
|
||||
cli_auth_credentials_store_mode: AuthCredentialsStoreMode::File,
|
||||
auth_route_config: None,
|
||||
auth_route_config: codex_login::test_support::transport_default_auth_route_config(),
|
||||
client_id: codex_login::CLIENT_ID.to_string(),
|
||||
issuer,
|
||||
port: 0,
|
||||
@@ -190,7 +190,7 @@ async fn hosted_login_redirects_to_configured_open_app_url() -> Result<()> {
|
||||
let server = run_login_server(ServerOptions {
|
||||
codex_home: tmp.path().to_path_buf(),
|
||||
cli_auth_credentials_store_mode: AuthCredentialsStoreMode::File,
|
||||
auth_route_config: None,
|
||||
auth_route_config: codex_login::test_support::transport_default_auth_route_config(),
|
||||
client_id: codex_login::CLIENT_ID.to_string(),
|
||||
issuer,
|
||||
port: 0,
|
||||
@@ -243,7 +243,7 @@ async fn creates_missing_codex_home_dir() -> Result<()> {
|
||||
let opts = ServerOptions {
|
||||
codex_home: server_home,
|
||||
cli_auth_credentials_store_mode: AuthCredentialsStoreMode::File,
|
||||
auth_route_config: None,
|
||||
auth_route_config: codex_login::test_support::transport_default_auth_route_config(),
|
||||
client_id: codex_login::CLIENT_ID.to_string(),
|
||||
issuer,
|
||||
port: 0,
|
||||
@@ -286,7 +286,7 @@ async fn login_server_includes_forced_workspaces_as_one_query_param() -> Result<
|
||||
let opts = ServerOptions {
|
||||
codex_home,
|
||||
cli_auth_credentials_store_mode: AuthCredentialsStoreMode::File,
|
||||
auth_route_config: None,
|
||||
auth_route_config: codex_login::test_support::transport_default_auth_route_config(),
|
||||
client_id: codex_login::CLIENT_ID.to_string(),
|
||||
issuer,
|
||||
port: 0,
|
||||
@@ -330,7 +330,7 @@ async fn forced_chatgpt_workspace_id_mismatch_blocks_login() -> Result<()> {
|
||||
let opts = ServerOptions {
|
||||
codex_home: codex_home.clone(),
|
||||
cli_auth_credentials_store_mode: AuthCredentialsStoreMode::File,
|
||||
auth_route_config: None,
|
||||
auth_route_config: codex_login::test_support::transport_default_auth_route_config(),
|
||||
client_id: codex_login::CLIENT_ID.to_string(),
|
||||
issuer,
|
||||
port: 0,
|
||||
@@ -393,7 +393,7 @@ async fn oauth_access_denied_missing_entitlement_blocks_login_with_clear_error()
|
||||
let opts = ServerOptions {
|
||||
codex_home: codex_home.clone(),
|
||||
cli_auth_credentials_store_mode: AuthCredentialsStoreMode::File,
|
||||
auth_route_config: None,
|
||||
auth_route_config: codex_login::test_support::transport_default_auth_route_config(),
|
||||
client_id: codex_login::CLIENT_ID.to_string(),
|
||||
issuer,
|
||||
port: 0,
|
||||
@@ -464,7 +464,7 @@ async fn oauth_access_denied_unknown_reason_uses_generic_error_page() -> Result<
|
||||
let opts = ServerOptions {
|
||||
codex_home: codex_home.clone(),
|
||||
cli_auth_credentials_store_mode: AuthCredentialsStoreMode::File,
|
||||
auth_route_config: None,
|
||||
auth_route_config: codex_login::test_support::transport_default_auth_route_config(),
|
||||
client_id: codex_login::CLIENT_ID.to_string(),
|
||||
issuer,
|
||||
port: 0,
|
||||
@@ -575,7 +575,7 @@ async fn falls_back_to_registered_fallback_port_when_default_port_is_in_use() ->
|
||||
/*forced_chatgpt_workspace_id*/ None,
|
||||
AuthCredentialsStoreMode::File,
|
||||
AuthKeyringBackendKind::default(),
|
||||
/*auth_route_config*/ None,
|
||||
codex_login::test_support::transport_default_auth_route_config(),
|
||||
);
|
||||
opts.issuer = issuer;
|
||||
opts.open_browser = false;
|
||||
@@ -614,7 +614,7 @@ async fn cancels_previous_login_server_when_port_is_in_use() -> Result<()> {
|
||||
let first_opts = ServerOptions {
|
||||
codex_home: first_codex_home,
|
||||
cli_auth_credentials_store_mode: AuthCredentialsStoreMode::File,
|
||||
auth_route_config: None,
|
||||
auth_route_config: codex_login::test_support::transport_default_auth_route_config(),
|
||||
client_id: codex_login::CLIENT_ID.to_string(),
|
||||
issuer: issuer.clone(),
|
||||
port: 0,
|
||||
@@ -638,7 +638,7 @@ async fn cancels_previous_login_server_when_port_is_in_use() -> Result<()> {
|
||||
let second_opts = ServerOptions {
|
||||
codex_home: second_codex_home,
|
||||
cli_auth_credentials_store_mode: AuthCredentialsStoreMode::File,
|
||||
auth_route_config: None,
|
||||
auth_route_config: codex_login::test_support::transport_default_auth_route_config(),
|
||||
client_id: codex_login::CLIENT_ID.to_string(),
|
||||
issuer,
|
||||
port: login_port,
|
||||
|
||||
@@ -61,7 +61,7 @@ async fn logout_with_revoke_revokes_refresh_token_then_removes_auth() -> Result<
|
||||
codex_home.path(),
|
||||
AuthCredentialsStoreMode::File,
|
||||
AuthKeyringBackendKind::default(),
|
||||
/*auth_route_config*/ None,
|
||||
&codex_login::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -120,7 +120,7 @@ async fn logout_with_revoke_uses_stored_auth_when_access_token_env_is_set() -> R
|
||||
codex_home.path(),
|
||||
AuthCredentialsStoreMode::File,
|
||||
AuthKeyringBackendKind::default(),
|
||||
/*auth_route_config*/ None,
|
||||
&codex_login::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -163,7 +163,7 @@ async fn logout_with_revoke_removes_auth_when_revoke_fails() -> Result<()> {
|
||||
codex_home.path(),
|
||||
AuthCredentialsStoreMode::File,
|
||||
AuthKeyringBackendKind::default(),
|
||||
/*auth_route_config*/ None,
|
||||
&codex_login::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -355,7 +355,7 @@ mod tests {
|
||||
task_id: Some("task-run-1".to_string()),
|
||||
},
|
||||
"https://auth.openai.com/api/accounts",
|
||||
/*auth_route_config*/ None,
|
||||
&codex_login::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await
|
||||
.expect("agent identity auth record should include task id")
|
||||
|
||||
@@ -271,7 +271,7 @@ c2ln",
|
||||
AuthCredentialsStoreMode::File,
|
||||
/*chatgpt_base_url*/ None,
|
||||
AuthKeyringBackendKind::default(),
|
||||
/*auth_route_config*/ None,
|
||||
&codex_login::test_support::transport_default_auth_route_config(),
|
||||
)
|
||||
.await
|
||||
.expect("auth should load")
|
||||
|
||||
Reference in New Issue
Block a user