mirror of
https://github.com/openai/codex.git
synced 2026-11-07 20:30:33 +00:00
Extend runtime auth to account, plugin, and file requests (#59564)
## What changed - Use the shared runtime and effective configuration for account API clients and remote plugin requests, including standalone plugin commands. - Support dual-header and assertion-only authentication for remote plugins and MCP file uploads when runtime agent instances are enabled. - Keep plugin credentials and caches scoped to the thread's account when resuming with different credentials. Bind request pools and multi-step plugin storage operations to the selected account so account replacement revokes stale operations. - Protect runtime credentials on cross-origin redirects while preserving configured routing policies and legacy authentication for custom backends. - Apply server-provided `upload_headers` to file storage uploads. ## Testing Add coverage for shared runtime registration, both authentication modes, configuration reloads, resumed-thread account selection, account replacement during plugin uploads and downloads, and credential stripping on redirects. Extend file upload tests to verify runtime assertions and storage-only upload headers. GitOrigin-RevId: 68aef81051a6f9bf919fffda52f372b108fce805
This commit is contained in:
@@ -17,6 +17,7 @@ use codex_model_provider::is_supported_amazon_bedrock_region;
|
||||
use codex_otel::auth_storage::AuthStorageOriginator;
|
||||
use codex_rmcp_client::EnterpriseOAuthCredentialGuard;
|
||||
|
||||
mod backend_client;
|
||||
mod bedrock_gov_cloud;
|
||||
mod bedrock_setup;
|
||||
mod enterprise_login;
|
||||
@@ -290,6 +291,7 @@ impl AccountRequestProcessor {
|
||||
.await
|
||||
{
|
||||
Ok(config) => {
|
||||
let runtime_auth = thread_manager.runtime_auth_for_config(&config);
|
||||
Self::spawn_effective_plugins_changed_task(
|
||||
Arc::clone(thread_manager),
|
||||
config_manager.clone(),
|
||||
@@ -311,6 +313,10 @@ impl AccountRequestProcessor {
|
||||
&plugins_config,
|
||||
Some(Arc::clone(&on_effective_plugins_changed)),
|
||||
);
|
||||
if let Err(err) = runtime_auth {
|
||||
warn!("failed to initialize runtime auth for remote plugin refresh: {err}");
|
||||
return;
|
||||
}
|
||||
thread_manager
|
||||
.plugins_manager()
|
||||
.maybe_start_remote_plugin_caches_refresh(
|
||||
@@ -1186,11 +1192,7 @@ impl AccountRequestProcessor {
|
||||
));
|
||||
}
|
||||
|
||||
let client = BackendClient::from_auth(
|
||||
self.config.chatgpt_base_url.clone(),
|
||||
&auth,
|
||||
http_client_factory,
|
||||
);
|
||||
let client = self.backend_client(&auth, http_client_factory).await?;
|
||||
|
||||
let usage_request = async {
|
||||
if params.supports_luna_reserve
|
||||
@@ -1303,11 +1305,7 @@ impl AccountRequestProcessor {
|
||||
));
|
||||
}
|
||||
|
||||
let client = BackendClient::from_auth(
|
||||
self.config.chatgpt_base_url.clone(),
|
||||
&auth,
|
||||
http_client_factory,
|
||||
);
|
||||
let client = self.backend_client(&auth, http_client_factory).await?;
|
||||
if let Some(thread_id) = thread_id {
|
||||
let thread_id = thread_id.to_string();
|
||||
let usage = tokio::time::timeout(
|
||||
@@ -1391,11 +1389,7 @@ impl AccountRequestProcessor {
|
||||
));
|
||||
}
|
||||
|
||||
let client = BackendClient::from_auth(
|
||||
self.config.chatgpt_base_url.clone(),
|
||||
&auth,
|
||||
http_client_factory,
|
||||
);
|
||||
let client = self.backend_client(&auth, http_client_factory).await?;
|
||||
let messages = tokio::time::timeout(
|
||||
ACCOUNT_WORKSPACE_MESSAGES_FETCH_TIMEOUT,
|
||||
client.list_workspace_messages(),
|
||||
@@ -1485,11 +1479,7 @@ impl AccountRequestProcessor {
|
||||
));
|
||||
}
|
||||
|
||||
let client = BackendClient::from_auth(
|
||||
self.config.chatgpt_base_url.clone(),
|
||||
&auth,
|
||||
http_client_factory,
|
||||
);
|
||||
let client = self.backend_client(&auth, http_client_factory).await?;
|
||||
|
||||
match client
|
||||
.send_add_credits_nudge_email(Self::backend_credit_type(params.credit_type))
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
//! Build account API clients from the latest config and captured account authority.
|
||||
|
||||
use super::*;
|
||||
use futures::future::BoxFuture;
|
||||
|
||||
impl AccountRequestProcessor {
|
||||
// Keep config loading and runtime bootstrap out of request-handler future types.
|
||||
#[inline(never)]
|
||||
pub(super) fn backend_client<'a>(
|
||||
&'a self,
|
||||
auth: &'a CodexAuth,
|
||||
http_client_factory: codex_http_client::HttpClientFactory,
|
||||
) -> BoxFuture<'a, Result<BackendClient, JSONRPCErrorError>> {
|
||||
Box::pin(async move {
|
||||
let config = self.load_latest_config().await;
|
||||
let runtime = self
|
||||
.thread_manager
|
||||
.runtime_auth_for_config(&config)
|
||||
.map_err(|error| internal_error(error.to_string()))?;
|
||||
BackendClient::from_auth_with_runtime(
|
||||
config.chatgpt_base_url.clone(),
|
||||
auth,
|
||||
http_client_factory,
|
||||
runtime.as_ref(),
|
||||
/*headers*/ &Default::default(),
|
||||
)
|
||||
.await
|
||||
.map_err(|error| internal_error(error.to_string()))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -111,11 +111,7 @@ impl AccountRequestProcessor {
|
||||
));
|
||||
}
|
||||
|
||||
Ok(BackendClient::from_auth(
|
||||
self.config.chatgpt_base_url.clone(),
|
||||
&auth,
|
||||
http_client_factory,
|
||||
))
|
||||
self.backend_client(&auth, http_client_factory).await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -399,15 +399,23 @@ impl AccountRequestProcessor {
|
||||
let routing = if let Some(cached) = cached {
|
||||
cached
|
||||
} else {
|
||||
let runtime = self
|
||||
.thread_manager
|
||||
.runtime_auth_for_config(&config)
|
||||
.map_err(|_| WorkspaceRoutingError::DiscoveryFailed)?;
|
||||
let mut recovery = self.auth_manager.unauthorized_recovery();
|
||||
let mut discovery_auth = auth.clone();
|
||||
let mut discovery_generation = auth_state.generation;
|
||||
let response = loop {
|
||||
let client = BackendClient::from_auth(
|
||||
let client = BackendClient::from_auth_with_runtime(
|
||||
&config.chatgpt_base_url,
|
||||
&discovery_auth,
|
||||
config.http_client_factory(),
|
||||
);
|
||||
runtime.as_ref(),
|
||||
/*headers*/ &Default::default(),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| WorkspaceRoutingError::DiscoveryFailed)?;
|
||||
match client.get_accounts_check().await {
|
||||
Ok(response) => break response,
|
||||
Err(error) => {
|
||||
|
||||
@@ -159,8 +159,16 @@ fn load_shared_plugin_ids_by_local_path(
|
||||
})
|
||||
}
|
||||
|
||||
fn remote_plugin_service_config(config: &Config) -> RemotePluginServiceConfig {
|
||||
config.plugins_config_input().remote_plugin_service_config()
|
||||
fn remote_plugin_service_config(
|
||||
thread_manager: &ThreadManager,
|
||||
config: &Config,
|
||||
) -> Result<RemotePluginServiceConfig, JSONRPCErrorError> {
|
||||
let _ = thread_manager
|
||||
.runtime_auth_for_config(config)
|
||||
.map_err(|error| internal_error(error.to_string()))?;
|
||||
Ok(thread_manager
|
||||
.plugins_manager()
|
||||
.remote_plugin_service_config(&config.plugins_config_input()))
|
||||
}
|
||||
|
||||
fn share_context_for_source(
|
||||
@@ -599,7 +607,8 @@ impl PluginRequestProcessor {
|
||||
let include_global_remote = context.remote_plugins_enabled() && !explicit_marketplace_kinds;
|
||||
let use_remote_global_catalog =
|
||||
include_global_remote && auth_mode.is_some_and(DomainAuthMode::uses_codex_backend);
|
||||
let remote_plugin_service_config = remote_plugin_service_config(&config);
|
||||
let remote_plugin_service_config =
|
||||
remote_plugin_service_config(&self.thread_manager, &config)?;
|
||||
let plugin_policy = &config.plugins;
|
||||
let remote_catalog_cache_mode = if force_refetch {
|
||||
RemotePluginCatalogCacheMode::ForceRefetch
|
||||
@@ -1056,7 +1065,8 @@ impl PluginRequestProcessor {
|
||||
);
|
||||
let share_context = match share_context {
|
||||
Some(context) => {
|
||||
let remote_plugin_service_config = remote_plugin_service_config(&config);
|
||||
let remote_plugin_service_config =
|
||||
remote_plugin_service_config(&self.thread_manager, &config)?;
|
||||
match codex_core_plugins::remote::fetch_remote_plugin_share_context(
|
||||
&remote_plugin_service_config,
|
||||
auth.as_ref(),
|
||||
@@ -1184,7 +1194,8 @@ impl PluginRequestProcessor {
|
||||
"remote plugin read is not enabled for marketplace {remote_marketplace_name}"
|
||||
)));
|
||||
}
|
||||
let remote_plugin_service_config = remote_plugin_service_config(&config);
|
||||
let remote_plugin_service_config =
|
||||
remote_plugin_service_config(&self.thread_manager, &config)?;
|
||||
validate_remote_plugin_id(&plugin_name)?;
|
||||
let remote_detail = codex_core_plugins::remote::fetch_remote_plugin_detail(
|
||||
&remote_plugin_service_config,
|
||||
@@ -1246,7 +1257,8 @@ impl PluginRequestProcessor {
|
||||
}
|
||||
|
||||
let auth = self.auth_manager.auth().await;
|
||||
let remote_plugin_service_config = remote_plugin_service_config(&config);
|
||||
let remote_plugin_service_config =
|
||||
remote_plugin_service_config(&self.thread_manager, &config)?;
|
||||
let remote_skill_detail = codex_core_plugins::remote::fetch_remote_plugin_skill_detail(
|
||||
&remote_plugin_service_config,
|
||||
auth.as_ref(),
|
||||
@@ -1297,7 +1309,8 @@ impl PluginRequestProcessor {
|
||||
validate_client_plugin_share_targets(share_targets)?;
|
||||
}
|
||||
|
||||
let remote_plugin_service_config = remote_plugin_service_config(&config);
|
||||
let remote_plugin_service_config =
|
||||
remote_plugin_service_config(&self.thread_manager, &config)?;
|
||||
let access_policy = codex_core_plugins::remote::RemotePluginShareAccessPolicy {
|
||||
discoverability: discoverability.map(remote_plugin_share_discoverability),
|
||||
share_targets: share_targets.map(remote_plugin_share_targets),
|
||||
@@ -1345,7 +1358,8 @@ impl PluginRequestProcessor {
|
||||
}
|
||||
validate_client_plugin_share_targets(&share_targets)?;
|
||||
|
||||
let remote_plugin_service_config = remote_plugin_service_config(&config);
|
||||
let remote_plugin_service_config =
|
||||
remote_plugin_service_config(&self.thread_manager, &config)?;
|
||||
let result = codex_core_plugins::remote::update_remote_plugin_share_targets(
|
||||
&remote_plugin_service_config,
|
||||
auth.as_ref(),
|
||||
@@ -1379,7 +1393,8 @@ impl PluginRequestProcessor {
|
||||
_params: PluginShareListParams,
|
||||
) -> Result<PluginShareListResponse, JSONRPCErrorError> {
|
||||
let (config, auth) = self.load_plugin_share_config_and_auth().await?;
|
||||
let remote_plugin_service_config = remote_plugin_service_config(&config);
|
||||
let remote_plugin_service_config =
|
||||
remote_plugin_service_config(&self.thread_manager, &config)?;
|
||||
let plugin_policy = &config.plugins;
|
||||
let data = codex_core_plugins::remote::list_remote_plugin_shares(
|
||||
&remote_plugin_service_config,
|
||||
@@ -1417,7 +1432,8 @@ impl PluginRequestProcessor {
|
||||
return Err(invalid_request("invalid remote plugin id"));
|
||||
}
|
||||
|
||||
let remote_plugin_service_config = remote_plugin_service_config(&config);
|
||||
let remote_plugin_service_config =
|
||||
remote_plugin_service_config(&self.thread_manager, &config)?;
|
||||
let result = codex_core_plugins::remote::checkout_remote_plugin_share(
|
||||
&remote_plugin_service_config,
|
||||
auth.as_ref(),
|
||||
@@ -1448,7 +1464,8 @@ impl PluginRequestProcessor {
|
||||
return Err(invalid_request("invalid remote plugin id"));
|
||||
}
|
||||
|
||||
let remote_plugin_service_config = remote_plugin_service_config(&config);
|
||||
let remote_plugin_service_config =
|
||||
remote_plugin_service_config(&self.thread_manager, &config)?;
|
||||
codex_core_plugins::remote::delete_remote_plugin_share(
|
||||
&remote_plugin_service_config,
|
||||
auth.as_ref(),
|
||||
@@ -1599,6 +1616,10 @@ impl PluginRequestProcessor {
|
||||
install_attempt_id: Option<String>,
|
||||
) -> Result<PluginInstallResponse, JSONRPCErrorError> {
|
||||
let mut config = self.load_latest_config(/*fallback_cwd*/ None).await?;
|
||||
let _ = self
|
||||
.thread_manager
|
||||
.runtime_auth_for_config(&config)
|
||||
.map_err(|error| internal_error(error.to_string()))?;
|
||||
let auth = match self.auth_manager.auth_with_http_client_factory().await {
|
||||
Some((auth, factory)) => {
|
||||
config.application_network_policy = factory.network_policy().clone();
|
||||
@@ -2105,6 +2126,10 @@ impl PluginRequestProcessor {
|
||||
plugin_id: String,
|
||||
) -> Result<PluginUninstallResponse, JSONRPCErrorError> {
|
||||
let config = self.load_latest_config(/*fallback_cwd*/ None).await?;
|
||||
let _ = self
|
||||
.thread_manager
|
||||
.runtime_auth_for_config(&config)
|
||||
.map_err(|error| internal_error(error.to_string()))?;
|
||||
let auth = self.auth_manager.auth().await;
|
||||
let outcome = self
|
||||
.thread_manager
|
||||
|
||||
@@ -78,7 +78,7 @@ impl PluginRequestProcessor {
|
||||
&& let Some(remote_scope) = remote_scope
|
||||
{
|
||||
let page = search_remote_plugins(
|
||||
&remote_plugin_service_config(&config),
|
||||
&remote_plugin_service_config(&self.thread_manager, &config)?,
|
||||
auth.as_ref(),
|
||||
RemotePluginSearchRequest {
|
||||
query: search_term,
|
||||
|
||||
Reference in New Issue
Block a user