Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions codex-rs/app-server-transport/src/connection_auth.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//! Binds a transport connection to the authentication owner that established it.
//! Owner revisions invalidate queued work even before transport closure is delivered.

use codex_login::AuthChangeState;
use codex_login::AuthManager;
use std::io;
use tokio::sync::watch;

#[derive(Clone, Debug)]
pub struct ConnectionAuth {
changes: watch::Receiver<AuthChangeState>,
owner_generation: u64,
}

impl ConnectionAuth {
pub(crate) fn capture(auth_manager: &AuthManager) -> Self {
let changes = auth_manager.auth_change_state_receiver();
let owner_generation = changes.borrow().owner_generation;
Self::new(changes, owner_generation)
}

pub(crate) fn ensure_current(&self) -> io::Result<()> {
if self.is_current() {
Ok(())
} else {
Err(io::Error::new(
io::ErrorKind::Interrupted,
"remote control authentication changed",
))
}
}

pub(crate) fn new(changes: watch::Receiver<AuthChangeState>, owner_generation: u64) -> Self {
Self {
changes,
owner_generation,
}
}

pub fn is_current(&self) -> bool {
self.changes.borrow().owner_generation == self.owner_generation
&& self.changes.has_changed().is_ok()
}

pub(crate) async fn invalidated(&self) {
let mut changes = self.changes.clone();
let _ = changes
.wait_for(|state| state.owner_generation != self.owner_generation)
.await;
}
}
2 changes: 2 additions & 0 deletions codex-rs/app-server-transport/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ pub use daemon_shutdown::DAEMON_SHUTDOWN_FILE_ENV;
pub use daemon_shutdown::daemon_shutdown_signal;
/// Only managed app-server launches accept the local socket shutdown request.
pub const DAEMON_SHUTDOWN_SOCKET_ENV: &str = "CODEX_DAEMON_SHUTDOWN_SOCKET";
mod connection_auth;
mod outgoing_message;
mod transport;

