From a45cdd65cd86d9477c78c8368545e8bfd9eb1742 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Thu, 9 Jul 2026 11:53:22 -0700 Subject: [PATCH] login: use route-aware HTTP client abstraction --- codex-rs/Cargo.lock | 1 + codex-rs/http-client/src/outbound_proxy.rs | 11 +++++++++++ codex-rs/login/Cargo.toml | 1 + codex-rs/login/src/auth/default_client.rs | 12 ++++-------- codex-rs/login/src/auth/revoke.rs | 7 ++++++- codex-rs/login/src/device_code_auth.rs | 13 +++++++------ codex-rs/login/src/server.rs | 6 +++--- 7 files changed, 33 insertions(+), 18 deletions(-) diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 023c5dfaa3..ea08c04e96 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -3350,6 +3350,7 @@ dependencies = [ "codex-terminal-detection", "codex-utils-template", "core_test_support", + "http 1.4.0", "jsonwebtoken", "keyring", "once_cell", diff --git a/codex-rs/http-client/src/outbound_proxy.rs b/codex-rs/http-client/src/outbound_proxy.rs index 7bdfd745b7..9e29b19090 100644 --- a/codex-rs/http-client/src/outbound_proxy.rs +++ b/codex-rs/http-client/src/outbound_proxy.rs @@ -15,6 +15,7 @@ use std::time::Instant; use crate::custom_ca::BuildCustomCaTransportError; use crate::custom_ca::build_reqwest_client_with_custom_ca; +use crate::default_client::HttpClient; #[cfg(any(target_os = "windows", target_os = "macos"))] use sha2::Digest; #[cfg(any(target_os = "windows", target_os = "macos"))] @@ -158,6 +159,16 @@ impl HttpClientFactory { ) } + /// Builds an HTTP client for a concrete outbound route. + pub fn build_client( + &self, + request_url: &str, + route_class: ClientRouteClass, + ) -> Result { + self.build_reqwest_client(reqwest::Client::builder(), request_url, route_class) + .map(HttpClient::new) + } + /// Builds a reqwest client for a concrete outbound route. pub fn build_reqwest_client( &self, diff --git a/codex-rs/login/Cargo.toml b/codex-rs/login/Cargo.toml index 3eb253d7dc..9e4dd17722 100644 --- a/codex-rs/login/Cargo.toml +++ b/codex-rs/login/Cargo.toml @@ -20,6 +20,7 @@ codex-protocol = { workspace = true } codex-secrets = { workspace = true } codex-terminal-detection = { workspace = true } codex-utils-template = { workspace = true } +http = { workspace = true } once_cell = { workspace = true } os_info = { workspace = true } rand = { workspace = true } diff --git a/codex-rs/login/src/auth/default_client.rs b/codex-rs/login/src/auth/default_client.rs index 0765d227ce..9c5243dcec 100644 --- a/codex-rs/login/src/auth/default_client.rs +++ b/codex-rs/login/src/auth/default_client.rs @@ -295,16 +295,12 @@ fn default_reqwest_client_builder() -> reqwest::ClientBuilder { with_chatgpt_cloudflare_cookie_store(builder) } -/// Builds a raw reqwest client for an auth endpoint without Codex default headers. -pub(crate) fn build_raw_auth_reqwest_client( +/// 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>, -) -> Result { - auth_http_client_factory(auth_route_config).build_reqwest_client( - reqwest::Client::builder(), - endpoint, - ClientRouteClass::Auth, - ) +) -> Result { + auth_http_client_factory(auth_route_config).build_client(endpoint, ClientRouteClass::Auth) } /// Builds the default Codex reqwest client for an auth endpoint. diff --git a/codex-rs/login/src/auth/revoke.rs b/codex-rs/login/src/auth/revoke.rs index ca22adb571..6b2b21dcf8 100644 --- a/codex-rs/login/src/auth/revoke.rs +++ b/codex-rs/login/src/auth/revoke.rs @@ -155,6 +155,9 @@ fn derive_revoke_token_endpoint(refresh_endpoint: &str) -> Option { #[cfg(test)] mod tests { use super::*; + use codex_http_client::ClientRouteClass; + use codex_http_client::HttpClientFactory; + use codex_http_client::OutboundProxyPolicy; use core_test_support::skip_if_no_network; use wiremock::Mock; use wiremock::MockServer; @@ -181,8 +184,10 @@ mod tests { .mount(&server) .await; - let client = HttpClient::new(reqwest::Client::new()); let endpoint = format!("{}/oauth/revoke", server.uri()); + let client = HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault) + .build_client(&endpoint, ClientRouteClass::Auth) + .expect("test HTTP client should build"); let error = revoke_oauth_token( &client, endpoint.as_str(), diff --git a/codex-rs/login/src/device_code_auth.rs b/codex-rs/login/src/device_code_auth.rs index e7a31916a5..0d9c7244a1 100644 --- a/codex-rs/login/src/device_code_auth.rs +++ b/codex-rs/login/src/device_code_auth.rs @@ -1,4 +1,5 @@ -use reqwest::StatusCode; +use codex_http_client::HttpClient; +use http::StatusCode; use serde::Deserialize; use serde::Serialize; use serde::de::Deserializer; @@ -6,7 +7,7 @@ use serde::de::{self}; use std::time::Duration; use std::time::Instant; -use crate::default_client::build_raw_auth_reqwest_client; +use crate::default_client::create_raw_auth_client; use crate::pkce::PkceCodes; use crate::server::ServerOptions; use std::io; @@ -60,7 +61,7 @@ struct CodeSuccessResp { /// Request the user code and polling interval. async fn request_user_code( - client: &reqwest::Client, + client: &HttpClient, auth_base_url: &str, client_id: &str, ) -> std::io::Result { @@ -97,7 +98,7 @@ async fn request_user_code( /// Poll token endpoint until a code is issued or timeout occurs. async fn poll_for_token( - client: &reqwest::Client, + client: &HttpClient, auth_base_url: &str, device_auth_id: &str, user_code: &str, @@ -165,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 = build_raw_auth_reqwest_client(base_url, opts.auth_route_config.as_ref())?; + let client = create_raw_auth_client(base_url, opts.auth_route_config.as_ref())?; let api_base_url = format!("{base_url}/api/accounts"); let code_resp = poll_for_token( diff --git a/codex-rs/login/src/server.rs b/codex-rs/login/src/server.rs index 01671bf0b1..804d05434e 100644 --- a/codex-rs/login/src/server.rs +++ b/codex-rs/login/src/server.rs @@ -27,7 +27,7 @@ use std::time::Duration; use crate::auth::AuthDotJson; use crate::auth::AuthKeyringBackendKind; use crate::auth::save_auth; -use crate::default_client::build_raw_auth_reqwest_client; +use crate::default_client::create_raw_auth_client; use crate::default_client::originator; use crate::outbound_proxy::AuthRouteConfig; use crate::pkce::PkceCodes; @@ -798,7 +798,7 @@ pub(crate) async fn exchange_code_for_tokens( // The route selected for the issuer is reused for token exchange; the token endpoint path is // not resolved separately. - let client = build_raw_auth_reqwest_client(issuer.trim_end_matches('/'), auth_route_config)?; + let client = create_raw_auth_client(issuer.trim_end_matches('/'), auth_route_config)?; let token_endpoint = format!("{}/oauth/token", issuer.trim_end_matches('/')); info!( issuer = %sanitize_url_for_logging(issuer), @@ -1120,7 +1120,7 @@ pub(crate) async fn obtain_api_key( access_token: String, } let token_endpoint = format!("{}/oauth/token", issuer.trim_end_matches('/')); - let client = build_raw_auth_reqwest_client(&token_endpoint, auth_route_config)?; + let client = create_raw_auth_client(&token_endpoint, auth_route_config)?; let resp = client .post(token_endpoint) .header("Content-Type", "application/x-www-form-urlencoded")