use super::*;
use crate::config::{Auth, AuthProviderRecord, read_auth, write_auth};
fn codex_auth_runtime(temp: &TempDir) -> Arc<ServiceRuntime> {
let mut runtime = runtime(temp);
let config = &mut Arc::get_mut(&mut runtime).unwrap().config;
config.provider = Some("openai-codex".into());
config.auth = Some(ProviderCredential::OAuth {
access: "synthetic-cached-access-secret".into(),
account_id: Some("synthetic-cached-account-secret".into()),
});
let mut settings = Settings::default();
settings.selected_model.provider = config.provider.clone();
settings.selected_model.model = config.model.clone();
crate::config::write_settings(&config.paths, &settings).unwrap();
runtime
}
fn invoke_auth_request(
coordinator: &mut Coordinator,
connection: &str,
method: &str,
payload: Value,
) -> Value {
let operation = (method == "auth.logout").then(|| uuid::Uuid::new_v4().to_string());
let request = request(
coordinator,
connection,
method,
None,
None,
operation.as_deref(),
payload,
);
let response = invoke(coordinator, connection, request.clone());
assert_eq!(response["protocol_version"], 2);
assert_eq!(response["kind"], "response");
assert_eq!(response["request_id"], request.request_id);
assert_eq!(response["connection_id"], connection);
assert_eq!(response["instance_id"], coordinator.instance);
assert_eq!(response["method"], method);
response
}
#[test]
fn shared_auth_status_tracks_store_changes_without_refreshing_credentials() {
let env = crate::test_support::env::env_lock();
let _provider = env.save("MC_PROVIDER");
let _model = env.save("MC_MODEL");
env.remove_var("MC_PROVIDER");
env.remove_var("MC_MODEL");
let temp = TempDir::new().unwrap();
let runtime = codex_auth_runtime(&temp);
let paths = runtime.config.paths.clone();
let mut coordinator = Coordinator::new(runtime).unwrap();
let connection = connect(&mut coordinator);
for (credential, ready) in [
(None, false),
(
Some(AuthProviderRecord::OAuth {
access: "synthetic-access-secret".into(),
refresh: Some("synthetic-refresh-secret".into()),
expires: Some(chrono::Utc::now().timestamp() + 3_600),
account_id: Some("synthetic-account-secret".into()),
}),
true,
),
(
Some(AuthProviderRecord::OAuth {
access: String::new(),
refresh: Some("synthetic-refresh-secret".into()),
expires: Some(1),
account_id: None,
}),
true,
),
(None, false),
] {
let mut auth = Auth::default();
if let Some(credential) = credential {
auth.providers.insert("openai-codex".into(), credential);
}
write_auth(&paths, &auth).unwrap();
let before = std::fs::read(&paths.auth_file).unwrap();
for method in ["status", "auth.status"] {
let response = invoke_auth_request(&mut coordinator, &connection, method, json!({}));
assert!(response["error"].is_null(), "{response}");
if method == "status" {
assert_eq!(response["payload"]["service"], "ready");
assert_eq!(response["payload"]["provider_auth_ready"], ready);
} else {
assert_eq!(response["payload"]["providers"][0]["ready"], ready);
}
assert!(!response.to_string().contains("secret"));
assert_eq!(std::fs::read(&paths.auth_file).unwrap(), before);
}
}
}
#[test]
fn shared_auth_read_failures_are_sanitized_and_correlated() {
let env = crate::test_support::env::env_lock();
let _provider = env.save("MC_PROVIDER");
let _model = env.save("MC_MODEL");
env.remove_var("MC_PROVIDER");
env.remove_var("MC_MODEL");
let temp = TempDir::new().unwrap();
let runtime = codex_auth_runtime(&temp);
let paths = runtime.config.paths.clone();
let mut coordinator = Coordinator::new(runtime).unwrap();
let connection = connect(&mut coordinator);
std::fs::write(
&paths.auth_file,
b"{\"malformed\":\"synthetic-raw-auth-secret\"",
)
.unwrap();
for method in ["status", "auth.status"] {
let response = invoke_auth_request(&mut coordinator, &connection, method, json!({}));
assert!(response["payload"].is_null());
assert_eq!(response["error"]["code"], "internal_error");
assert_eq!(response["error"]["message"], Code::InternalError.message());
let encoded = response.to_string();
for forbidden in [
"synthetic-raw-auth-secret",
"openai-codex",
paths.auth_file.to_str().unwrap(),
] {
assert!(!encoded.contains(forbidden));
}
}
}
#[test]
fn shared_auth_logout_requires_confirmation_and_preserves_other_providers() {
let temp = TempDir::new().unwrap();
let runtime = codex_auth_runtime(&temp);
let paths = runtime.config.paths.clone();
let mut auth = Auth::default();
auth.providers.insert(
"openai-codex".into(),
AuthProviderRecord::OAuth {
access: "synthetic-access-secret".into(),
refresh: Some("synthetic-refresh-secret".into()),
expires: Some(0),
account_id: Some("synthetic-account-secret".into()),
},
);
auth.providers.insert(
"anthropic".into(),
AuthProviderRecord::ApiKey {
key: "synthetic-api-secret".into(),
},
);
write_auth(&paths, &auth).unwrap();
let mut coordinator = Coordinator::new(runtime).unwrap();
let connection = coordinator.connect(Instant::now()).unwrap();
let before = invoke_auth_request(&mut coordinator, &connection, "auth.status", json!({}));
assert_eq!(before["error"]["code"], "not_initialized");
let init = request(
&coordinator,
&connection,
"initialize",
None,
None,
None,
json!({"supported_protocol_versions":[2], "requested_capabilities":[]}),
);
assert!(invoke(&mut coordinator, &connection, init)["error"].is_null());
let status = invoke_auth_request(&mut coordinator, &connection, "auth.status", json!({}));
assert_eq!(status["payload"]["providers"][0]["readiness"], "ready");
for forbidden in ["secret", "account_id", "auth.json"] {
assert!(!status.to_string().contains(forbidden));
}
for confirmation in [None, Some(false)] {
let mut payload = json!({"provider_id":"openai-codex"});
if let Some(confirmation) = confirmation {
payload["confirmed"] = json!(confirmation);
}
let denied = invoke_auth_request(&mut coordinator, &connection, "auth.logout", payload);
assert_eq!(denied["error"]["code"], "confirmation_required");
assert_eq!(read_auth(&paths).unwrap(), auth);
}
let removed = invoke_auth_request(
&mut coordinator,
&connection,
"auth.logout",
json!({"provider_id":"openai-codex", "confirmed":true}),
);
assert!(removed["error"].is_null(), "{removed}");
assert_eq!(removed["payload"]["removed"], true);
assert!(!removed.to_string().contains("secret"));
let stored = read_auth(&paths).unwrap();
assert!(!stored.providers.contains_key("openai-codex"));
assert_eq!(
stored.providers.get("anthropic"),
auth.providers.get("anthropic")
);
let status = invoke_auth_request(&mut coordinator, &connection, "auth.status", json!({}));
assert_eq!(status["payload"]["providers"][0]["readiness"], "missing");
}
#[test]
fn shared_auth_environment_and_no_auth_readiness_do_not_expose_values() {
let env = crate::test_support::env::env_lock();
let _anthropic = env.save("ANTHROPIC_API_KEY");
let _custom = env.save("MC_SERVICE_AUTH_TEST_KEY");
env.set_var("ANTHROPIC_API_KEY", "synthetic-anthropic-secret");
env.set_var("MC_SERVICE_AUTH_TEST_KEY", "synthetic-custom-secret");
let temp = TempDir::new().unwrap();
let mut runtime = runtime(&temp);
let config = &mut Arc::get_mut(&mut runtime).unwrap().config;
for (id, url, key) in [
("local", "http://localhost:9999/v1", ""),
(
"remote",
"https://example.test/v1",
"MC_SERVICE_AUTH_TEST_KEY",
),
] {
config.custom_providers.insert(
id.into(),
crate::config::make_custom_provider_config(id, url, key).unwrap(),
);
}
let mut coordinator = Coordinator::new(runtime).unwrap();
let connection = connect(&mut coordinator);
let status = invoke_auth_request(&mut coordinator, &connection, "auth.status", json!({}));
let providers = status["payload"]["providers"].as_array().unwrap();
assert_eq!(providers[1]["source"], "environment");
assert_eq!(providers[1]["ready"], true);
assert_eq!(providers[2]["auth_method"], "none");
assert_eq!(providers[2]["ready"], true);
assert_eq!(providers[3]["source"], "environment");
assert_eq!(providers[3]["ready"], true);
for forbidden in [
"secret",
"MC_SERVICE_AUTH_TEST_KEY",
"http://localhost",
"example.test",
] {
assert!(!status.to_string().contains(forbidden));
}
env.remove_var("MC_SERVICE_AUTH_TEST_KEY");
let missing = invoke_auth_request(&mut coordinator, &connection, "auth.status", json!({}));
assert_eq!(missing["payload"]["providers"][3]["readiness"], "missing");
}
#[test]
fn held_auth_lock_preserves_other_sessions_disconnect_and_accepted_logout() {
for (method, cross_process) in ["status", "auth.status", "auth.logout", "auth.login.start"]
.into_iter()
.flat_map(|method| [false, true].map(|cross_process| (method, cross_process)))
{
let temp = TempDir::new().unwrap();
let runtime = runtime(&temp);
let mut coordinator = Coordinator::new(Arc::clone(&runtime)).unwrap();
coordinator
.execution
.set_login_worker(Arc::new(|_| panic!("disconnected login ran")));
let owner = connect(&mut coordinator);
let lock =
crate::persistence::in_process_file_lock(&runtime.config.paths.auth_file, "auth")
.unwrap();
let held = (!cross_process).then(|| lock.lock().unwrap());
let file_lock = cross_process.then(|| {
crate::persistence::CrossProcessFileLock::acquire(&runtime.config.paths.auth_file)
.unwrap()
});
let work = request(
&coordinator,
&owner,
method,
None,
None,
matches!(method, "auth.logout" | "auth.login.start").then_some("auth-work"),
match method {
"auth.logout" => json!({"provider_id":"openai-codex", "confirmed":true}),
"auth.login.start" => json!({"provider_id":"openai-codex"}),
_ => json!({}),
},
);
let (completed, result) = bounded(1);
let worker = std::thread::spawn(move || {
coordinator
.submit(&owner, &serde_json::to_vec(&work).unwrap(), Instant::now())
.unwrap();
if method == "auth.logout" {
assert_eq!(
coordinator.operations.lookup(
&coordinator.instance,
&coordinator.instance,
"auth-work"
)["state"],
"accepted"
);
}
coordinator.disconnect(&owner);
let other = connect(&mut coordinator);
let session = create(&mut coordinator, &other);
claim(&mut coordinator, &other, &session);
completed.send(coordinator).unwrap();
});
let progress = result.recv_timeout(Duration::from_secs(2));
drop(held);
drop(file_lock);
worker.join().unwrap();
let mut coordinator = progress.expect("auth lock blocked disconnect or another session");
run_until(&mut coordinator, |state| {
state.auth_work.is_none() && state.login.is_none()
});
if method == "auth.logout" {
let outcome = coordinator.operations.lookup(
&coordinator.instance,
&coordinator.instance,
"auth-work",
);
assert_eq!(outcome["state"], "terminal", "{outcome}");
assert_eq!(outcome["result"]["status"], "completed", "{outcome}");
assert_eq!(
crate::config::read_auth_store(&runtime.config.paths)
.unwrap()
.provider_generation("openai-codex"),
1
);
}
if method == "auth.login.start" {
let outcome = coordinator.operations.lookup(
&coordinator.instance,
&coordinator.instance,
"auth-work",
);
assert_eq!(outcome["result"]["status"], "cancelled", "{outcome}");
}
}
}
#[test]
fn held_auth_lock_does_not_block_login_cancel_or_turn_terminal_cleanup() {
let temp = TempDir::new().unwrap();
let runtime = runtime(&temp);
let mut coordinator = Coordinator::new(Arc::clone(&runtime)).unwrap();
let (started, ready) = bounded(1);
coordinator.execution.set_login_worker(Arc::new(move |job| {
started.send(()).unwrap();
while !job.cancel.load(Ordering::Acquire) {
std::thread::sleep(Duration::from_millis(1));
}
"cancelled"
}));
let (finish, terminal) = bounded(1);
coordinator.execution.set_turn_worker(Arc::new(move |job| {
terminal.recv().unwrap();
job.sender
.send(TurnWorkerMessage::Terminal {
turn_id: job.turn_id.clone(),
event: ServiceEvent::turn_terminal(
job.request_id,
job.session.id().into(),
job.turn_id,
TurnTerminalStatus::Cancelled,
String::new(),
),
})
.unwrap();
}));
let owner = connect(&mut coordinator);
let other = connect(&mut coordinator);
let session = create(&mut coordinator, &other);
let grant = claim(&mut coordinator, &other, &session);
let start = request(
&coordinator,
&other,
"turn.start",
Some(&session),
Some(grant.clone()),
Some("turn"),
json!({"prompt":"hello"}),
);
let turn = invoke(&mut coordinator, &other, start);
let login = request(
&coordinator,
&owner,
"auth.login.start",
None,
None,
Some("login"),
json!({"provider_id":"openai-codex"}),
);
let login = invoke(&mut coordinator, &owner, login);
ready.recv_timeout(Duration::from_secs(2)).unwrap();
let lock =
crate::persistence::in_process_file_lock(&runtime.config.paths.auth_file, "auth").unwrap();
let held = lock.lock().unwrap();
let (completed, result) = bounded(1);
let worker = std::thread::spawn(move || {
let cancel = request(
&coordinator,
&owner,
"auth.login.cancel",
None,
None,
Some("cancel-login"),
json!({"login_id":login["payload"]["login_id"]}),
);
assert!(invoke(&mut coordinator, &owner, cancel)["error"].is_null());
let logout = request(
&coordinator,
&owner,
"auth.logout",
None,
None,
Some("logout"),
json!({"provider_id":"openai-codex", "confirmed":true}),
);
coordinator
.submit(
&owner,
&serde_json::to_vec(&logout).unwrap(),
Instant::now(),
)
.unwrap();
coordinator.disconnect(&owner);
let cancel = request(
&coordinator,
&other,
"turn.cancel",
Some(&session),
Some(grant),
Some("cancel-turn"),
json!({"turn_id":turn["payload"]["turn_id"]}),
);
coordinator
.submit(
&other,
&serde_json::to_vec(&cancel).unwrap(),
Instant::now(),
)
.unwrap();
finish.send(()).unwrap();
run_until(&mut coordinator, |state| {
state.actors[&session].phase == "idle"
});
assert!(coordinator.login.is_some());
assert!(coordinator.auth_work.is_some());
completed.send(coordinator).unwrap();
});
let progress = result.recv_timeout(Duration::from_secs(2));
drop(held);
worker.join().unwrap();
let mut coordinator = progress.expect("auth cancellation blocked turn cleanup");
run_until(&mut coordinator, |state| {
state.login.is_none() && state.auth_work.is_none()
});
let outcome =
coordinator
.operations
.lookup(&coordinator.instance, &coordinator.instance, "logout");
assert_eq!(outcome["result"]["status"], "completed", "{outcome}");
assert_eq!(
crate::config::read_auth_store(&runtime.config.paths)
.unwrap()
.provider_generation("openai-codex"),
1
);
}