diff --git a/codex-rs/app-server/src/request_processors/account_processor.rs b/codex-rs/app-server/src/request_processors/account_processor.rs index eb1ce2b9ba..e22cfc4c10 100644 --- a/codex-rs/app-server/src/request_processors/account_processor.rs +++ b/codex-rs/app-server/src/request_processors/account_processor.rs @@ -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)] diff --git a/codex-rs/cli/src/login.rs b/codex-rs/cli/src/login.rs index 33640dcb95..f02b419ba9 100644 --- a/codex-rs/cli/src/login.rs +++ b/codex-rs/cli/src/login.rs @@ -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>, cli_auth_credentials_store_mode: AuthCredentialsStoreMode, auth_keyring_backend_kind: AuthKeyringBackendKind, - auth_route_config: Option, + 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; diff --git a/codex-rs/cli/src/main.rs b/codex-rs/cli/src/main.rs index f59c3b1041..297c3297d6 100644 --- a/codex-rs/cli/src/main.rs +++ b/codex-rs/cli/src/main.rs @@ -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)); diff --git a/codex-rs/cli/src/plugin_cmd.rs b/codex-rs/cli/src/plugin_cmd.rs index 6fec1f67ed..e8f95b2be6 100644 --- a/codex-rs/cli/src/plugin_cmd.rs +++ b/codex-rs/cli/src/plugin_cmd.rs @@ -610,7 +610,7 @@ pub(crate) async fn load_cli_auth_mode(config: &Config) -> Option { 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() diff --git a/codex-rs/cloud-config/src/service_tests.rs b/codex-rs/cloud-config/src/service_tests.rs index 6d9bc93a20..42486e67aa 100644 --- a/codex-rs/cloud-config/src/service_tests.rs +++ b/codex-rs/cloud-config/src/service_tests.rs @@ -113,7 +113,7 @@ async fn auth_manager_with_agent_identity_business_plan() -> Arc { 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"), diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index d5dc15d551..479f4b9784 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -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") diff --git a/codex-rs/core/tests/suite/compact_remote.rs b/codex-rs/core/tests/suite/compact_remote.rs index 4cb0e92c8d..222d5bc4b3 100644 --- a/codex-rs/core/tests/suite/compact_remote.rs +++ b/codex-rs/core/tests/suite/compact_remote.rs @@ -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?, )), diff --git a/codex-rs/http-client/src/lib.rs b/codex-rs/http-client/src/lib.rs index 52a8e8a1d5..a8d791bdef 100644 --- a/codex-rs/http-client/src/lib.rs +++ b/codex-rs/http-client/src/lib.rs @@ -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; diff --git a/codex-rs/http-client/src/outbound_proxy.rs b/codex-rs/http-client/src/outbound_proxy.rs index 7eee6de1b6..93c85bb12f 100644 --- a/codex-rs/http-client/src/outbound_proxy.rs +++ b/codex-rs/http-client/src/outbound_proxy.rs @@ -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, cache_key: &str, diff --git a/codex-rs/login/src/auth/agent_identity.rs b/codex-rs/login/src/auth/agent_identity.rs index 0d07439d9f..9894951f04 100644 --- a/codex-rs/login/src/auth/agent_identity.rs +++ b/codex-rs/login/src/auth/agent_identity.rs @@ -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 { 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 { 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 { 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::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 { 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?; diff --git a/codex-rs/login/src/auth/auth_tests.rs b/codex-rs/login/src/auth/auth_tests.rs index da7f6b9d67..fa1120db5e 100644 --- a/codex-rs/login/src/auth/auth_tests.rs +++ b/codex-rs/login/src/auth/auth_tests.rs @@ -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") diff --git a/codex-rs/login/src/auth/default_client.rs b/codex-rs/login/src/auth/default_client.rs index ef3722dad8..bccedffbed 100644 --- a/codex-rs/login/src/auth/default_client.rs +++ b/codex-rs/login/src/auth/default_client.rs @@ -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 { - 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 { 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); diff --git a/codex-rs/login/src/auth/default_client_tests.rs b/codex-rs/login/src/auth/default_client_tests.rs index 23bd54791b..4565f79788 100644 --- a/codex-rs/login/src/auth/default_client_tests.rs +++ b/codex-rs/login/src/auth/default_client_tests.rs @@ -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") diff --git a/codex-rs/login/src/auth/manager.rs b/codex-rs/login/src/auth/manager.rs index 07bedaf656..0b6e8c9e53 100644 --- a/codex-rs/login/src/auth/manager.rs +++ b/codex-rs/login/src/auth/manager.rs @@ -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 { 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> { 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 { 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 { 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 { 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>, - auth_route_config: Option<&AuthRouteConfig>, + auth_route_config: &AuthRouteConfig, session_source: SessionSource, ) -> std::io::Result> { match self { @@ -662,7 +662,7 @@ impl CodexAuth { &self, agent_identity_authapi_base_url: &str, forced_chatgpt_workspace_id: Option>, - auth_route_config: Option<&AuthRouteConfig>, + auth_route_config: &AuthRouteConfig, session_source: SessionSource, ) -> std::io::Result { 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 { 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> { // 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}"); } diff --git a/codex-rs/login/src/auth/personal_access_token.rs b/codex-rs/login/src/auth/personal_access_token.rs index 09c6d5d2b3..dba80c8f94 100644 --- a/codex-rs/login/src/auth/personal_access_token.rs +++ b/codex-rs/login/src/auth/personal_access_token.rs @@ -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 { let authapi_base_url = env::var(CODEX_AUTHAPI_BASE_URL_ENV_VAR) .ok() diff --git a/codex-rs/login/src/auth/revoke.rs b/codex-rs/login/src/auth/revoke.rs index 1b90020938..fa82220ec7 100644 --- a/codex-rs/login/src/auth/revoke.rs +++ b/codex-rs/login/src/auth/revoke.rs @@ -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(()); diff --git a/codex-rs/login/src/device_code_auth.rs b/codex-rs/login/src/device_code_auth.rs index 0d9c7244a1..a909da40f2 100644 --- a/codex-rs/login/src/device_code_auth.rs +++ b/codex-rs/login/src/device_code_auth.rs @@ -166,7 +166,7 @@ pub async fn request_device_code(opts: &ServerOptions) -> std::io::Result 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}")))?; diff --git a/codex-rs/login/src/server.rs b/codex-rs/login/src/server.rs index 2b05e1df48..7f42740899 100644 --- a/codex-rs/login/src/server.rs +++ b/codex-rs/login/src/server.rs @@ -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, + pub auth_route_config: AuthRouteConfig, } impl ServerOptions { @@ -88,7 +88,7 @@ impl ServerOptions { forced_chatgpt_workspace_id: Option>, cli_auth_credentials_store_mode: AuthCredentialsStoreMode, auth_keyring_backend_kind: AuthKeyringBackendKind, - auth_route_config: Option, + 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 { #[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 { // Token exchange for an API key access token #[derive(serde::Deserialize)] diff --git a/codex-rs/login/tests/suite/auth_refresh.rs b/codex-rs/login/tests/suite/auth_refresh.rs index 4dd62e7798..0c7a77791d 100644 --- a/codex-rs/login/tests/suite/auth_refresh.rs +++ b/codex-rs/login/tests/suite/auth_refresh.rs @@ -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::(&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] diff --git a/codex-rs/login/tests/suite/device_code_login.rs b/codex-rs/login/tests/suite/device_code_login.rs index 83cae8af5f..50c1f12367 100644 --- a/codex-rs/login/tests/suite/device_code_login.rs +++ b/codex-rs/login/tests/suite/device_code_login.rs @@ -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; diff --git a/codex-rs/login/tests/suite/login_server_e2e.rs b/codex-rs/login/tests/suite/login_server_e2e.rs index ae55ae998b..4f015cba74 100644 --- a/codex-rs/login/tests/suite/login_server_e2e.rs +++ b/codex-rs/login/tests/suite/login_server_e2e.rs @@ -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, diff --git a/codex-rs/login/tests/suite/logout.rs b/codex-rs/login/tests/suite/logout.rs index e4bcb07f21..2d06522a71 100644 --- a/codex-rs/login/tests/suite/logout.rs +++ b/codex-rs/login/tests/suite/logout.rs @@ -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?; diff --git a/codex-rs/model-provider/src/auth.rs b/codex-rs/model-provider/src/auth.rs index ee1c71a5c3..f8bbea38d8 100644 --- a/codex-rs/model-provider/src/auth.rs +++ b/codex-rs/model-provider/src/auth.rs @@ -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") diff --git a/codex-rs/models-manager/src/manager_tests.rs b/codex-rs/models-manager/src/manager_tests.rs index 9e0dc5242b..3344061c1f 100644 --- a/codex-rs/models-manager/src/manager_tests.rs +++ b/codex-rs/models-manager/src/manager_tests.rs @@ -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")