mirror of
https://github.com/openai/codex.git
synced 2026-10-31 19:28:49 +00:00
Integrate runtime authentication into model requests (#57171)
## What changed - Add opt-in runtime authentication through `ModelClient::with_runtime_auth`, requiring the provider and runtime manager to share the selected OpenAI credentials. - Keep runtime identity, credentials, routing, and transport selection consistent for each request and its retries, signing a fresh assertion for each attempt. - Apply authentication asynchronously to WebSocket handshakes, replace stale authentication headers, and reconnect when the runtime changes. - Reject account, organization, or project selectors that conflict with the registered runtime, and preserve gateway OAuth composition. ## Testing Add coverage for dual-header and assertion-only authentication, HTTP retries, runtime replacement and account switches, stale WebSocket headers, selector mismatches, gateway OAuth failures, transport revocation, and Realtime compatibility. GitOrigin-RevId: 45b9efc2c80481406a0f6f6396a8cdd21d933c12
This commit is contained in:
@@ -47,6 +47,19 @@ pub trait AuthProvider: Send + Sync {
|
||||
Ok(headers)
|
||||
}
|
||||
|
||||
/// Describes headers expected on a successful request without refreshing or registering auth.
|
||||
/// Async providers should override this rather than resolving credentials for telemetry.
|
||||
fn auth_header_telemetry(&self) -> Result<AuthHeaderTelemetry, AuthError> {
|
||||
let headers = self.to_auth_headers()?;
|
||||
let name = headers
|
||||
.contains_key(http::header::AUTHORIZATION)
|
||||
.then_some("authorization");
|
||||
Ok(AuthHeaderTelemetry {
|
||||
attached: name.is_some(),
|
||||
name,
|
||||
})
|
||||
}
|
||||
|
||||
/// Resolves auth headers for an outbound request.
|
||||
///
|
||||
/// Unlike [`Self::to_auth_headers`], implementations may perform asynchronous work to refresh
|
||||
@@ -63,6 +76,15 @@ pub trait AuthProvider: Send + Sync {
|
||||
Box::pin(async { false })
|
||||
}
|
||||
|
||||
/// Authenticates a header-only request, including removing credentials it supersedes.
|
||||
/// WebSocket handshakes must use the returned map rather than overlaying resolved headers.
|
||||
fn apply_auth_headers(&self, mut headers: HeaderMap) -> AuthHeadersFuture<'_> {
|
||||
Box::pin(async move {
|
||||
headers.extend(self.resolve_auth_headers().await?);
|
||||
Ok(headers)
|
||||
})
|
||||
}
|
||||
|
||||
/// Applies auth to a complete outbound request and returns the request to send.
|
||||
///
|
||||
/// The input `request` is moved into this method. Implementations may mutate
|
||||
@@ -77,7 +99,7 @@ pub trait AuthProvider: Send + Sync {
|
||||
fn apply_auth(&self, request: Request) -> AuthProviderFuture<'_> {
|
||||
Box::pin(async move {
|
||||
let mut request = request;
|
||||
request.headers.extend(self.resolve_auth_headers().await?);
|
||||
request.headers = self.apply_auth_headers(request.headers).await?;
|
||||
Ok(request)
|
||||
})
|
||||
}
|
||||
@@ -107,13 +129,5 @@ pub struct AuthHeaderTelemetry {
|
||||
}
|
||||
|
||||
pub fn auth_header_telemetry(auth: &dyn AuthProvider) -> Result<AuthHeaderTelemetry, AuthError> {
|
||||
let mut headers = HeaderMap::new();
|
||||
auth.add_auth_headers(&mut headers)?;
|
||||
let name = headers
|
||||
.contains_key(http::header::AUTHORIZATION)
|
||||
.then_some("authorization");
|
||||
Ok(AuthHeaderTelemetry {
|
||||
attached: name.is_some(),
|
||||
name,
|
||||
})
|
||||
auth.auth_header_telemetry()
|
||||
}
|
||||
|
||||
@@ -464,9 +464,8 @@ impl ResponsesWebsocketClient {
|
||||
.websocket_url_for_path("/responses")
|
||||
.map_err(|err| ApiError::Stream(format!("failed to build websocket URL: {err}")))?;
|
||||
|
||||
let mut headers =
|
||||
merge_request_headers(&self.provider.headers, extra_headers, default_headers);
|
||||
self.auth.add_auth_headers(&mut headers)?;
|
||||
let headers = merge_request_headers(&self.provider.headers, extra_headers, default_headers);
|
||||
let headers = self.auth.apply_auth_headers(headers).await?;
|
||||
|
||||
let connector = WebSocketConnector::new(http_client_factory)
|
||||
.map_err(|err| ApiError::Stream(format!("failed to configure websocket TLS: {err}")))?;
|
||||
|
||||
@@ -33,9 +33,8 @@ impl ResponsesWebsocketClient {
|
||||
.provider
|
||||
.websocket_url_for_path("/responses")
|
||||
.map_err(|err| ApiError::Stream(format!("failed to build websocket URL: {err}")))?;
|
||||
let mut headers =
|
||||
merge_request_headers(&self.provider.headers, extra_headers, default_headers);
|
||||
self.auth.add_auth_headers(&mut headers)?;
|
||||
let headers = merge_request_headers(&self.provider.headers, extra_headers, default_headers);
|
||||
let headers = self.auth.apply_auth_headers(headers).await?;
|
||||
let (stream, _status, server_reasoning_included, server_model) =
|
||||
connect_websocket(ws_url, headers, connector, turn_state).await?;
|
||||
Ok(ResponsesWebsocketConnection::new(
|
||||
|
||||
Reference in New Issue
Block a user