Codex Handbook
app-server-transport/src/transport/remote_control/auth.rs 105 lines
use codex_api::SharedAuthProvider;use codex_login::AuthManager;use codex_login::UnauthorizedRecovery;use std::io;use std::io::ErrorKind;use std::sync::Arc;use tokio::sync::watch;use tracing::info;use tracing::warn;pub(super) struct RemoteControlConnectionAuth {    pub(super) auth_provider: SharedAuthProvider,    pub(super) account_id: String,}pub(super) async fn load_remote_control_auth(    auth_manager: &Arc<AuthManager>,) -> io::Result<RemoteControlConnectionAuth> {    let mut reloaded = false;    let auth = loop {        let Some(auth) = auth_manager.auth().await else {            if reloaded {                return Err(io::Error::new(                    ErrorKind::PermissionDenied,                    "remote control requires ChatGPT authentication",                ));            }            auth_manager.reload().await;            reloaded = true;            continue;        };        if !auth.uses_codex_backend() {            break auth;        }        if auth.get_account_id().is_none() && !reloaded {            auth_manager.reload().await;            reloaded = true;            continue;        }        break auth;    };    if !auth.uses_codex_backend() {        return Err(io::Error::new(            ErrorKind::PermissionDenied,            "remote control requires ChatGPT authentication; API key auth is not supported",        ));    }    Ok(RemoteControlConnectionAuth {        auth_provider: codex_model_provider::auth_provider_from_auth(&auth),        account_id: auth.get_account_id().ok_or_else(|| {            io::Error::new(                ErrorKind::WouldBlock,                "remote control enrollment is waiting for a ChatGPT account id",            )        })?,    })}pub(super) async fn recover_remote_control_auth(    auth_recovery: &mut UnauthorizedRecovery,    auth_change_rx: &mut watch::Receiver<u64>,) -> bool {    if !auth_recovery.has_next() {        return false;    }    let mode = auth_recovery.mode_name();    let step = auth_recovery.step_name();    let auth_change_revision_before_recovery = *auth_change_rx.borrow();    match auth_recovery.next().await {        Ok(step_result) => {            if step_result.auth_state_changed() == Some(true) {                mark_recovery_auth_change_seen(                    auth_change_rx,                    auth_change_revision_before_recovery,                );            }            info!(                "remote control auth recovery succeeded: mode={mode}, step={step}, auth_state_changed={:?}",                step_result.auth_state_changed()            );            true        }        Err(err) => {            warn!("remote control auth recovery failed: mode={mode}, step={step}: {err}");            false        }    }}pub(super) fn mark_recovery_auth_change_seen(    auth_change_rx: &mut watch::Receiver<u64>,    auth_change_revision_before_recovery: u64,) {    let auth_change_revision_after_recovery = *auth_change_rx.borrow();    if auth_change_revision_after_recovery == auth_change_revision_before_recovery.wrapping_add(1) {        // Recovery updated the same watch that wakes the outer reconnect        // loop. Mark only that single revision seen; if more revisions        // arrived while recovery was in flight, leave them pending so the        // reconnect loop still reacts to the later external auth change.        auth_change_rx.borrow_and_update();    }}