mirror of
https://github.com/openai/codex.git
synced 2026-11-12 21:20:34 +00:00
Add renewable EMA HTTP authentication and credential versioning (#49987)
## What changed - Add `EmaAuthenticatedHttpClient` to cache and renew resource bearers, serialize concurrent renewals, and include authentication time in the request deadline. Confine bearers to the configured MCP endpoint and reject redirects. - Return authentication-required errors for `401` responses without replaying MCP operations. Preserve a newer cached bearer when a delayed response rejects an older one. - Add revocable request authority that cancels pending requests and ends response streams. - Pin enterprise credentials to a persistent version advanced on save or deletion. Use short credential locks and invalidate rejected grants only when their record and version are still current, preserving newer logins. - Hide keyring account identifiers from enterprise credential errors and traces while preserving authentication failure classification. ## Testing Add tests for token expiry and renewal, endpoint confinement, redirects, request deadlines, revocation during renewal and streaming, delayed `401` responses, credential replacement and logout races, and credential error privacy. GitOrigin-RevId: ee3e5cf7ecbba3d09e1ab29ed4fbb2a35eacc60d
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
use anyhow::Error;
|
||||
use codex_exec_server::ExecServerError;
|
||||
use rmcp::service::ClientInitializeError;
|
||||
use rmcp::service::ServiceError;
|
||||
use rmcp::transport::DynamicTransportError;
|
||||
use rmcp::transport::auth::AuthError;
|
||||
use rmcp::transport::streamable_http_client::StreamableHttpError;
|
||||
|
||||
use crate::ema_auth_policy::EmaAuthFailure;
|
||||
use crate::ema_auth_policy::EmaInvalidGrantSource;
|
||||
use crate::http_client_adapter::StreamableHttpClientAdapterError;
|
||||
use crate::rmcp_client::ClientOperationError;
|
||||
|
||||
@@ -18,6 +21,22 @@ pub fn is_authentication_required_error(error: &Error) -> bool {
|
||||
source
|
||||
.downcast_ref::<AuthError>()
|
||||
.is_some_and(auth_error_requires_authentication)
|
||||
|| source
|
||||
.downcast_ref::<EmaAuthFailure>()
|
||||
.is_some_and(|error| match error {
|
||||
EmaAuthFailure::InvalidGrant {
|
||||
grant_source: EmaInvalidGrantSource::EnterpriseIdentity,
|
||||
}
|
||||
| EmaAuthFailure::InsufficientUserAuthentication
|
||||
| EmaAuthFailure::ReauthenticationRequired => true,
|
||||
// Rejecting an ID-JAG does not invalidate the enterprise login itself.
|
||||
EmaAuthFailure::InvalidGrant {
|
||||
grant_source: EmaInvalidGrantSource::ResourceAuthorization,
|
||||
} => false,
|
||||
})
|
||||
|| source
|
||||
.downcast_ref::<ExecServerError>()
|
||||
.is_some_and(exec_server_error_requires_authentication)
|
||||
|| source
|
||||
.downcast_ref::<ClientInitializeError>()
|
||||
.is_some_and(|mut error| {
|
||||
@@ -49,10 +68,17 @@ fn transport_error_requires_authentication(error: &DynamicTransportError) -> boo
|
||||
.is_some_and(|error| match error {
|
||||
StreamableHttpError::AuthRequired(_) => true,
|
||||
StreamableHttpError::Auth(auth_error) => auth_error_requires_authentication(auth_error),
|
||||
StreamableHttpError::Client(StreamableHttpClientAdapterError::HttpRequest(error)) => {
|
||||
exec_server_error_requires_authentication(error)
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
fn exec_server_error_requires_authentication(error: &ExecServerError) -> bool {
|
||||
matches!(error, ExecServerError::AuthenticationRequired(_))
|
||||
}
|
||||
|
||||
fn auth_error_requires_authentication(error: &AuthError) -> bool {
|
||||
matches!(
|
||||
error,
|
||||
|
||||
Reference in New Issue
Block a user