mirror of
https://github.com/openai/codex.git
synced 2026-10-23 18:08:45 +00:00
Propagate authentication header failures before sending requests (#55607)
## Why Synchronous auth header construction could silently return no credentials when managed authentication became unavailable. Callers need an error to avoid sending requests with missing or partial authentication. ## What changed - Make `AuthProvider::add_auth_headers`, `to_auth_headers`, and auth header telemetry fallible, and propagate failures through request and WebSocket setup. - Return a transient error when managed authentication is unavailable, and preserve network policy denials through auth error conversion. - Retry transient MCP authentication errors while treating build errors and policy denials as terminal. - Cancel local MCP event streams before resolving authentication so cleanup still works when credentials become unavailable. ## Testing Add regression coverage for rejected partial authentication, async header resolution, terminal auth errors without sending requests, managed account changes, MCP retry classification, and local stream cancellation after authentication becomes unavailable. GitOrigin-RevId: 39d981e232cba1e302de502a2fa6239101114ed1
This commit is contained in:
@@ -1043,7 +1043,7 @@ pub async fn fetch_recommended_plugins(
|
||||
config.http_request(Method::GET, &url),
|
||||
auth,
|
||||
&config.product_sku,
|
||||
)
|
||||
)?
|
||||
.timeout(RECOMMENDED_PLUGINS_TIMEOUT);
|
||||
let response: RecommendedPluginsResponse = send_and_decode(request, &url).await?;
|
||||
Ok(recommended_plugins_mode(response))
|
||||
@@ -1415,7 +1415,7 @@ pub async fn fetch_remote_plugin_skill_detail(
|
||||
config.http_request(Method::GET, &url),
|
||||
auth,
|
||||
&config.product_sku,
|
||||
);
|
||||
)?;
|
||||
let response: RemotePluginSkillDetailResponse = send_and_decode(request, &url).await?;
|
||||
if response.plugin_id != plugin_id {
|
||||
return Err(RemotePluginCatalogError::UnexpectedPluginId {
|
||||
@@ -1626,7 +1626,7 @@ async fn install_remote_plugin_inner(
|
||||
config.http_request(Method::POST, &url),
|
||||
auth,
|
||||
&config.product_sku,
|
||||
);
|
||||
)?;
|
||||
let request = if let Some(install_attempt_id) = install_attempt_id {
|
||||
request.json(&RemotePluginInstallRequest { install_attempt_id })
|
||||
} else {
|
||||
@@ -1723,7 +1723,7 @@ pub async fn uninstall_remote_plugin(
|
||||
config.http_request(Method::POST, &url),
|
||||
auth,
|
||||
&config.product_sku,
|
||||
);
|
||||
)?;
|
||||
let response: RemotePluginMutationResponse = send_and_decode(request, &url).await?;
|
||||
if response.id != remote_plugin_id {
|
||||
return Err(RemotePluginCatalogError::UnexpectedPluginId {
|
||||
@@ -2221,7 +2221,7 @@ async fn get_remote_plugin_list_page(
|
||||
config.http_request(Method::GET, &url),
|
||||
auth,
|
||||
&config.product_sku,
|
||||
);
|
||||
)?;
|
||||
send_and_decode(request, &url).await
|
||||
}
|
||||
|
||||
@@ -2243,7 +2243,7 @@ async fn get_remote_shared_workspace_plugins_page(
|
||||
config.http_request(Method::GET, &url),
|
||||
auth,
|
||||
&config.product_sku,
|
||||
);
|
||||
)?;
|
||||
send_and_decode(request, &url).await
|
||||
}
|
||||
|
||||
@@ -2279,7 +2279,7 @@ async fn get_remote_plugin_installed_page(
|
||||
config.http_request(Method::GET, &url),
|
||||
auth,
|
||||
&config.product_sku,
|
||||
);
|
||||
)?;
|
||||
send_and_decode(request, &url).await
|
||||
}
|
||||
|
||||
@@ -2318,7 +2318,7 @@ async fn fetch_plugin_detail_with_timeout(
|
||||
config.http_request(Method::GET, &url),
|
||||
auth,
|
||||
&config.product_sku,
|
||||
)
|
||||
)?
|
||||
.timeout(timeout);
|
||||
send_and_decode(request, &url).await
|
||||
}
|
||||
@@ -2358,11 +2358,16 @@ fn authenticated_request(
|
||||
request: RequestBuilder,
|
||||
auth: &CodexAuth,
|
||||
product_sku: &str,
|
||||
) -> RequestBuilder {
|
||||
request
|
||||
) -> Result<RequestBuilder, RemotePluginCatalogError> {
|
||||
Ok(request
|
||||
.timeout(REMOTE_PLUGIN_CATALOG_TIMEOUT)
|
||||
.headers(codex_model_provider::auth_provider_from_auth(auth).to_auth_headers())
|
||||
.header(OAI_PRODUCT_SKU_HEADER, product_sku)
|
||||
.headers(
|
||||
codex_model_provider::auth_provider_from_auth(auth)
|
||||
.to_auth_headers()
|
||||
.map_err(std::io::Error::other)
|
||||
.map_err(RemotePluginCatalogError::AuthToken)?,
|
||||
)
|
||||
.header(OAI_PRODUCT_SKU_HEADER, product_sku))
|
||||
}
|
||||
|
||||
async fn send_and_decode<T: for<'de> Deserialize<'de>>(
|
||||
|
||||
@@ -64,7 +64,7 @@ pub async fn search_remote_plugins(
|
||||
config.http_request(Method::GET, &url),
|
||||
auth,
|
||||
&config.product_sku,
|
||||
);
|
||||
)?;
|
||||
let response: RemotePluginListResponse = send_and_decode(request, &url_for_error)
|
||||
.await
|
||||
.map_err(|error| match error {
|
||||
|
||||
@@ -289,7 +289,7 @@ pub async fn delete_remote_plugin_share(
|
||||
config.http_request(Method::DELETE, &url),
|
||||
auth,
|
||||
&config.product_sku,
|
||||
);
|
||||
)?;
|
||||
send_and_expect_status(request, &url, &[StatusCode::NO_CONTENT]).await?;
|
||||
if let Err(err) = local_paths::remove_plugin_share_local_path(codex_home, remote_plugin_id) {
|
||||
warn!(
|
||||
@@ -326,7 +326,7 @@ pub async fn update_remote_plugin_share_targets(
|
||||
config.http_request(Method::PUT, &url),
|
||||
auth,
|
||||
&config.product_sku,
|
||||
)
|
||||
)?
|
||||
.json(&RemotePluginShareUpdateTargetsRequest {
|
||||
discoverability,
|
||||
targets,
|
||||
@@ -401,7 +401,7 @@ async fn get_created_workspace_plugins_page(
|
||||
config.http_request(Method::GET, &url),
|
||||
auth,
|
||||
&config.product_sku,
|
||||
);
|
||||
)?;
|
||||
send_and_decode(request, &url).await
|
||||
}
|
||||
|
||||
@@ -418,7 +418,7 @@ async fn create_workspace_plugin_upload(
|
||||
config.http_request(Method::POST, &url),
|
||||
auth,
|
||||
&config.product_sku,
|
||||
)
|
||||
)?
|
||||
.json(&RemoteWorkspacePluginUploadUrlRequest {
|
||||
filename,
|
||||
mime_type: "application/gzip",
|
||||
@@ -480,7 +480,7 @@ async fn finalize_workspace_plugin_upload(
|
||||
config.http_request(Method::POST, &url),
|
||||
auth,
|
||||
&config.product_sku,
|
||||
)
|
||||
)?
|
||||
.json(&body);
|
||||
send_and_decode(request, &url).await
|
||||
}
|
||||
|
||||
@@ -95,6 +95,8 @@ impl RemotePluginMutationError {
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum RemotePluginFetchError {
|
||||
#[error("failed to authenticate remote featured plugin request: {0}")]
|
||||
AuthToken(#[source] std::io::Error),
|
||||
#[error("invalid chatgpt base url for remote featured plugin request: {0}")]
|
||||
InvalidBaseUrl(#[source] url::ParseError),
|
||||
|
||||
@@ -138,8 +140,12 @@ pub async fn fetch_remote_featured_plugin_ids(
|
||||
.timeout(REMOTE_FEATURED_PLUGIN_FETCH_TIMEOUT);
|
||||
|
||||
if let Some(auth) = auth.filter(|auth| auth.uses_codex_backend()) {
|
||||
request =
|
||||
request.headers(codex_model_provider::auth_provider_from_auth(auth).to_auth_headers());
|
||||
request = request.headers(
|
||||
codex_model_provider::auth_provider_from_auth(auth)
|
||||
.to_auth_headers()
|
||||
.map_err(std::io::Error::other)
|
||||
.map_err(RemotePluginFetchError::AuthToken)?,
|
||||
);
|
||||
}
|
||||
|
||||
let response = request
|
||||
@@ -202,7 +208,12 @@ async fn post_remote_plugin_mutation(
|
||||
let request = config
|
||||
.http_request(Method::POST, &url)
|
||||
.timeout(REMOTE_PLUGIN_MUTATION_TIMEOUT)
|
||||
.headers(codex_model_provider::auth_provider_from_auth(auth).to_auth_headers());
|
||||
.headers(
|
||||
codex_model_provider::auth_provider_from_auth(auth)
|
||||
.to_auth_headers()
|
||||
.map_err(std::io::Error::other)
|
||||
.map_err(RemotePluginMutationError::AuthToken)?,
|
||||
);
|
||||
|
||||
let response = request
|
||||
.send()
|
||||
|
||||
Reference in New Issue
Block a user