fix auth login store carrier

This commit is contained in:
starr-openai
2026-06-03 01:10:46 -07:00
parent cb357bdb6c
commit 0483fd17ab
5 changed files with 87 additions and 68 deletions
@@ -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;
@@ -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<LoginAccountResponse, JSONRPCErrorError> {
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())),
+30 -1
View File
@@ -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<Device
pub async fn complete_device_code_login(
opts: ServerOptions,
device_code: DeviceCode,
) -> 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<dyn AuthCredentialStore>,
) -> 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<Arc<dyn AuthCredentialStore>>,
) -> 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,
+2
View File
@@ -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;
+42 -61
View File
@@ -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<Template> = LazyLock::new(|| {
});
/// Options for launching the local login callback server.
#[derive(Clone)]
#[derive(Debug, Clone)]
pub struct ServerOptions {
pub codex_home: PathBuf,
pub client_id: String,
@@ -73,7 +72,6 @@ pub struct ServerOptions {
pub forced_chatgpt_workspace_id: Option<Vec<String>>,
pub codex_streamlined_login: bool,
pub cli_auth_credentials_store_mode: AuthCredentialsStoreMode,
configured_auth_store: Option<Arc<dyn AuthCredentialStore>>,
}
impl ServerOptions {
@@ -94,54 +92,8 @@ impl ServerOptions {
forced_chatgpt_workspace_id,
codex_streamlined_login: false,
cli_auth_credentials_store_mode,
configured_auth_store: None,
}
}
/// Overrides first-party ChatGPT credential persistence for this login flow.
pub fn with_configured_auth_store(
mut self,
configured_auth_store: Arc<dyn AuthCredentialStore>,
) -> Self {
self.configured_auth_store = Some(configured_auth_store);
self
}
pub(crate) fn configured_auth_store(&self) -> Arc<dyn AuthCredentialStore> {
self.configured_auth_store.clone().unwrap_or_else(|| {
AuthStores::local(
self.codex_home.clone(),
self.cli_auth_credentials_store_mode,
)
.configured
})
}
}
impl Debug for ServerOptions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ServerOptions")
.field("codex_home", &self.codex_home)
.field("client_id", &self.client_id)
.field("issuer", &self.issuer)
.field("port", &self.port)
.field("open_browser", &self.open_browser)
.field("force_state", &self.force_state)
.field(
"forced_chatgpt_workspace_id",
&self.forced_chatgpt_workspace_id,
)
.field("codex_streamlined_login", &self.codex_streamlined_login)
.field(
"cli_auth_credentials_store_mode",
&self.cli_auth_credentials_store_mode,
)
.field(
"has_configured_auth_store",
&self.configured_auth_store.is_some(),
)
.finish()
}
}
/// Handle for a running login callback server.
@@ -186,6 +138,21 @@ impl ShutdownHandle {
/// Starts a local callback server and returns the browser auth URL.
pub fn run_login_server(opts: ServerOptions) -> io::Result<LoginServer> {
run_login_server_with_optional_configured_auth_store(opts, /*configured_auth_store*/ None)
}
/// Starts a local callback server that persists managed auth into the provided store.
pub fn run_login_server_with_configured_auth_store(
opts: ServerOptions,
configured_auth_store: Arc<dyn AuthCredentialStore>,
) -> io::Result<LoginServer> {
run_login_server_with_optional_configured_auth_store(opts, Some(configured_auth_store))
}
fn run_login_server_with_optional_configured_auth_store(
opts: ServerOptions,
configured_auth_store: Option<Arc<dyn AuthCredentialStore>>,
) -> io::Result<LoginServer> {
let pkce = generate_pkce();
let state = opts.force_state.clone().unwrap_or_else(generate_state);
@@ -249,8 +216,16 @@ pub fn run_login_server(opts: ServerOptions) -> io::Result<LoginServer> {
};
let url_raw = req.url().to_string();
let response =
process_request(&url_raw, &opts, &redirect_uri, &pkce, actual_port, &state).await;
let response = process_request(
&url_raw,
&opts,
configured_auth_store.as_ref().map(Arc::clone),
&redirect_uri,
&pkce,
actual_port,
&state,
)
.await;
let exit_result = match response {
HandledRequest::Response(response) => {
@@ -311,6 +286,7 @@ enum HandledRequest {
async fn process_request(
url_raw: &str,
opts: &ServerOptions,
configured_auth_store: Option<Arc<dyn AuthCredentialStore>>,
redirect_uri: &str,
pkce: &PkceCodes,
actual_port: u16,
@@ -404,7 +380,7 @@ async fn process_request(
.await
.ok();
if let Err(err) = persist_tokens_to_store_async(
opts.configured_auth_store(),
configured_auth_store_for_options(opts, configured_auth_store),
api_key.clone(),
tokens.id_token.clone(),
tokens.access_token.clone(),
@@ -851,6 +827,19 @@ pub(crate) async fn persist_tokens_async(
.await
}
pub(crate) fn configured_auth_store_for_options(
opts: &ServerOptions,
configured_auth_store: Option<Arc<dyn AuthCredentialStore>>,
) -> Arc<dyn AuthCredentialStore> {
configured_auth_store.unwrap_or_else(|| {
AuthStores::local(
opts.codex_home.clone(),
opts.cli_auth_credentials_store_mode,
)
.configured
})
}
pub(crate) async fn persist_tokens_to_store_async(
configured_auth_store: Arc<dyn AuthCredentialStore>,
api_key: Option<String>,
@@ -1282,16 +1271,8 @@ mod tests {
async fn configured_auth_store_overrides_local_login_persistence() -> anyhow::Result<()> {
let codex_home = tempdir()?;
let configured = Arc::new(FakeAuthCredentialStore::default());
let opts = super::ServerOptions::new(
codex_home.path().to_path_buf(),
"client-id".to_string(),
/*forced_chatgpt_workspace_id*/ None,
AuthCredentialsStoreMode::File,
)
.with_configured_auth_store(configured.clone());
persist_tokens_to_store_async(
opts.configured_auth_store(),
configured.clone(),
/*api_key*/ None,
jwt_for_account("new-account"),
"new-access".to_string(),