mirror of
https://github.com/openai/codex.git
synced 2026-11-11 21:10:39 +00:00
exec-server: make remote auth selection explicit
This commit is contained in:
@@ -1550,6 +1550,7 @@ async fn load_exec_server_remote_auth_provider(
|
||||
let auth = load_exec_server_remote_auth(
|
||||
root_config_overrides,
|
||||
config_profile,
|
||||
/*allow_agent_identity_auth*/ true,
|
||||
"remote exec-server registration requires ChatGPT authentication or Agent Identity auth from CODEX_ACCESS_TOKEN",
|
||||
)
|
||||
.await?;
|
||||
@@ -1566,6 +1567,7 @@ async fn load_chatgpt_exec_server_remote_auth_provider(
|
||||
let auth = load_exec_server_remote_auth(
|
||||
root_config_overrides,
|
||||
config_profile,
|
||||
/*allow_agent_identity_auth*/ false,
|
||||
"remote exec-server registration requires ChatGPT authentication; run `codex login` first",
|
||||
)
|
||||
.await?;
|
||||
@@ -1582,6 +1584,7 @@ async fn load_chatgpt_exec_server_remote_auth_provider(
|
||||
async fn load_exec_server_remote_auth(
|
||||
root_config_overrides: &CliConfigOverrides,
|
||||
config_profile: Option<String>,
|
||||
allow_agent_identity_auth: bool,
|
||||
missing_auth_error: &'static str,
|
||||
) -> anyhow::Result<codex_login::CodexAuth> {
|
||||
let cli_kv_overrides = root_config_overrides
|
||||
@@ -1595,8 +1598,12 @@ async fn load_exec_server_remote_auth(
|
||||
})
|
||||
.build()
|
||||
.await?;
|
||||
let auth_manager =
|
||||
AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ true).await;
|
||||
let auth_manager = AuthManager::shared_from_config_with_env(
|
||||
&config,
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
/*enable_codex_access_token_env*/ allow_agent_identity_auth,
|
||||
)
|
||||
.await;
|
||||
|
||||
let auth = match auth_manager.auth().await {
|
||||
Some(auth) => auth,
|
||||
|
||||
@@ -380,7 +380,9 @@ The crate exports:
|
||||
|
||||
Callers must pass `ExecServerRuntimePaths` to `run_main()`. The top-level
|
||||
`codex exec-server` command builds these paths from the `codex` arg0 dispatch
|
||||
state.
|
||||
state. `RemoteExecutorConfig::new(...)` also takes the auth provider that
|
||||
remote registration should use; the CLI builds that provider from Codex auth
|
||||
state before starting remote mode.
|
||||
|
||||
## Example session
|
||||
|
||||
|
||||
@@ -209,6 +209,7 @@ async fn pro_account_with_no_api_key_uses_chatgpt_auth() {
|
||||
let auth = super::load_auth(
|
||||
codex_home.path(),
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
/*enable_codex_access_token_env*/ true,
|
||||
AuthCredentialsStoreMode::File,
|
||||
/*chatgpt_base_url*/ None,
|
||||
)
|
||||
@@ -265,6 +266,7 @@ async fn loads_api_key_from_auth_json() {
|
||||
let auth = super::load_auth(
|
||||
dir.path(),
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
/*enable_codex_access_token_env*/ true,
|
||||
AuthCredentialsStoreMode::File,
|
||||
/*chatgpt_base_url*/ None,
|
||||
)
|
||||
@@ -342,6 +344,7 @@ async fn refresh_failure_is_scoped_to_the_matching_auth_snapshot() {
|
||||
let auth = super::load_auth(
|
||||
codex_home.path(),
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
/*enable_codex_access_token_env*/ true,
|
||||
AuthCredentialsStoreMode::File,
|
||||
/*chatgpt_base_url*/ None,
|
||||
)
|
||||
@@ -743,6 +746,7 @@ async fn load_auth_reads_access_token_from_env() {
|
||||
let auth = super::load_auth(
|
||||
codex_home.path(),
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
/*enable_codex_access_token_env*/ true,
|
||||
AuthCredentialsStoreMode::File,
|
||||
Some(&chatgpt_base_url),
|
||||
)
|
||||
@@ -774,6 +778,7 @@ async fn load_auth_keeps_codex_api_key_env_precedence() {
|
||||
let auth = super::load_auth(
|
||||
codex_home.path(),
|
||||
/*enable_codex_api_key_env*/ true,
|
||||
/*enable_codex_access_token_env*/ true,
|
||||
AuthCredentialsStoreMode::File,
|
||||
/*chatgpt_base_url*/ None,
|
||||
)
|
||||
@@ -784,6 +789,85 @@ async fn load_auth_keeps_codex_api_key_env_precedence() {
|
||||
assert_eq!(auth.api_key(), Some("sk-env"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial(codex_auth_env)]
|
||||
async fn load_auth_can_ignore_access_token_env_for_persisted_chatgpt() {
|
||||
let codex_home = tempdir().unwrap();
|
||||
let record = agent_identity_record(WORKSPACE_ID_ALLOWED);
|
||||
let agent_identity = fake_agent_identity_jwt(&record).expect("fake agent identity");
|
||||
let _access_token_guard = EnvVarGuard::set(CODEX_ACCESS_TOKEN_ENV_VAR, &agent_identity);
|
||||
write_auth_file(
|
||||
AuthFileParams {
|
||||
openai_api_key: None,
|
||||
chatgpt_plan_type: Some("pro".to_string()),
|
||||
chatgpt_account_id: None,
|
||||
},
|
||||
codex_home.path(),
|
||||
)
|
||||
.expect("failed to write auth file");
|
||||
|
||||
let auth = super::load_auth(
|
||||
codex_home.path(),
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
/*enable_codex_access_token_env*/ false,
|
||||
AuthCredentialsStoreMode::File,
|
||||
/*chatgpt_base_url*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("persisted auth should load")
|
||||
.expect("persisted auth should be present");
|
||||
|
||||
assert!(auth.is_chatgpt_auth());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial(codex_auth_env)]
|
||||
async fn load_auth_can_ignore_api_key_env_for_agent_identity_selection() {
|
||||
let codex_home = tempdir().unwrap();
|
||||
let expected_record = agent_identity_record(WORKSPACE_ID_ALLOWED);
|
||||
let agent_identity =
|
||||
signed_agent_identity_jwt(&expected_record, json!(expected_record.plan_type))
|
||||
.expect("signed agent identity");
|
||||
let server = MockServer::start().await;
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/backend-api/wham/agent-identities/jwks"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(test_jwks_body()))
|
||||
.expect(1)
|
||||
.mount(&server)
|
||||
.await;
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/backend-api/v1/agent/agent-runtime-id/task/register"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"task_id": "task-123",
|
||||
})))
|
||||
.expect(1)
|
||||
.mount(&server)
|
||||
.await;
|
||||
let _access_token_guard = EnvVarGuard::set(CODEX_ACCESS_TOKEN_ENV_VAR, &agent_identity);
|
||||
let _api_key_guard = EnvVarGuard::set(CODEX_API_KEY_ENV_VAR, "sk-env");
|
||||
|
||||
let chatgpt_base_url = format!("{}/backend-api", server.uri());
|
||||
let _authapi_guard =
|
||||
EnvVarGuard::set("CODEX_AGENT_IDENTITY_AUTHAPI_BASE_URL", &chatgpt_base_url);
|
||||
let auth = super::load_auth(
|
||||
codex_home.path(),
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
/*enable_codex_access_token_env*/ true,
|
||||
AuthCredentialsStoreMode::File,
|
||||
Some(&chatgpt_base_url),
|
||||
)
|
||||
.await
|
||||
.expect("env auth should load")
|
||||
.expect("env auth should be present");
|
||||
|
||||
let CodexAuth::AgentIdentity(agent_identity) = auth else {
|
||||
panic!("env auth should load as agent identity");
|
||||
};
|
||||
assert_eq!(agent_identity.record(), &expected_record);
|
||||
assert_eq!(agent_identity.process_task_id(), "task-123");
|
||||
server.verify().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial(codex_auth_env)]
|
||||
async fn enforce_login_restrictions_logs_out_for_method_mismatch() {
|
||||
@@ -1182,6 +1266,7 @@ async fn plan_type_maps_known_plan() {
|
||||
let auth = super::load_auth(
|
||||
codex_home.path(),
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
/*enable_codex_access_token_env*/ true,
|
||||
AuthCredentialsStoreMode::File,
|
||||
/*chatgpt_base_url*/ None,
|
||||
)
|
||||
@@ -1210,6 +1295,7 @@ async fn plan_type_maps_self_serve_business_usage_based_plan() {
|
||||
let auth = super::load_auth(
|
||||
codex_home.path(),
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
/*enable_codex_access_token_env*/ true,
|
||||
AuthCredentialsStoreMode::File,
|
||||
/*chatgpt_base_url*/ None,
|
||||
)
|
||||
@@ -1241,6 +1327,7 @@ async fn plan_type_maps_enterprise_cbp_usage_based_plan() {
|
||||
let auth = super::load_auth(
|
||||
codex_home.path(),
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
/*enable_codex_access_token_env*/ true,
|
||||
AuthCredentialsStoreMode::File,
|
||||
/*chatgpt_base_url*/ None,
|
||||
)
|
||||
@@ -1272,6 +1359,7 @@ async fn plan_type_maps_unknown_to_unknown() {
|
||||
let auth = super::load_auth(
|
||||
codex_home.path(),
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
/*enable_codex_access_token_env*/ true,
|
||||
AuthCredentialsStoreMode::File,
|
||||
/*chatgpt_base_url*/ None,
|
||||
)
|
||||
@@ -1300,6 +1388,7 @@ async fn missing_plan_type_maps_to_unknown() {
|
||||
let auth = super::load_auth(
|
||||
codex_home.path(),
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
/*enable_codex_access_token_env*/ true,
|
||||
AuthCredentialsStoreMode::File,
|
||||
/*chatgpt_base_url*/ None,
|
||||
)
|
||||
|
||||
@@ -246,6 +246,7 @@ impl CodexAuth {
|
||||
load_auth(
|
||||
codex_home,
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
/*enable_codex_access_token_env*/ true,
|
||||
auth_credentials_store_mode,
|
||||
chatgpt_base_url,
|
||||
)
|
||||
@@ -618,6 +619,7 @@ 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,
|
||||
/*enable_codex_access_token_env*/ true,
|
||||
config.auth_credentials_store_mode,
|
||||
config.chatgpt_base_url.as_deref(),
|
||||
)
|
||||
@@ -731,6 +733,7 @@ fn logout_all_stores(
|
||||
async fn load_auth(
|
||||
codex_home: &Path,
|
||||
enable_codex_api_key_env: bool,
|
||||
enable_codex_access_token_env: bool,
|
||||
auth_credentials_store_mode: AuthCredentialsStoreMode,
|
||||
chatgpt_base_url: Option<&str>,
|
||||
) -> std::io::Result<Option<CodexAuth>> {
|
||||
@@ -761,7 +764,9 @@ async fn load_auth(
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if let Some(agent_identity) = read_codex_access_token_from_env() {
|
||||
if enable_codex_access_token_env
|
||||
&& let Some(agent_identity) = read_codex_access_token_from_env()
|
||||
{
|
||||
return CodexAuth::from_agent_identity_jwt(&agent_identity, chatgpt_base_url)
|
||||
.await
|
||||
.map(Some);
|
||||
@@ -1253,6 +1258,7 @@ pub struct AuthManager {
|
||||
codex_home: PathBuf,
|
||||
inner: RwLock<CachedAuth>,
|
||||
enable_codex_api_key_env: bool,
|
||||
enable_codex_access_token_env: bool,
|
||||
auth_credentials_store_mode: AuthCredentialsStoreMode,
|
||||
forced_chatgpt_workspace_id: RwLock<Option<Vec<String>>>,
|
||||
chatgpt_base_url: Option<String>,
|
||||
@@ -1286,6 +1292,10 @@ impl Debug for AuthManager {
|
||||
.field("codex_home", &self.codex_home)
|
||||
.field("inner", &self.inner)
|
||||
.field("enable_codex_api_key_env", &self.enable_codex_api_key_env)
|
||||
.field(
|
||||
"enable_codex_access_token_env",
|
||||
&self.enable_codex_access_token_env,
|
||||
)
|
||||
.field(
|
||||
"auth_credentials_store_mode",
|
||||
&self.auth_credentials_store_mode,
|
||||
@@ -1310,10 +1320,28 @@ impl AuthManager {
|
||||
enable_codex_api_key_env: bool,
|
||||
auth_credentials_store_mode: AuthCredentialsStoreMode,
|
||||
chatgpt_base_url: Option<String>,
|
||||
) -> Self {
|
||||
Self::new_with_env(
|
||||
codex_home,
|
||||
enable_codex_api_key_env,
|
||||
/*enable_codex_access_token_env*/ true,
|
||||
auth_credentials_store_mode,
|
||||
chatgpt_base_url,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn new_with_env(
|
||||
codex_home: PathBuf,
|
||||
enable_codex_api_key_env: bool,
|
||||
enable_codex_access_token_env: bool,
|
||||
auth_credentials_store_mode: AuthCredentialsStoreMode,
|
||||
chatgpt_base_url: Option<String>,
|
||||
) -> Self {
|
||||
let managed_auth = load_auth(
|
||||
&codex_home,
|
||||
enable_codex_api_key_env,
|
||||
enable_codex_access_token_env,
|
||||
auth_credentials_store_mode,
|
||||
chatgpt_base_url.as_deref(),
|
||||
)
|
||||
@@ -1327,6 +1355,7 @@ impl AuthManager {
|
||||
permanent_refresh_failure: None,
|
||||
}),
|
||||
enable_codex_api_key_env,
|
||||
enable_codex_access_token_env,
|
||||
auth_credentials_store_mode,
|
||||
forced_chatgpt_workspace_id: RwLock::new(None),
|
||||
chatgpt_base_url,
|
||||
@@ -1346,6 +1375,7 @@ impl AuthManager {
|
||||
codex_home: PathBuf::from("non-existent"),
|
||||
inner: RwLock::new(cached),
|
||||
enable_codex_api_key_env: false,
|
||||
enable_codex_access_token_env: false,
|
||||
auth_credentials_store_mode: AuthCredentialsStoreMode::File,
|
||||
forced_chatgpt_workspace_id: RwLock::new(None),
|
||||
chatgpt_base_url: None,
|
||||
@@ -1364,6 +1394,7 @@ impl AuthManager {
|
||||
codex_home,
|
||||
inner: RwLock::new(cached),
|
||||
enable_codex_api_key_env: false,
|
||||
enable_codex_access_token_env: false,
|
||||
auth_credentials_store_mode: AuthCredentialsStoreMode::File,
|
||||
forced_chatgpt_workspace_id: RwLock::new(None),
|
||||
chatgpt_base_url: None,
|
||||
@@ -1380,6 +1411,7 @@ impl AuthManager {
|
||||
permanent_refresh_failure: None,
|
||||
}),
|
||||
enable_codex_api_key_env: false,
|
||||
enable_codex_access_token_env: false,
|
||||
auth_credentials_store_mode: AuthCredentialsStoreMode::File,
|
||||
forced_chatgpt_workspace_id: RwLock::new(None),
|
||||
chatgpt_base_url: None,
|
||||
@@ -1518,6 +1550,7 @@ impl AuthManager {
|
||||
load_auth(
|
||||
&self.codex_home,
|
||||
self.enable_codex_api_key_env,
|
||||
self.enable_codex_access_token_env,
|
||||
self.auth_credentials_store_mode,
|
||||
self.chatgpt_base_url.as_deref(),
|
||||
)
|
||||
@@ -1602,14 +1635,49 @@ impl AuthManager {
|
||||
)
|
||||
}
|
||||
|
||||
async fn shared_with_env(
|
||||
codex_home: PathBuf,
|
||||
enable_codex_api_key_env: bool,
|
||||
enable_codex_access_token_env: bool,
|
||||
auth_credentials_store_mode: AuthCredentialsStoreMode,
|
||||
chatgpt_base_url: Option<String>,
|
||||
) -> Arc<Self> {
|
||||
Arc::new(
|
||||
Self::new_with_env(
|
||||
codex_home,
|
||||
enable_codex_api_key_env,
|
||||
enable_codex_access_token_env,
|
||||
auth_credentials_store_mode,
|
||||
chatgpt_base_url,
|
||||
)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
|
||||
/// Convenience constructor returning an `Arc` wrapper from resolved config.
|
||||
pub async fn shared_from_config(
|
||||
config: &impl AuthManagerConfig,
|
||||
enable_codex_api_key_env: bool,
|
||||
) -> Arc<Self> {
|
||||
let auth_manager = Self::shared(
|
||||
Self::shared_from_config_with_env(
|
||||
config,
|
||||
enable_codex_api_key_env,
|
||||
/*enable_codex_access_token_env*/ true,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Convenience constructor returning an `Arc` wrapper from resolved config,
|
||||
/// with explicit control over environment-backed auth.
|
||||
pub async fn shared_from_config_with_env(
|
||||
config: &impl AuthManagerConfig,
|
||||
enable_codex_api_key_env: bool,
|
||||
enable_codex_access_token_env: bool,
|
||||
) -> Arc<Self> {
|
||||
let auth_manager = Self::shared_with_env(
|
||||
config.codex_home(),
|
||||
enable_codex_api_key_env,
|
||||
enable_codex_access_token_env,
|
||||
config.cli_auth_credentials_store_mode(),
|
||||
Some(config.chatgpt_base_url()),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user