diff --git a/codex-rs/app-server/src/request_processors.rs b/codex-rs/app-server/src/request_processors.rs index 4fc32a28e9..2f6778dc4d 100644 --- a/codex-rs/app-server/src/request_processors.rs +++ b/codex-rs/app-server/src/request_processors.rs @@ -341,9 +341,9 @@ use codex_login::CLIENT_ID; use codex_login::CodexAuth; use codex_login::ServerOptions as LoginServerOptions; use codex_login::ShutdownHandle; -use codex_login::complete_device_code_login; +use codex_login::complete_device_code_login_with_configured_auth_store; use codex_login::request_device_code; -use codex_login::run_login_server; +use codex_login::run_login_server_with_configured_auth_store; use codex_mcp::McpRuntimeContext; use codex_mcp::McpServerStatusSnapshot; use codex_mcp::McpSnapshotDetail; diff --git a/codex-rs/app-server/src/request_processors/account_processor.rs b/codex-rs/app-server/src/request_processors/account_processor.rs index 1b66e66fc3..edb77081f1 100644 --- a/codex-rs/app-server/src/request_processors/account_processor.rs +++ b/codex-rs/app-server/src/request_processors/account_processor.rs @@ -322,7 +322,6 @@ impl AccountRequestProcessor { config.forced_chatgpt_workspace_id.clone(), config.cli_auth_credentials_store_mode, ) - .with_configured_auth_store(self.auth_manager.configured_auth_store()) }; #[cfg(debug_assertions)] let opts = { @@ -361,8 +360,11 @@ impl AccountRequestProcessor { codex_streamlined_login: bool, ) -> Result { let opts = self.login_chatgpt_common(codex_streamlined_login).await?; - let server = run_login_server(opts) - .map_err(|err| internal_error(format!("failed to start login server: {err}")))?; + let server = run_login_server_with_configured_auth_store( + opts, + self.auth_manager.configured_auth_store(), + ) + .map_err(|err| internal_error(format!("failed to start login server: {err}")))?; let login_id = Uuid::new_v4(); let shutdown_handle = server.cancel_handle(); @@ -459,12 +461,17 @@ impl AccountRequestProcessor { let thread_manager = Arc::clone(&self.thread_manager); let chatgpt_base_url = self.config.chatgpt_base_url.clone(); let active_login = self.active_login.clone(); + let configured_auth_store = self.auth_manager.configured_auth_store(); tokio::spawn(async move { let (success, error_msg) = tokio::select! { _ = cancel.cancelled() => { (false, Some("Login was not completed".to_string())) } - r = complete_device_code_login(opts, device_code) => { + r = complete_device_code_login_with_configured_auth_store( + opts, + device_code, + configured_auth_store, + ) => { match r { Ok(()) => (true, None), Err(err) => (false, Some(err.to_string())), diff --git a/codex-rs/login/src/device_code_auth.rs b/codex-rs/login/src/device_code_auth.rs index d31d3b3da6..60a2c7d346 100644 --- a/codex-rs/login/src/device_code_auth.rs +++ b/codex-rs/login/src/device_code_auth.rs @@ -3,9 +3,11 @@ use serde::Deserialize; use serde::Serialize; use serde::de::Deserializer; use serde::de::{self}; +use std::sync::Arc; use std::time::Duration; use std::time::Instant; +use crate::auth::AuthCredentialStore; use crate::pkce::PkceCodes; use crate::server::ServerOptions; use codex_client::build_reqwest_client_with_custom_ca; @@ -173,6 +175,33 @@ pub async fn request_device_code(opts: &ServerOptions) -> std::io::Result std::io::Result<()> { + complete_device_code_login_with_optional_configured_auth_store( + opts, + device_code, + /*configured_auth_store*/ None, + ) + .await +} + +/// Completes device-code login and persists managed auth into the provided store. +pub async fn complete_device_code_login_with_configured_auth_store( + opts: ServerOptions, + device_code: DeviceCode, + configured_auth_store: Arc, +) -> std::io::Result<()> { + complete_device_code_login_with_optional_configured_auth_store( + opts, + device_code, + Some(configured_auth_store), + ) + .await +} + +async fn complete_device_code_login_with_optional_configured_auth_store( + opts: ServerOptions, + device_code: DeviceCode, + configured_auth_store: Option>, ) -> std::io::Result<()> { let client = build_reqwest_client_with_custom_ca(reqwest::Client::builder())?; let base_url = opts.issuer.trim_end_matches('/'); @@ -211,7 +240,7 @@ pub async fn complete_device_code_login( } crate::server::persist_tokens_to_store_async( - opts.configured_auth_store(), + crate::server::configured_auth_store_for_options(&opts, configured_auth_store), /*api_key*/ None, tokens.id_token, tokens.access_token, diff --git a/codex-rs/login/src/lib.rs b/codex-rs/login/src/lib.rs index fde253312b..34181760bb 100644 --- a/codex-rs/login/src/lib.rs +++ b/codex-rs/login/src/lib.rs @@ -10,12 +10,14 @@ pub use codex_client::BuildCustomCaTransportError as BuildLoginHttpClientError; pub use codex_config::types::AuthCredentialsStoreMode; pub use device_code_auth::DeviceCode; pub use device_code_auth::complete_device_code_login; +pub use device_code_auth::complete_device_code_login_with_configured_auth_store; pub use device_code_auth::request_device_code; pub use device_code_auth::run_device_code_login; pub use server::LoginServer; pub use server::ServerOptions; pub use server::ShutdownHandle; pub use server::run_login_server; +pub use server::run_login_server_with_configured_auth_store; pub use auth::AuthConfig; pub use auth::AuthCredentialStore; diff --git a/codex-rs/login/src/server.rs b/codex-rs/login/src/server.rs index 1e180be8b9..80c8aca358 100644 --- a/codex-rs/login/src/server.rs +++ b/codex-rs/login/src/server.rs @@ -11,7 +11,6 @@ //! This module therefore keeps the user-facing error path and the structured-log path separate. //! Returned `io::Error` values still carry the detail needed by CLI/browser callers, while //! structured logs only emit explicitly reviewed fields plus redacted URL/error values. -use std::fmt::Debug; use std::io::Cursor; use std::io::Read; use std::io::Write; @@ -62,7 +61,7 @@ static LOGIN_ERROR_PAGE_TEMPLATE: LazyLock