login: use route-aware HTTP client abstraction

This commit is contained in:
Michael Bolin
2026-07-09 11:53:22 -07:00
parent 2e8c3756f9
commit a45cdd65cd
7 changed files with 33 additions and 18 deletions

1
codex-rs/Cargo.lock generated
View File

@@ -3350,6 +3350,7 @@ dependencies = [
"codex-terminal-detection",
"codex-utils-template",
"core_test_support",
"http 1.4.0",
"jsonwebtoken",
"keyring",
"once_cell",

View File

@@ -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<HttpClient, BuildRouteAwareHttpClientError> {
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,

View File

@@ -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 }

View File

@@ -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<reqwest::Client, BuildRouteAwareHttpClientError> {
auth_http_client_factory(auth_route_config).build_reqwest_client(
reqwest::Client::builder(),
endpoint,
ClientRouteClass::Auth,
)
) -> Result<HttpClient, BuildRouteAwareHttpClientError> {
auth_http_client_factory(auth_route_config).build_client(endpoint, ClientRouteClass::Auth)
}
/// Builds the default Codex reqwest client for an auth endpoint.

View File

@@ -155,6 +155,9 @@ fn derive_revoke_token_endpoint(refresh_endpoint: &str) -> Option<String> {
#[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(),

View File

@@ -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<UserCodeResp> {
@@ -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<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 = 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 uc = request_user_code(&client, &api_base_url, &opts.client_id).await?;
@@ -182,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 = 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(

View File

@@ -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")