refactor: make auth loading async (#19762)

## Summary

Auth loading used to expose synchronous construction helpers in several
places even though some auth sources now need async work. This PR makes
the auth-loading surface async and updates the callers to await it.

This is intentionally only plumbing. It does not change how
AgentIdentity tokens are decoded, how task runtime ids are allocated, or
how JWT signatures are verified.

## Stack

1. **This PR:** [refactor: make auth loading
async](https://github.com/openai/codex/pull/19762)
2. [refactor: load AgentIdentity runtime
eagerly](https://github.com/openai/codex/pull/19763)
3. [feat: verify AgentIdentity JWTs with
JWKS](https://github.com/openai/codex/pull/19764)

## Important call sites

| Area | Change |
| --- | --- |
| `codex-login` auth loading | `CodexAuth` and `AuthManager`
construction paths now await auth loading. |
| app-server startup | Auth manager construction is awaited during
initialization. |
| CLI/TUI/exec/MCP/chatgpt callers | Existing auth-loading calls now
await the same behavior. |
| cloud requirements storage loader | The loader becomes async so it can
share the same auth construction path. |
| auth tests | Tests that load auth now run in async contexts. |

## Testing

Tests: targeted Rust auth test compilation, formatter, scoped Clippy
fix, and Bazel lock check.
This commit is contained in:
efrazer-oai
2026-04-27 11:00:27 -07:00
committed by GitHub
Unverified
parent 4ed22fc7d2
commit 2009f6e894
25 changed files with 291 additions and 203 deletions
@@ -1354,7 +1354,7 @@ impl CodexMessageProcessor {
self.config.cli_auth_credentials_store_mode,
) {
Ok(()) => {
self.auth_manager.reload();
self.auth_manager.reload().await;
Ok(())
}
Err(err) => Err(JSONRPCErrorError {
@@ -1505,7 +1505,7 @@ impl CodexMessageProcessor {
.await;
if success {
auth_manager.reload();
auth_manager.reload().await;
config_manager.replace_cloud_requirements_loader(
auth_manager.clone(),
chatgpt_base_url,
@@ -1613,7 +1613,7 @@ impl CodexMessageProcessor {
.await;
if success {
auth_manager.reload();
auth_manager.reload().await;
config_manager.replace_cloud_requirements_loader(
auth_manager.clone(),
chatgpt_base_url,
@@ -1749,7 +1749,7 @@ impl CodexMessageProcessor {
self.outgoing.send_error(request_id, error).await;
return;
}
self.auth_manager.reload();
self.auth_manager.reload().await;
self.config_manager.replace_cloud_requirements_loader(
self.auth_manager.clone(),
self.config.chatgpt_base_url.clone(),
+2 -1
View File
@@ -391,7 +391,8 @@ fn start_uninitialized(args: InProcessStartArgs) -> InProcessClientHandle {
let processor_outgoing = Arc::clone(&outgoing_message_sender);
let auth_manager =
AuthManager::shared_from_config(args.config.as_ref(), args.enable_codex_api_key_env);
AuthManager::shared_from_config(args.config.as_ref(), args.enable_codex_api_key_env)
.await;
let config_manager = ConfigManager::new(
args.config.codex_home.to_path_buf(),
args.cli_overrides,
+3 -3
View File
@@ -469,7 +469,7 @@ pub async fn run_main_with_transport_options(
config_manager
.replace_thread_config_loader(Arc::clone(&discovered_thread_config_loader));
let auth_manager =
AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ false);
AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ false).await;
config_manager.replace_cloud_requirements_loader(auth_manager, config.chatgpt_base_url);
}
Err(err) => {
@@ -631,7 +631,7 @@ pub async fn run_main_with_transport_options(
}
let auth_manager =
AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ false);
AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ false).await;
let remote_control_enabled = config.features.enabled(Feature::RemoteControl);
if transport_accept_handles.is_empty() && !remote_control_enabled {
@@ -712,7 +712,7 @@ pub async fn run_main_with_transport_options(
let outgoing_message_sender = Arc::new(OutgoingMessageSender::new(outgoing_tx));
let outbound_control_tx = outbound_control_tx;
let auth_manager =
AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ false);
AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ false).await;
let processor = Arc::new(MessageProcessor::new(MessageProcessorArgs {
outgoing: outgoing_message_sender,
arg0_paths,
@@ -127,7 +127,7 @@ impl TracingHarness {
let server = create_mock_responses_server_repeating_assistant("Done").await;
let codex_home = TempDir::new()?;
let config = Arc::new(build_test_config(codex_home.path(), &server.uri()).await?);
let (processor, outgoing_rx) = build_test_processor(config);
let (processor, outgoing_rx) = build_test_processor(config).await;
let tracing = init_test_tracing();
tracing.exporter.reset();
tracing::callsite::rebuild_interest_cache();
@@ -257,7 +257,7 @@ async fn build_test_config(codex_home: &Path, server_uri: &str) -> Result<Config
.await?)
}
fn build_test_processor(
async fn build_test_processor(
config: Arc<Config>,
) -> (
Arc<MessageProcessor>,
@@ -266,7 +266,7 @@ fn build_test_processor(
let (outgoing_tx, outgoing_rx) = mpsc::channel(16);
let outgoing = Arc::new(OutgoingMessageSender::new(outgoing_tx));
let auth_manager =
AuthManager::shared_from_config(config.as_ref(), /*enable_codex_api_key_env*/ false);
AuthManager::shared_from_config(config.as_ref(), /*enable_codex_api_key_env*/ false).await;
let config_manager = ConfigManager::new(
config.codex_home.to_path_buf(),
Vec::new(),
@@ -497,7 +497,8 @@ async fn remote_control_start_allows_missing_auth_when_enabled() {
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
/*chatgpt_base_url*/ None,
);
)
.await;
let (transport_event_tx, _transport_event_rx) =
mpsc::channel::<TransportEvent>(CHANNEL_CAPACITY);
let shutdown_token = CancellationToken::new();
@@ -1085,7 +1086,8 @@ async fn remote_control_waits_for_account_id_before_enrolling() {
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
/*chatgpt_base_url*/ None,
);
)
.await;
let expected_server_name = gethostname().to_string_lossy().trim().to_string();
let expected_enrollment = RemoteControlEnrollment {
account_id: "account_id".to_string(),
@@ -706,7 +706,7 @@ pub(crate) async fn load_remote_control_auth(
"remote control requires ChatGPT authentication",
));
}
auth_manager.reload();
auth_manager.reload().await;
reloaded = true;
continue;
};
@@ -714,7 +714,7 @@ pub(crate) async fn load_remote_control_auth(
break auth;
}
if auth.get_account_id().is_none() && !reloaded {
auth_manager.reload();
auth_manager.reload().await;
reloaded = true;
continue;
}
@@ -1090,7 +1090,8 @@ mod tests {
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
/*chatgpt_base_url*/ None,
);
)
.await;
let mut auth_recovery = auth_manager.unauthorized_recovery();
let mut enrollment = Some(RemoteControlEnrollment {
account_id: "account_id".to_string(),
@@ -1172,7 +1173,8 @@ mod tests {
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
/*chatgpt_base_url*/ None,
);
)
.await;
let mut auth_recovery = auth_manager.unauthorized_recovery();
let mut enrollment = None;
save_auth(
+1 -1
View File
@@ -21,7 +21,7 @@ pub(crate) async fn chatgpt_get_request_with_timeout<T: DeserializeOwned>(
) -> anyhow::Result<T> {
let chatgpt_base_url = &config.chatgpt_base_url;
let auth_manager =
AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false);
AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false).await;
let auth = auth_manager
.auth()
.await
+2 -2
View File
@@ -26,7 +26,7 @@ const DIRECTORY_CONNECTORS_TIMEOUT: Duration = Duration::from_secs(60);
async fn apps_enabled(config: &Config) -> bool {
let auth_manager =
AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false);
AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false).await;
let auth = auth_manager.auth().await;
config
.features
@@ -35,7 +35,7 @@ async fn apps_enabled(config: &Config) -> bool {
async fn connector_auth(config: &Config) -> anyhow::Result<CodexAuth> {
let auth_manager =
AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false);
AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false).await;
let auth = auth_manager
.auth()
.await
+3 -1
View File
@@ -362,7 +362,9 @@ pub async fn run_login_with_device_code_fallback_to_browser(
pub async fn run_login_status(cli_config_overrides: CliConfigOverrides) -> ! {
let config = load_config_or_exit(cli_config_overrides).await;
match CodexAuth::from_auth_storage(&config.codex_home, config.cli_auth_credentials_store_mode) {
match CodexAuth::from_auth_storage(&config.codex_home, config.cli_auth_credentials_store_mode)
.await
{
Ok(Some(auth)) => match auth.auth_mode() {
AuthMode::ApiKey => match auth.get_token() {
Ok(api_key) => {
+1 -1
View File
@@ -1384,7 +1384,7 @@ async fn run_debug_models_command(
.map_err(anyhow::Error::msg)?;
let config = Config::load_with_cli_overrides(cli_overrides).await?;
let auth_manager =
AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ true);
AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ true).await;
let models_manager =
build_models_manager(&config, auth_manager, CollaborationModesConfig::default());
models_manager
+92 -67
View File
@@ -727,7 +727,7 @@ pub fn cloud_requirements_loader(
})
}
pub fn cloud_requirements_loader_for_storage(
pub async fn cloud_requirements_loader_for_storage(
codex_home: PathBuf,
enable_codex_api_key_env: bool,
credentials_store_mode: AuthCredentialsStoreMode,
@@ -738,7 +738,8 @@ pub fn cloud_requirements_loader_for_storage(
enable_codex_api_key_env,
credentials_store_mode,
Some(chatgpt_base_url.clone()),
);
)
.await;
cloud_requirements_loader(auth_manager, chatgpt_base_url, codex_home)
}
@@ -853,7 +854,7 @@ mod tests {
Ok(())
}
fn auth_manager_with_api_key() -> Arc<AuthManager> {
async fn auth_manager_with_api_key() -> Arc<AuthManager> {
let tmp = tempdir().expect("tempdir");
let auth_json = json!({
"OPENAI_API_KEY": "sk-test-key",
@@ -861,15 +862,18 @@ mod tests {
"last_refresh": null,
});
write_auth_json(tmp.path(), auth_json).expect("write auth");
Arc::new(AuthManager::new(
tmp.path().to_path_buf(),
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
/*chatgpt_base_url*/ None,
))
Arc::new(
AuthManager::new(
tmp.path().to_path_buf(),
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
/*chatgpt_base_url*/ None,
)
.await,
)
}
fn auth_manager_with_plan_and_identity(
async fn auth_manager_with_plan_and_identity(
plan_type: &str,
chatgpt_user_id: Option<&str>,
account_id: Option<&str>,
@@ -886,12 +890,15 @@ mod tests {
),
)
.expect("write auth");
Arc::new(AuthManager::new(
tmp.path().to_path_buf(),
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
/*chatgpt_base_url*/ None,
))
Arc::new(
AuthManager::new(
tmp.path().to_path_buf(),
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
/*chatgpt_base_url*/ None,
)
.await,
)
}
fn chatgpt_auth_json(
@@ -975,7 +982,7 @@ mod tests {
manager: Arc<AuthManager>,
}
fn managed_auth_context(
async fn managed_auth_context(
plan_type: &str,
chatgpt_user_id: Option<&str>,
account_id: Option<&str>,
@@ -995,18 +1002,22 @@ mod tests {
)
.expect("write auth");
ManagedAuthContext {
manager: Arc::new(AuthManager::new(
home.path().to_path_buf(),
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
/*chatgpt_base_url*/ None,
)),
manager: Arc::new(
AuthManager::new(
home.path().to_path_buf(),
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
/*chatgpt_base_url*/ None,
)
.await,
),
_home: home,
}
}
fn auth_manager_with_plan(plan_type: &str) -> Arc<AuthManager> {
async fn auth_manager_with_plan(plan_type: &str) -> Arc<AuthManager> {
auth_manager_with_plan_and_identity(plan_type, Some("user-12345"), Some("account-12345"))
.await
}
fn parse_for_fetch(contents: Option<&str>) -> Option<ConfigRequirementsToml> {
@@ -1118,7 +1129,7 @@ mod tests {
#[tokio::test]
async fn fetch_cloud_requirements_skips_non_chatgpt_auth() {
let auth_manager = auth_manager_with_api_key();
let auth_manager = auth_manager_with_api_key().await;
let codex_home = tempdir().expect("tempdir");
let service = CloudRequirementsService::new(
auth_manager,
@@ -1134,7 +1145,7 @@ mod tests {
async fn fetch_cloud_requirements_skips_non_business_or_enterprise_plan() {
let codex_home = tempdir().expect("tempdir");
let service = CloudRequirementsService::new(
auth_manager_with_plan("pro"),
auth_manager_with_plan("pro").await,
Arc::new(StaticFetcher { contents: None }),
codex_home.path().to_path_buf(),
CLOUD_REQUIREMENTS_TIMEOUT,
@@ -1147,7 +1158,7 @@ mod tests {
async fn fetch_cloud_requirements_skips_team_like_usage_based_plan() {
let codex_home = tempdir().expect("tempdir");
let service = CloudRequirementsService::new(
auth_manager_with_plan("self_serve_business_usage_based"),
auth_manager_with_plan("self_serve_business_usage_based").await,
Arc::new(StaticFetcher {
contents: Some("allowed_approval_policies = [\"never\"]".to_string()),
}),
@@ -1161,7 +1172,7 @@ mod tests {
async fn fetch_cloud_requirements_allows_business_plan() {
let codex_home = tempdir().expect("tempdir");
let service = CloudRequirementsService::new(
auth_manager_with_plan("business"),
auth_manager_with_plan("business").await,
Arc::new(StaticFetcher {
contents: Some("allowed_approval_policies = [\"never\"]".to_string()),
}),
@@ -1193,7 +1204,7 @@ mod tests {
async fn fetch_cloud_requirements_allows_business_like_usage_based_plan() {
let codex_home = tempdir().expect("tempdir");
let service = CloudRequirementsService::new(
auth_manager_with_plan("enterprise_cbp_usage_based"),
auth_manager_with_plan("enterprise_cbp_usage_based").await,
Arc::new(StaticFetcher {
contents: Some("allowed_approval_policies = [\"never\"]".to_string()),
}),
@@ -1225,7 +1236,7 @@ mod tests {
async fn fetch_cloud_requirements_allows_hc_plan_as_enterprise() {
let codex_home = tempdir().expect("tempdir");
let service = CloudRequirementsService::new(
auth_manager_with_plan("hc"),
auth_manager_with_plan("hc").await,
Arc::new(StaticFetcher {
contents: Some("allowed_approval_policies = [\"never\"]".to_string()),
}),
@@ -1329,7 +1340,7 @@ enabled = false
#[tokio::test(start_paused = true)]
async fn fetch_cloud_requirements_times_out() {
let auth_manager = auth_manager_with_plan("enterprise");
let auth_manager = auth_manager_with_plan("enterprise").await;
let codex_home = tempdir().expect("tempdir");
let service = CloudRequirementsService::new(
auth_manager,
@@ -1356,7 +1367,7 @@ enabled = false
]));
let codex_home = tempdir().expect("tempdir");
let service = CloudRequirementsService::new(
auth_manager_with_plan("business"),
auth_manager_with_plan("business").await,
fetcher.clone(),
codex_home.path().to_path_buf(),
CLOUD_REQUIREMENTS_TIMEOUT,
@@ -1405,12 +1416,15 @@ enabled = false
),
)
.expect("write initial auth");
let auth_manager = Arc::new(AuthManager::new(
auth_home.path().to_path_buf(),
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
/*chatgpt_base_url*/ None,
));
let auth_manager = Arc::new(
AuthManager::new(
auth_home.path().to_path_buf(),
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
/*chatgpt_base_url*/ None,
)
.await,
);
write_auth_json(
auth_home.path(),
@@ -1479,12 +1493,15 @@ enabled = false
),
)
.expect("write initial auth");
let auth_manager = Arc::new(AuthManager::new(
auth_home.path().to_path_buf(),
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
/*chatgpt_base_url*/ None,
));
let auth_manager = Arc::new(
AuthManager::new(
auth_home.path().to_path_buf(),
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
/*chatgpt_base_url*/ None,
)
.await,
);
write_auth_json(
auth_home.path(),
@@ -1559,7 +1576,8 @@ enabled = false
Some("account-12345"),
"stale-access-token",
"test-refresh-token",
);
)
.await;
write_auth_json(
auth._home.path(),
chatgpt_auth_json(
@@ -1611,12 +1629,15 @@ enabled = false
),
)
.expect("write auth");
let auth_manager = Arc::new(AuthManager::new(
auth_home.path().to_path_buf(),
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
/*chatgpt_base_url*/ None,
));
let auth_manager = Arc::new(
AuthManager::new(
auth_home.path().to_path_buf(),
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
/*chatgpt_base_url*/ None,
)
.await,
);
let fetcher = Arc::new(UnauthorizedFetcher {
message:
@@ -1653,7 +1674,7 @@ enabled = false
]));
let codex_home = tempdir().expect("tempdir");
let service = CloudRequirementsService::new(
auth_manager_with_plan("business"),
auth_manager_with_plan("business").await,
fetcher.clone(),
codex_home.path().to_path_buf(),
CLOUD_REQUIREMENTS_TIMEOUT,
@@ -1678,7 +1699,7 @@ enabled = false
))]));
let codex_home = tempdir().expect("tempdir");
let service = CloudRequirementsService::new(
auth_manager_with_plan("business"),
auth_manager_with_plan("business").await,
fetcher,
codex_home.path().to_path_buf(),
CLOUD_REQUIREMENTS_TIMEOUT,
@@ -1700,7 +1721,7 @@ enabled = false
async fn fetch_cloud_requirements_uses_cache_when_valid() {
let codex_home = tempdir().expect("tempdir");
let prime_service = CloudRequirementsService::new(
auth_manager_with_plan("business"),
auth_manager_with_plan("business").await,
Arc::new(StaticFetcher {
contents: Some("allowed_approval_policies = [\"never\"]".to_string()),
}),
@@ -1711,7 +1732,7 @@ enabled = false
let fetcher = Arc::new(SequenceFetcher::new(vec![Err(request_error())]));
let service = CloudRequirementsService::new(
auth_manager_with_plan("business"),
auth_manager_with_plan("business").await,
fetcher.clone(),
codex_home.path().to_path_buf(),
CLOUD_REQUIREMENTS_TIMEOUT,
@@ -1747,7 +1768,8 @@ enabled = false
"business",
/*chatgpt_user_id*/ None,
Some("account-12345"),
),
)
.await,
Arc::new(StaticFetcher {
contents: Some("allowed_approval_policies = [\"never\"]".to_string()),
}),
@@ -1790,7 +1812,7 @@ enabled = false
async fn fetch_cloud_requirements_does_not_use_cache_when_auth_identity_is_incomplete() {
let codex_home = tempdir().expect("tempdir");
let prime_service = CloudRequirementsService::new(
auth_manager_with_plan("business"),
auth_manager_with_plan("business").await,
Arc::new(StaticFetcher {
contents: Some("allowed_approval_policies = [\"never\"]".to_string()),
}),
@@ -1807,7 +1829,8 @@ enabled = false
"business",
/*chatgpt_user_id*/ None,
Some("account-12345"),
),
)
.await,
fetcher.clone(),
codex_home.path().to_path_buf(),
CLOUD_REQUIREMENTS_TIMEOUT,
@@ -1843,7 +1866,8 @@ enabled = false
"business",
Some("user-12345"),
Some("account-12345"),
),
)
.await,
Arc::new(StaticFetcher {
contents: Some("allowed_approval_policies = [\"never\"]".to_string()),
}),
@@ -1860,7 +1884,8 @@ enabled = false
"business",
Some("user-99999"),
Some("account-12345"),
),
)
.await,
fetcher.clone(),
codex_home.path().to_path_buf(),
CLOUD_REQUIREMENTS_TIMEOUT,
@@ -1892,7 +1917,7 @@ enabled = false
async fn fetch_cloud_requirements_ignores_tampered_cache() {
let codex_home = tempdir().expect("tempdir");
let prime_service = CloudRequirementsService::new(
auth_manager_with_plan("business"),
auth_manager_with_plan("business").await,
Arc::new(StaticFetcher {
contents: Some("allowed_approval_policies = [\"never\"]".to_string()),
}),
@@ -1917,7 +1942,7 @@ enabled = false
"allowed_approval_policies = [\"never\"]".to_string(),
))]));
let service = CloudRequirementsService::new(
auth_manager_with_plan("enterprise"),
auth_manager_with_plan("enterprise").await,
fetcher.clone(),
codex_home.path().to_path_buf(),
CLOUD_REQUIREMENTS_TIMEOUT,
@@ -1975,7 +2000,7 @@ enabled = false
"allowed_approval_policies = [\"never\"]".to_string(),
))]));
let service = CloudRequirementsService::new(
auth_manager_with_plan("enterprise"),
auth_manager_with_plan("enterprise").await,
fetcher.clone(),
codex_home.path().to_path_buf(),
CLOUD_REQUIREMENTS_TIMEOUT,
@@ -2007,7 +2032,7 @@ enabled = false
async fn fetch_cloud_requirements_writes_signed_cache() {
let codex_home = tempdir().expect("tempdir");
let service = CloudRequirementsService::new(
auth_manager_with_plan("business"),
auth_manager_with_plan("business").await,
Arc::new(StaticFetcher {
contents: Some("allowed_approval_policies = [\"never\"]".to_string()),
}),
@@ -2070,7 +2095,7 @@ enabled = false
let fetcher = Arc::new(SequenceFetcher::new(vec![Ok(None), Err(request_error())]));
let codex_home = tempdir().expect("tempdir");
let service = CloudRequirementsService::new(
auth_manager_with_plan("enterprise"),
auth_manager_with_plan("enterprise").await,
fetcher.clone(),
codex_home.path().to_path_buf(),
CLOUD_REQUIREMENTS_TIMEOUT,
@@ -2088,7 +2113,7 @@ enabled = false
]));
let codex_home = tempdir().expect("tempdir");
let service = CloudRequirementsService::new(
auth_manager_with_plan("enterprise"),
auth_manager_with_plan("enterprise").await,
fetcher.clone(),
codex_home.path().to_path_buf(),
CLOUD_REQUIREMENTS_TIMEOUT,
@@ -2121,7 +2146,7 @@ enabled = false
)),
]));
let service = CloudRequirementsService::new(
auth_manager_with_plan("business"),
auth_manager_with_plan("business").await,
fetcher,
codex_home.path().to_path_buf(),
CLOUD_REQUIREMENTS_TIMEOUT,
+9 -6
View File
@@ -44,12 +44,15 @@ pub fn normalize_base_url(input: &str) -> String {
pub async fn load_auth_manager(chatgpt_base_url: Option<String>) -> Option<AuthManager> {
// TODO: pass in cli overrides once cloud tasks properly support them.
let config = Config::load_with_cli_overrides(Vec::new()).await.ok()?;
Some(AuthManager::new(
config.codex_home.to_path_buf(),
/*enable_codex_api_key_env*/ false,
config.cli_auth_credentials_store_mode,
chatgpt_base_url.or(Some(config.chatgpt_base_url)),
))
Some(
AuthManager::new(
config.codex_home.to_path_buf(),
/*enable_codex_api_key_env*/ false,
config.cli_auth_credentials_store_mode,
chatgpt_base_url.or(Some(config.chatgpt_base_url)),
)
.await,
)
}
/// Build headers for ChatGPT-backed requests: `User-Agent`, optional `Authorization`,
+3 -3
View File
@@ -144,7 +144,7 @@ pub async fn list_cached_accessible_connectors_from_mcp_tools(
config: &Config,
) -> Option<Vec<AppInfo>> {
let auth_manager =
AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false);
AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false).await;
let auth = auth_manager.auth().await;
if !config
.features
@@ -216,7 +216,7 @@ pub async fn list_accessible_connectors_from_mcp_tools_with_environment_manager(
environment_manager: &EnvironmentManager,
) -> anyhow::Result<AccessibleConnectorsStatus> {
let auth_manager =
AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false);
AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false).await;
let auth = auth_manager.auth().await;
if !config
.features
@@ -434,7 +434,7 @@ async fn list_directory_connectors_for_tool_suggest_with_auth(
Some(auth)
} else {
let auth_manager =
AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false);
AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false).await;
loaded_auth = auth_manager.auth().await;
loaded_auth.as_ref()
};
+1 -1
View File
@@ -29,7 +29,7 @@ pub async fn build_prompt_input(
config.ephemeral = true;
let auth_manager =
AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ false);
AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ false).await;
let local_runtime_paths = ExecServerRuntimePaths::from_optional_paths(
config.codex_self_exe.clone(),
+2 -1
View File
@@ -1091,7 +1091,8 @@ async fn prefers_apikey_when_config_prefers_apikey_even_with_chatgpt_tokens() {
config.model_provider = model_provider;
let auth_manager =
match CodexAuth::from_auth_storage(codex_home.path(), AuthCredentialsStoreMode::File) {
match CodexAuth::from_auth_storage(codex_home.path(), AuthCredentialsStoreMode::File).await
{
Ok(Some(auth)) => codex_core::test_support::auth_manager_from_auth(auth),
Ok(None) => panic!("No CodexAuth found in codex_home"),
Err(e) => panic!("Failed to load CodexAuth: {e}"),
+5 -2
View File
@@ -348,7 +348,8 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
/*enable_codex_api_key_env*/ false,
config_toml.cli_auth_credentials_store.unwrap_or_default(),
chatgpt_base_url,
);
)
.await;
let run_cli_overrides = cli_kv_overrides.clone();
let run_loader_overrides = loader_overrides.clone();
let run_cloud_requirements = cloud_requirements.clone();
@@ -438,7 +439,9 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
auth_credentials_store_mode: config.cli_auth_credentials_store_mode,
forced_login_method: config.forced_login_method,
forced_chatgpt_workspace_id: config.forced_chatgpt_workspace_id.clone(),
}) {
})
.await
{
eprintln!("{err}");
std::process::exit(1);
}
+45 -25
View File
@@ -116,10 +116,11 @@ fn login_with_agent_identity_rejects_invalid_jwt() {
);
}
#[test]
fn missing_auth_json_returns_none() {
#[tokio::test]
async fn missing_auth_json_returns_none() {
let dir = tempdir().unwrap();
let auth = CodexAuth::from_auth_storage(dir.path(), AuthCredentialsStoreMode::File)
.await
.expect("call should succeed");
assert_eq!(auth, None);
}
@@ -143,6 +144,7 @@ async fn pro_account_with_no_api_key_uses_chatgpt_auth() {
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
)
.await
.unwrap()
.unwrap();
assert_eq!(None, auth.api_key());
@@ -196,6 +198,7 @@ async fn loads_api_key_from_auth_json() {
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
)
.await
.unwrap()
.unwrap();
assert_eq!(auth.auth_mode(), AuthMode::ApiKey);
@@ -222,15 +225,16 @@ fn logout_removes_auth_file() -> Result<(), std::io::Error> {
Ok(())
}
#[test]
fn unauthorized_recovery_reports_mode_and_step_names() {
#[tokio::test]
async fn unauthorized_recovery_reports_mode_and_step_names() {
let dir = tempdir().unwrap();
let manager = AuthManager::shared(
dir.path().to_path_buf(),
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
/*chatgpt_base_url*/ None,
);
)
.await;
let managed = UnauthorizedRecovery {
manager: Arc::clone(&manager),
step: UnauthorizedRecoveryStep::Reload,
@@ -250,8 +254,8 @@ fn unauthorized_recovery_reports_mode_and_step_names() {
assert_eq!(external.step_name(), "external_refresh");
}
#[test]
fn refresh_failure_is_scoped_to_the_matching_auth_snapshot() {
#[tokio::test]
async fn refresh_failure_is_scoped_to_the_matching_auth_snapshot() {
let codex_home = tempdir().unwrap();
write_auth_file(
AuthFileParams {
@@ -268,6 +272,7 @@ fn refresh_failure_is_scoped_to_the_matching_auth_snapshot() {
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
)
.await
.expect("load auth")
.expect("auth available");
let mut updated_auth_dot_json = auth
@@ -284,6 +289,7 @@ fn refresh_failure_is_scoped_to_the_matching_auth_snapshot() {
updated_auth_dot_json,
AuthCredentialsStoreMode::File,
)
.await
.expect("updated auth should parse");
let manager = AuthManager::from_auth_for_testing(auth.clone());
@@ -619,9 +625,9 @@ impl Drop for EnvVarGuard {
}
}
#[test]
#[tokio::test]
#[serial(codex_auth_env)]
fn load_auth_reads_agent_identity_from_env() {
async fn load_auth_reads_agent_identity_from_env() {
let codex_home = tempdir().unwrap();
let expected_record = agent_identity_record("account-123");
let agent_identity = fake_agent_identity_jwt(&expected_record).expect("fake agent identity");
@@ -632,6 +638,7 @@ fn load_auth_reads_agent_identity_from_env() {
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
)
.await
.expect("env auth should load")
.expect("env auth should be present");
@@ -645,9 +652,9 @@ fn load_auth_reads_agent_identity_from_env() {
);
}
#[test]
#[tokio::test]
#[serial(codex_auth_env)]
fn load_auth_keeps_codex_api_key_env_precedence() {
async fn load_auth_keeps_codex_api_key_env_precedence() {
let codex_home = tempdir().unwrap();
let record = agent_identity_record("account-123");
let agent_identity = fake_agent_identity_jwt(&record).expect("fake agent identity");
@@ -659,6 +666,7 @@ fn load_auth_keeps_codex_api_key_env_precedence() {
/*enable_codex_api_key_env*/ true,
AuthCredentialsStoreMode::File,
)
.await
.expect("env auth should load")
.expect("env auth should be present");
@@ -679,8 +687,9 @@ async fn enforce_login_restrictions_logs_out_for_method_mismatch() {
)
.await;
let err =
super::enforce_login_restrictions(&config).expect_err("expected method mismatch to error");
let err = super::enforce_login_restrictions(&config)
.await
.expect_err("expected method mismatch to error");
assert!(err.to_string().contains("ChatGPT login is required"));
assert!(
!codex_home.path().join("auth.json").exists(),
@@ -710,6 +719,7 @@ async fn enforce_login_restrictions_logs_out_for_workspace_mismatch() {
.await;
let err = super::enforce_login_restrictions(&config)
.await
.expect_err("expected workspace mismatch to error");
assert!(err.to_string().contains("workspace org_mine"));
assert!(
@@ -739,7 +749,9 @@ async fn enforce_login_restrictions_allows_matching_workspace() {
)
.await;
super::enforce_login_restrictions(&config).expect("matching workspace should succeed");
super::enforce_login_restrictions(&config)
.await
.expect("matching workspace should succeed");
assert!(
codex_home.path().join("auth.json").exists(),
"auth.json should remain when restrictions pass"
@@ -761,7 +773,9 @@ async fn enforce_login_restrictions_allows_api_key_if_login_method_not_set_but_f
)
.await;
super::enforce_login_restrictions(&config).expect("matching workspace should succeed");
super::enforce_login_restrictions(&config)
.await
.expect("matching workspace should succeed");
assert!(
codex_home.path().join("auth.json").exists(),
"auth.json should remain when restrictions pass"
@@ -782,6 +796,7 @@ async fn enforce_login_restrictions_blocks_env_api_key_when_chatgpt_required() {
.await;
let err = super::enforce_login_restrictions(&config)
.await
.expect_err("environment API key should not satisfy forced ChatGPT login");
assert!(
err.to_string()
@@ -818,8 +833,8 @@ fn fake_agent_identity_jwt(record: &AgentIdentityAuthRecord) -> std::io::Result<
Ok(format!("{header_b64}.{payload_b64}.{signature_b64}"))
}
#[test]
fn plan_type_maps_known_plan() {
#[tokio::test]
async fn plan_type_maps_known_plan() {
let codex_home = tempdir().unwrap();
let _jwt = write_auth_file(
AuthFileParams {
@@ -836,14 +851,15 @@ fn plan_type_maps_known_plan() {
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
)
.await
.expect("load auth")
.expect("auth available");
pretty_assertions::assert_eq!(auth.account_plan_type(), Some(AccountPlanType::Pro));
}
#[test]
fn plan_type_maps_self_serve_business_usage_based_plan() {
#[tokio::test]
async fn plan_type_maps_self_serve_business_usage_based_plan() {
let codex_home = tempdir().unwrap();
let _jwt = write_auth_file(
AuthFileParams {
@@ -860,6 +876,7 @@ fn plan_type_maps_self_serve_business_usage_based_plan() {
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
)
.await
.expect("load auth")
.expect("auth available");
@@ -869,8 +886,8 @@ fn plan_type_maps_self_serve_business_usage_based_plan() {
);
}
#[test]
fn plan_type_maps_enterprise_cbp_usage_based_plan() {
#[tokio::test]
async fn plan_type_maps_enterprise_cbp_usage_based_plan() {
let codex_home = tempdir().unwrap();
let _jwt = write_auth_file(
AuthFileParams {
@@ -887,6 +904,7 @@ fn plan_type_maps_enterprise_cbp_usage_based_plan() {
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
)
.await
.expect("load auth")
.expect("auth available");
@@ -896,8 +914,8 @@ fn plan_type_maps_enterprise_cbp_usage_based_plan() {
);
}
#[test]
fn plan_type_maps_unknown_to_unknown() {
#[tokio::test]
async fn plan_type_maps_unknown_to_unknown() {
let codex_home = tempdir().unwrap();
let _jwt = write_auth_file(
AuthFileParams {
@@ -914,14 +932,15 @@ fn plan_type_maps_unknown_to_unknown() {
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
)
.await
.expect("load auth")
.expect("auth available");
pretty_assertions::assert_eq!(auth.account_plan_type(), Some(AccountPlanType::Unknown));
}
#[test]
fn missing_plan_type_maps_to_unknown() {
#[tokio::test]
async fn missing_plan_type_maps_to_unknown() {
let codex_home = tempdir().unwrap();
let _jwt = write_auth_file(
AuthFileParams {
@@ -938,6 +957,7 @@ fn missing_plan_type_maps_to_unknown() {
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
)
.await
.expect("load auth")
.expect("auth available");
+51 -32
View File
@@ -193,7 +193,7 @@ impl From<RefreshTokenError> for std::io::Error {
}
impl CodexAuth {
fn from_auth_dot_json(
async fn from_auth_dot_json(
codex_home: &Path,
auth_dot_json: AuthDotJson,
auth_credentials_store_mode: AuthCredentialsStoreMode,
@@ -234,7 +234,7 @@ impl CodexAuth {
}
}
pub fn from_auth_storage(
pub async fn from_auth_storage(
codex_home: &Path,
auth_credentials_store_mode: AuthCredentialsStoreMode,
) -> std::io::Result<Option<Self>> {
@@ -243,6 +243,7 @@ impl CodexAuth {
/*enable_codex_api_key_env*/ false,
auth_credentials_store_mode,
)
.await
}
pub fn from_agent_identity_jwt(jwt: &str) -> std::io::Result<Self> {
@@ -522,6 +523,7 @@ pub async fn logout_with_revoke(
auth_credentials_store_mode,
/*chatgpt_base_url*/ None,
)
.await
.logout_with_revoke()
.await
}
@@ -609,12 +611,13 @@ pub struct AuthConfig {
pub forced_chatgpt_workspace_id: Option<String>,
}
pub fn enforce_login_restrictions(config: &AuthConfig) -> std::io::Result<()> {
pub async fn enforce_login_restrictions(config: &AuthConfig) -> std::io::Result<()> {
let Some(auth) = load_auth(
&config.codex_home,
/*enable_codex_api_key_env*/ true,
config.auth_credentials_store_mode,
)?
)
.await?
else {
return Ok(());
};
@@ -714,15 +717,11 @@ fn logout_all_stores(
Ok(removed_ephemeral || removed_managed)
}
fn load_auth(
async fn load_auth(
codex_home: &Path,
enable_codex_api_key_env: bool,
auth_credentials_store_mode: AuthCredentialsStoreMode,
) -> std::io::Result<Option<CodexAuth>> {
let build_auth = |auth_dot_json: AuthDotJson, storage_mode| {
CodexAuth::from_auth_dot_json(codex_home, auth_dot_json, storage_mode)
};
// API key via env var takes precedence over any other auth method.
if enable_codex_api_key_env && let Some(api_key) = read_codex_api_key_from_env() {
return Ok(Some(CodexAuth::from_api_key(api_key.as_str())));
@@ -735,7 +734,12 @@ fn load_auth(
AuthCredentialsStoreMode::Ephemeral,
);
if let Some(auth_dot_json) = ephemeral_storage.load()? {
let auth = build_auth(auth_dot_json, AuthCredentialsStoreMode::Ephemeral)?;
let auth = CodexAuth::from_auth_dot_json(
codex_home,
auth_dot_json,
AuthCredentialsStoreMode::Ephemeral,
)
.await?;
return Ok(Some(auth));
}
@@ -755,7 +759,9 @@ fn load_auth(
None => return Ok(None),
};
let auth = build_auth(auth_dot_json, auth_credentials_store_mode)?;
let auth =
CodexAuth::from_auth_dot_json(codex_home, auth_dot_json, auth_credentials_store_mode)
.await?;
Ok(Some(auth))
}
@@ -1169,6 +1175,7 @@ impl UnauthorizedRecovery {
match self
.manager
.reload_if_account_id_matches(self.expected_account_id.as_deref())
.await
{
ReloadOutcome::ReloadedChanged => {
self.step = UnauthorizedRecoveryStep::RefreshToken;
@@ -1279,7 +1286,7 @@ impl AuthManager {
/// preferred auth method. Errors loading auth are swallowed; `auth()` will
/// simply return `None` in that case so callers can treat it as an
/// unauthenticated state.
pub fn new(
pub async fn new(
codex_home: PathBuf,
enable_codex_api_key_env: bool,
auth_credentials_store_mode: AuthCredentialsStoreMode,
@@ -1290,6 +1297,7 @@ impl AuthManager {
enable_codex_api_key_env,
auth_credentials_store_mode,
)
.await
.ok()
.flatten();
Self {
@@ -1402,13 +1410,16 @@ impl AuthManager {
/// Force a reload of the auth information from auth.json. Returns
/// whether the auth value changed.
pub fn reload(&self) -> bool {
pub async fn reload(&self) -> bool {
tracing::info!("Reloading auth");
let new_auth = self.load_auth_from_storage();
let new_auth = self.load_auth_from_storage().await;
self.set_cached_auth(new_auth)
}
fn reload_if_account_id_matches(&self, expected_account_id: Option<&str>) -> ReloadOutcome {
async fn reload_if_account_id_matches(
&self,
expected_account_id: Option<&str>,
) -> ReloadOutcome {
let expected_account_id = match expected_account_id {
Some(account_id) => account_id,
None => {
@@ -1417,7 +1428,7 @@ impl AuthManager {
}
};
let new_auth = self.load_auth_from_storage();
let new_auth = self.load_auth_from_storage().await;
let new_account_id = new_auth.as_ref().and_then(CodexAuth::get_account_id);
if new_account_id.as_deref() != Some(expected_account_id) {
@@ -1488,12 +1499,13 @@ impl AuthManager {
}
}
fn load_auth_from_storage(&self) -> Option<CodexAuth> {
async fn load_auth_from_storage(&self) -> Option<CodexAuth> {
load_auth(
&self.codex_home,
self.enable_codex_api_key_env,
self.auth_credentials_store_mode,
)
.await
.ok()
.flatten()
}
@@ -1557,22 +1569,25 @@ impl AuthManager {
}
/// Convenience constructor returning an `Arc` wrapper.
pub fn shared(
pub async fn shared(
codex_home: PathBuf,
enable_codex_api_key_env: bool,
auth_credentials_store_mode: AuthCredentialsStoreMode,
chatgpt_base_url: Option<String>,
) -> Arc<Self> {
Arc::new(Self::new(
codex_home,
enable_codex_api_key_env,
auth_credentials_store_mode,
chatgpt_base_url,
))
Arc::new(
Self::new(
codex_home,
enable_codex_api_key_env,
auth_credentials_store_mode,
chatgpt_base_url,
)
.await,
)
}
/// Convenience constructor returning an `Arc` wrapper from resolved config.
pub fn shared_from_config(
pub async fn shared_from_config(
config: &impl AuthManagerConfig,
enable_codex_api_key_env: bool,
) -> Arc<Self> {
@@ -1581,7 +1596,8 @@ impl AuthManager {
enable_codex_api_key_env,
config.cli_auth_credentials_store_mode(),
Some(config.chatgpt_base_url()),
);
)
.await;
auth_manager.set_forced_chatgpt_workspace_id(config.forced_chatgpt_workspace_id());
auth_manager
}
@@ -1647,7 +1663,10 @@ impl AuthManager {
.as_ref()
.and_then(CodexAuth::get_account_id);
match self.reload_if_account_id_matches(expected_account_id.as_deref()) {
match self
.reload_if_account_id_matches(expected_account_id.as_deref())
.await
{
ReloadOutcome::ReloadedChanged => {
tracing::info!("Skipping token refresh because auth changed after guarded reload.");
Ok(())
@@ -1714,10 +1733,10 @@ impl AuthManager {
/// if a file was removed, Ok(false) if no auth file existed. On success,
/// reloads the inmemory auth cache so callers immediately observe the
/// unauthenticated state.
pub fn logout(&self) -> std::io::Result<bool> {
pub async fn logout(&self) -> std::io::Result<bool> {
let removed = logout_all_stores(&self.codex_home, self.auth_credentials_store_mode)?;
// Always reload to clear any cached auth (even if file absent).
self.reload();
self.reload().await;
Ok(removed)
}
@@ -1730,7 +1749,7 @@ impl AuthManager {
}
let result = logout_all_stores(&self.codex_home, self.auth_credentials_store_mode)?;
// Always reload to clear any cached auth (even if file absent).
self.reload();
self.reload().await;
Ok(result)
}
@@ -1826,7 +1845,7 @@ impl AuthManager {
AuthCredentialsStoreMode::Ephemeral,
)
.map_err(RefreshTokenError::Transient)?;
self.reload();
self.reload().await;
Ok(())
}
@@ -1846,7 +1865,7 @@ impl AuthManager {
refresh_response.refresh_token,
)
.map_err(RefreshTokenError::from)?;
self.reload();
self.reload().await;
Ok(())
}
+35 -34
View File
@@ -46,7 +46,7 @@ async fn refresh_token_succeeds_updates_storage() -> Result<()> {
.mount(&server)
.await;
let ctx = RefreshTokenTestContext::new(&server)?;
let ctx = RefreshTokenTestContext::new(&server).await?;
let initial_last_refresh = Utc::now() - Duration::days(1);
let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN);
let initial_auth = AuthDotJson {
@@ -56,7 +56,7 @@ async fn refresh_token_succeeds_updates_storage() -> Result<()> {
last_refresh: Some(initial_last_refresh),
agent_identity: None,
};
ctx.write_auth(&initial_auth)?;
ctx.write_auth(&initial_auth).await?;
ctx.auth_manager
.refresh_token_from_authority()
@@ -110,7 +110,7 @@ async fn refresh_token_refreshes_when_auth_is_unchanged() -> Result<()> {
.mount(&server)
.await;
let ctx = RefreshTokenTestContext::new(&server)?;
let ctx = RefreshTokenTestContext::new(&server).await?;
let initial_last_refresh = Utc::now() - Duration::days(1);
let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN);
let initial_auth = AuthDotJson {
@@ -120,7 +120,7 @@ async fn refresh_token_refreshes_when_auth_is_unchanged() -> Result<()> {
last_refresh: Some(initial_last_refresh),
agent_identity: None,
};
ctx.write_auth(&initial_auth)?;
ctx.write_auth(&initial_auth).await?;
ctx.auth_manager
.refresh_token()
@@ -164,7 +164,7 @@ async fn refresh_token_skips_refresh_when_auth_changed() -> Result<()> {
skip_if_no_network!(Ok(()));
let server = MockServer::start().await;
let ctx = RefreshTokenTestContext::new(&server)?;
let ctx = RefreshTokenTestContext::new(&server).await?;
let initial_last_refresh = Utc::now() - Duration::days(1);
let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN);
@@ -175,7 +175,7 @@ async fn refresh_token_skips_refresh_when_auth_changed() -> Result<()> {
last_refresh: Some(initial_last_refresh),
agent_identity: None,
};
ctx.write_auth(&initial_auth)?;
ctx.write_auth(&initial_auth).await?;
let disk_tokens = build_tokens("disk-access-token", "disk-refresh-token");
let disk_auth = AuthDotJson {
@@ -230,7 +230,7 @@ async fn refresh_token_errors_on_account_mismatch() -> Result<()> {
.mount(&server)
.await;
let ctx = RefreshTokenTestContext::new(&server)?;
let ctx = RefreshTokenTestContext::new(&server).await?;
let initial_last_refresh = Utc::now() - Duration::days(1);
let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN);
let initial_auth = AuthDotJson {
@@ -240,7 +240,7 @@ async fn refresh_token_errors_on_account_mismatch() -> Result<()> {
last_refresh: Some(initial_last_refresh),
agent_identity: None,
};
ctx.write_auth(&initial_auth)?;
ctx.write_auth(&initial_auth).await?;
let mut disk_tokens = build_tokens("disk-access-token", "disk-refresh-token");
disk_tokens.account_id = Some("other-account".to_string());
@@ -299,7 +299,7 @@ async fn returns_fresh_tokens_as_is() -> Result<()> {
.mount(&server)
.await;
let ctx = RefreshTokenTestContext::new(&server)?;
let ctx = RefreshTokenTestContext::new(&server).await?;
let stale_refresh = Utc::now() - Duration::days(9);
let fresh_access_token = access_token_with_expiration(Utc::now() + Duration::hours(1));
let initial_tokens = build_tokens(&fresh_access_token, INITIAL_REFRESH_TOKEN);
@@ -310,7 +310,7 @@ async fn returns_fresh_tokens_as_is() -> Result<()> {
last_refresh: Some(stale_refresh),
agent_identity: None,
};
ctx.write_auth(&initial_auth)?;
ctx.write_auth(&initial_auth).await?;
let cached_auth = ctx
.auth_manager
@@ -347,7 +347,7 @@ async fn refreshes_token_when_access_token_is_expired() -> Result<()> {
.mount(&server)
.await;
let ctx = RefreshTokenTestContext::new(&server)?;
let ctx = RefreshTokenTestContext::new(&server).await?;
let fresh_refresh = Utc::now() - Duration::days(1);
let expired_access_token = access_token_with_expiration(Utc::now() - Duration::hours(1));
let initial_tokens = build_tokens(&expired_access_token, INITIAL_REFRESH_TOKEN);
@@ -358,7 +358,7 @@ async fn refreshes_token_when_access_token_is_expired() -> Result<()> {
last_refresh: Some(fresh_refresh),
agent_identity: None,
};
ctx.write_auth(&initial_auth)?;
ctx.write_auth(&initial_auth).await?;
let cached_auth = ctx
.auth_manager
@@ -398,7 +398,7 @@ async fn auth_reloads_disk_auth_when_cached_auth_is_stale() -> Result<()> {
let server = MockServer::start().await;
let ctx = RefreshTokenTestContext::new(&server)?;
let ctx = RefreshTokenTestContext::new(&server).await?;
let stale_refresh = Utc::now() - Duration::days(9);
let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN);
let initial_auth = AuthDotJson {
@@ -408,7 +408,7 @@ async fn auth_reloads_disk_auth_when_cached_auth_is_stale() -> Result<()> {
last_refresh: Some(stale_refresh),
agent_identity: None,
};
ctx.write_auth(&initial_auth)?;
ctx.write_auth(&initial_auth).await?;
let fresh_refresh = Utc::now() - Duration::days(1);
let disk_tokens = build_tokens("disk-access-token", "disk-refresh-token");
@@ -461,7 +461,7 @@ async fn auth_reloads_disk_auth_without_calling_expired_refresh_token() -> Resul
.mount(&server)
.await;
let ctx = RefreshTokenTestContext::new(&server)?;
let ctx = RefreshTokenTestContext::new(&server).await?;
let stale_refresh = Utc::now() - Duration::days(9);
let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN);
let initial_auth = AuthDotJson {
@@ -471,7 +471,7 @@ async fn auth_reloads_disk_auth_without_calling_expired_refresh_token() -> Resul
last_refresh: Some(stale_refresh),
agent_identity: None,
};
ctx.write_auth(&initial_auth)?;
ctx.write_auth(&initial_auth).await?;
let fresh_refresh = Utc::now() - Duration::days(1);
let disk_tokens = build_tokens("disk-access-token", "disk-refresh-token");
@@ -522,7 +522,7 @@ async fn refresh_token_returns_permanent_error_for_expired_refresh_token() -> Re
.mount(&server)
.await;
let ctx = RefreshTokenTestContext::new(&server)?;
let ctx = RefreshTokenTestContext::new(&server).await?;
let initial_last_refresh = Utc::now() - Duration::days(1);
let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN);
let initial_auth = AuthDotJson {
@@ -532,7 +532,7 @@ async fn refresh_token_returns_permanent_error_for_expired_refresh_token() -> Re
last_refresh: Some(initial_last_refresh),
agent_identity: None,
};
ctx.write_auth(&initial_auth)?;
ctx.write_auth(&initial_auth).await?;
let err = ctx
.auth_manager
@@ -575,7 +575,7 @@ async fn refresh_token_does_not_retry_after_permanent_failure() -> Result<()> {
.mount(&server)
.await;
let ctx = RefreshTokenTestContext::new(&server)?;
let ctx = RefreshTokenTestContext::new(&server).await?;
let initial_last_refresh = Utc::now() - Duration::days(1);
let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN);
let initial_auth = AuthDotJson {
@@ -585,7 +585,7 @@ async fn refresh_token_does_not_retry_after_permanent_failure() -> Result<()> {
last_refresh: Some(initial_last_refresh),
agent_identity: None,
};
ctx.write_auth(&initial_auth)?;
ctx.write_auth(&initial_auth).await?;
let first_err = ctx
.auth_manager
@@ -642,7 +642,7 @@ async fn refresh_token_reloads_changed_auth_after_permanent_failure() -> Result<
.mount(&server)
.await;
let ctx = RefreshTokenTestContext::new(&server)?;
let ctx = RefreshTokenTestContext::new(&server).await?;
let initial_last_refresh = Utc::now() - Duration::days(1);
let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN);
let initial_auth = AuthDotJson {
@@ -652,7 +652,7 @@ async fn refresh_token_reloads_changed_auth_after_permanent_failure() -> Result<
last_refresh: Some(initial_last_refresh),
agent_identity: None,
};
ctx.write_auth(&initial_auth)?;
ctx.write_auth(&initial_auth).await?;
let first_err = ctx
.auth_manager
@@ -723,7 +723,7 @@ async fn refresh_token_returns_transient_error_on_server_failure() -> Result<()>
.mount(&server)
.await;
let ctx = RefreshTokenTestContext::new(&server)?;
let ctx = RefreshTokenTestContext::new(&server).await?;
let initial_last_refresh = Utc::now() - Duration::days(1);
let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN);
let initial_auth = AuthDotJson {
@@ -733,7 +733,7 @@ async fn refresh_token_returns_transient_error_on_server_failure() -> Result<()>
last_refresh: Some(initial_last_refresh),
agent_identity: None,
};
ctx.write_auth(&initial_auth)?;
ctx.write_auth(&initial_auth).await?;
let err = ctx
.auth_manager
@@ -776,7 +776,7 @@ async fn unauthorized_recovery_reloads_then_refreshes_tokens() -> Result<()> {
.mount(&server)
.await;
let ctx = RefreshTokenTestContext::new(&server)?;
let ctx = RefreshTokenTestContext::new(&server).await?;
let initial_last_refresh = Utc::now() - Duration::days(1);
let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN);
let initial_auth = AuthDotJson {
@@ -786,7 +786,7 @@ async fn unauthorized_recovery_reloads_then_refreshes_tokens() -> Result<()> {
last_refresh: Some(initial_last_refresh),
agent_identity: None,
};
ctx.write_auth(&initial_auth)?;
ctx.write_auth(&initial_auth).await?;
let disk_tokens = build_tokens("disk-access-token", "disk-refresh-token");
let disk_auth = AuthDotJson {
@@ -870,7 +870,7 @@ async fn unauthorized_recovery_errors_on_account_mismatch() -> Result<()> {
.mount(&server)
.await;
let ctx = RefreshTokenTestContext::new(&server)?;
let ctx = RefreshTokenTestContext::new(&server).await?;
let initial_last_refresh = Utc::now() - Duration::days(1);
let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN);
let initial_auth = AuthDotJson {
@@ -880,7 +880,7 @@ async fn unauthorized_recovery_errors_on_account_mismatch() -> Result<()> {
last_refresh: Some(initial_last_refresh),
agent_identity: None,
};
ctx.write_auth(&initial_auth)?;
ctx.write_auth(&initial_auth).await?;
let mut disk_tokens = build_tokens("disk-access-token", "disk-refresh-token");
disk_tokens.account_id = Some("other-account".to_string());
@@ -941,7 +941,7 @@ async fn unauthorized_recovery_requires_chatgpt_auth() -> Result<()> {
skip_if_no_network!(Ok(()));
let server = MockServer::start().await;
let ctx = RefreshTokenTestContext::new(&server)?;
let ctx = RefreshTokenTestContext::new(&server).await?;
let auth = AuthDotJson {
auth_mode: Some(AuthMode::ApiKey),
openai_api_key: Some("sk-test".to_string()),
@@ -949,7 +949,7 @@ async fn unauthorized_recovery_requires_chatgpt_auth() -> Result<()> {
last_refresh: None,
agent_identity: None,
};
ctx.write_auth(&auth)?;
ctx.write_auth(&auth).await?;
let mut recovery = ctx.auth_manager.unauthorized_recovery();
assert!(!recovery.has_next());
@@ -974,7 +974,7 @@ struct RefreshTokenTestContext {
}
impl RefreshTokenTestContext {
fn new(server: &MockServer) -> Result<Self> {
async fn new(server: &MockServer) -> Result<Self> {
let codex_home = TempDir::new()?;
let endpoint = format!("{}/oauth/token", server.uri());
@@ -985,7 +985,8 @@ impl RefreshTokenTestContext {
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
/*chatgpt_base_url*/ None,
);
)
.await;
Ok(Self {
codex_home,
@@ -1000,13 +1001,13 @@ impl RefreshTokenTestContext {
.context("auth.json should exist")
}
fn write_auth(&self, auth_dot_json: &AuthDotJson) -> Result<()> {
async fn write_auth(&self, auth_dot_json: &AuthDotJson) -> Result<()> {
save_auth(
self.codex_home.path(),
auth_dot_json,
AuthCredentialsStoreMode::File,
)?;
self.auth_manager.reload();
self.auth_manager.reload().await;
Ok(())
}
}
+2 -1
View File
@@ -143,7 +143,8 @@ async fn auth_manager_logout_with_revoke_uses_cached_auth() -> Result<()> {
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
/*chatgpt_base_url*/ None,
);
)
.await;
save_auth(
codex_home.path(),
&chatgpt_auth_with_refresh_token("newer-disk-refresh-token"),
+2 -1
View File
@@ -141,7 +141,8 @@ pub async fn run_main(
arg0_paths,
Arc::new(config),
environment_manager,
);
)
.await;
async move {
while let Some(msg) = incoming_rx.recv().await {
match msg {
+3 -2
View File
@@ -49,7 +49,7 @@ pub(crate) struct MessageProcessor {
impl MessageProcessor {
/// Create a new `MessageProcessor`, retaining a handle to the outgoing
/// `Sender` so handlers can enqueue messages to be written to stdout.
pub(crate) fn new(
pub(crate) async fn new(
outgoing: OutgoingMessageSender,
arg0_paths: Arg0DispatchPaths,
config: Arc<Config>,
@@ -59,7 +59,8 @@ impl MessageProcessor {
let auth_manager = AuthManager::shared_from_config(
config.as_ref(),
/*enable_codex_api_key_env*/ false,
);
)
.await;
let thread_manager = Arc::new(ThreadManager::new(
config.as_ref(),
auth_manager,
+3 -2
View File
@@ -206,7 +206,7 @@ fn static_manager_for_tests(model_catalog: ModelsResponse) -> StaticModelsManage
)
}
fn chatgpt_auth_tokens_for_tests(codex_home: &Path) -> CodexAuth {
async fn chatgpt_auth_tokens_for_tests(codex_home: &Path) -> CodexAuth {
let auth_dot_json = codex_login::AuthDotJson {
auth_mode: Some(AuthMode::ChatgptAuthTokens),
openai_api_key: None,
@@ -232,6 +232,7 @@ c2ln",
.expect("auth.json should be written");
CodexAuth::from_auth_storage(codex_home, AuthCredentialsStoreMode::File)
.await
.expect("auth should load")
.expect("auth should be present")
}
@@ -685,7 +686,7 @@ async fn refresh_available_models_fetches_with_chatgpt_auth_tokens() {
"ChatGPT Auth Tokens",
/*priority*/ 1,
)]]);
let auth = chatgpt_auth_tokens_for_tests(codex_home.path());
let auth = chatgpt_auth_tokens_for_tests(codex_home.path()).await;
let manager = openai_manager_for_tests_with_auth(
codex_home.path().to_path_buf(),
endpoint.clone(),
+7 -3
View File
@@ -795,7 +795,8 @@ pub async fn run_main(
/*enable_codex_api_key_env*/ false,
config_toml.cli_auth_credentials_store.unwrap_or_default(),
chatgpt_base_url,
);
)
.await;
let model_provider_override = if cli.oss {
let resolved = resolve_oss_provider(
@@ -895,7 +896,9 @@ pub async fn run_main(
auth_credentials_store_mode: config.cli_auth_credentials_store_mode,
forced_login_method: config.forced_login_method,
forced_chatgpt_workspace_id: config.forced_chatgpt_workspace_id.clone(),
}) {
})
.await
{
eprintln!("{err}");
std::process::exit(1);
}
@@ -1166,7 +1169,8 @@ async fn run_ratatui_app(
/*enable_codex_api_key_env*/ false,
initial_config.cli_auth_credentials_store_mode,
initial_config.chatgpt_base_url.clone(),
);
)
.await;
}
// If the user made an explicit trust decision, or we showed the login flow, reload config
+2 -1
View File
@@ -986,7 +986,8 @@ mod tests {
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
"https://chatgpt.com/backend-api/".to_string(),
),
)
.await,
feedback: codex_feedback::CodexFeedback::new(),
log_db: None,
environment_manager: Arc::new(