pub use connection_auth::ConnectionAuth;
pub use outgoing_message::ConnectionId;
pub use outgoing_message::OutgoingError;
pub use outgoing_message::OutgoingMessage;
Expand Down
1 change: 1 addition & 0 deletions codex-rs/app-server-transport/src/transport/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ pub enum TransportEvent {
ConnectionOpened {
connection_id: ConnectionId,
origin: ConnectionOrigin,
auth: Option<crate::ConnectionAuth>,
writer: mpsc::Sender<QueuedOutgoingMessage>,
disconnect_sender: Option<CancellationToken>,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
//! Credentials and recovery bound to one remote-control login lifetime.
//! A request can refresh credentials, but cannot adopt a replacement authentication owner.

use axum::http::HeaderMap;
use axum::http::HeaderValue;
use codex_api::SharedAuthProvider;
Expand All @@ -10,6 +13,46 @@ use tokio::sync::watch;
use tracing::info;
use tracing::warn;

#[derive(Clone)]
pub(super) struct RemoteControlAuth {
manager: Arc<AuthManager>,
pub(super) owner: crate::ConnectionAuth,
}

pub(super) struct RemoteControlRecovery {
auth: RemoteControlAuth,
recovery: UnauthorizedRecovery,
}

impl RemoteControlAuth {
pub(super) fn capture(manager: Arc<AuthManager>) -> (Self, bool) {
loop {
let owner = crate::ConnectionAuth::capture(&manager);
let authenticated = manager
.auth_cached()
.is_some_and(|auth| auth.uses_codex_backend() && auth.get_account_id().is_some());
if owner.is_current() {
return (Self { manager, owner }, authenticated);
}
}
}

pub(super) fn ensure_current(&self) -> io::Result<()> {
self.owner.ensure_current()
}

pub(super) fn unauthorized_recovery(&self) -> RemoteControlRecovery {
RemoteControlRecovery {
auth: self.clone(),
recovery: self.manager.unauthorized_recovery(),
}
}

pub(super) fn auth_change_receiver(&self) -> watch::Receiver<u64> {
self.manager.auth_change_receiver()
}
}

pub(super) const REMOTE_CONTROL_ACCOUNT_ID_HEADER: &str = "chatgpt-account-id";

pub(super) struct RemoteControlConnectionAuth {
Expand All @@ -35,6 +78,15 @@ impl RemoteControlConnectionAuth {
}

pub(super) async fn load_remote_control_auth(
auth: &RemoteControlAuth,
) -> io::Result<RemoteControlConnectionAuth> {
auth.ensure_current()?;
let credentials = load_auth_manager(&auth.manager).await?;
auth.ensure_current()?;
Ok(credentials)
}

async fn load_auth_manager(
auth_manager: &Arc<AuthManager>,
) -> io::Result<RemoteControlConnectionAuth> {
let mut reloaded = false;
Expand Down Expand Up @@ -80,9 +132,13 @@ pub(super) async fn load_remote_control_auth(
}

pub(super) async fn recover_remote_control_auth(
auth_recovery: &mut UnauthorizedRecovery,
recovery: &mut RemoteControlRecovery,
auth_change_rx: &mut watch::Receiver<u64>,
) -> bool {
if recovery.auth.ensure_current().is_err() {
return false;
}
let auth_recovery = &mut recovery.recovery;
if !auth_recovery.has_next() {
return false;
}
Expand All @@ -92,6 +148,9 @@ pub(super) async fn recover_remote_control_auth(
let auth_change_revision_before_recovery = *auth_change_rx.borrow();
match auth_recovery.next().await {
Ok(step_result) => {
if recovery.auth.ensure_current().is_err() {
return false;
}
if step_result.auth_state_changed() == Some(true) {
mark_recovery_auth_change_seen(
auth_change_rx,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ struct ClientState {
}

pub(crate) struct ClientTracker {
pub(super) auth: Option<crate::ConnectionAuth>,
clients: HashMap<(ClientId, StreamId), ClientState>,
legacy_stream_ids: HashMap<ClientId, StreamId>,
join_set: JoinSet<(ClientId, StreamId)>,
Expand All @@ -58,6 +59,7 @@ impl ClientTracker {
shutdown_token: &CancellationToken,
) -> Self {
Self {
auth: None,
clients: HashMap::new(),
legacy_stream_ids: HashMap::new(),
join_set: JoinSet::new(),
Expand Down Expand Up @@ -168,6 +170,7 @@ impl ClientTracker {
self.send_transport_event(TransportEvent::ConnectionOpened {
connection_id,
origin: ConnectionOrigin::RemoteControl,
auth: self.auth.clone(),
writer: writer_tx,
disconnect_sender: Some(disconnect_token.clone()),
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use super::auth::RemoteControlAuth;
use super::auth::RemoteControlConnectionAuth;
use super::auth::load_remote_control_auth;
use super::auth::recover_remote_control_auth;
Expand All @@ -11,12 +12,10 @@ use codex_app_server_protocol::RemoteControlClientsListParams;
use codex_app_server_protocol::RemoteControlClientsListResponse;
use codex_app_server_protocol::RemoteControlClientsRevokeParams;
use codex_app_server_protocol::RemoteControlClientsRevokeResponse;
use codex_login::AuthManager;
use codex_login::default_client::create_client_without_request_logging;
use serde::Deserialize;
use std::io;
use std::io::ErrorKind;
use std::sync::Arc;
use time::OffsetDateTime;
use time::format_description::well_known::Rfc3339;
use url::Url;
Expand Down Expand Up @@ -68,7 +67,7 @@ struct ClientManagementResponse {

pub(super) async fn list_remote_control_clients(
remote_control_url: &str,
auth_manager: &Arc<AuthManager>,
auth_manager: &RemoteControlAuth,
params: RemoteControlClientsListParams,
) -> io::Result<RemoteControlClientsListResponse> {
if params.environment_id.is_empty() {
Expand Down Expand Up @@ -123,7 +122,7 @@ pub(super) async fn list_remote_control_clients(

pub(super) async fn revoke_remote_control_client(
remote_control_url: &str,
auth_manager: &Arc<AuthManager>,
auth_manager: &RemoteControlAuth,
params: RemoteControlClientsRevokeParams,
) -> io::Result<RemoteControlClientsRevokeResponse> {
if params.environment_id.is_empty() {
Expand Down Expand Up @@ -164,7 +163,7 @@ pub(super) async fn revoke_remote_control_client(
}

async fn send_client_management_request(
auth_manager: &Arc<AuthManager>,
auth_manager: &RemoteControlAuth,
request: ClientManagementRequest<'_>,
action: &str,
) -> io::Result<ClientManagementResponse> {
Expand Down
Loading
Loading