Download codex-rs/cli/tests/login.rs from SaylorTwift/codex: direct link, hf CLI and curl.
- Browser
- Download file 12.2 kB
-
https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/cli/tests/login.rs
- Command line
-
hf download hf://SaylorTwift/codex/codex-rs/cli/tests/login.rs
-
curl -L -o login.rs https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/cli/tests/login.rs
12.2 kB
| use std::path::Path; | |
| use std::sync::Arc; | |
| use std::sync::atomic::AtomicUsize; | |
| use std::sync::atomic::Ordering; | |
| use anyhow::Context; | |
| use anyhow::Result; | |
| use app_test_support::ChatGptAuthFixture; | |
| use app_test_support::write_chatgpt_auth; | |
| use codex_config::types::AuthCredentialsStoreMode; | |
| use codex_login::AuthKeyringBackendKind; | |
| use codex_login::CLIENT_ID; | |
| use codex_login::CODEX_ACCESS_TOKEN_ENV_VAR; | |
| use codex_login::REVOKE_TOKEN_URL_OVERRIDE_ENV_VAR; | |
| use codex_login::login_with_bedrock_access_keys; | |
| use codex_protocol::shell_environment::OPENAI_FEDERATION_RULE_ID_ENV_VAR; | |
| use codex_protocol::shell_environment::OPENAI_IDENTITY_TOKEN_FILE_ENV_VAR; | |
| use predicates::str::contains; | |
| use pretty_assertions::assert_eq; | |
| use serde_json::Value; | |
| use serde_json::json; | |
| use tempfile::TempDir; | |
| use wiremock::Mock; | |
| use wiremock::MockServer; | |
| use wiremock::ResponseTemplate; | |
| use wiremock::matchers::header; | |
| use wiremock::matchers::method; | |
| use wiremock::matchers::path; | |
| fn codex_command(codex_home: &Path) -> Result<assert_cmd::Command> { | |
| let mut cmd = assert_cmd::Command::new(codex_utils_cargo_bin::cargo_bin("codex")?); | |
| cmd.env("CODEX_HOME", codex_home); | |
| Ok(cmd) | |
| } | |
| fn write_file_auth_config(codex_home: &Path) -> Result<()> { | |
| std::fs::write( | |
| codex_home.join("config.toml"), | |
| "cli_auth_credentials_store = \"file\"\n", | |
| )?; | |
| Ok(()) | |
| } | |
| fn read_auth_json(codex_home: &Path) -> Result<Value> { | |
| let auth_json = std::fs::read_to_string(codex_home.join("auth.json"))?; | |
| Ok(serde_json::from_str(&auth_json)?) | |
| } | |
| fn login_with_api_key_reads_stdin_and_writes_auth_json() -> Result<()> { | |
| let codex_home = TempDir::new()?; | |
| write_file_auth_config(codex_home.path())?; | |
| let mut cmd = codex_command(codex_home.path())?; | |
| cmd.args([ | |
| "-c", | |
| "forced_login_method=\"api\"", | |
| "login", | |
| "--with-api-key", | |
| ]) | |
| .write_stdin("sk-test\n") | |
| .assert() | |
| .success() | |
| .stderr(contains("Successfully logged in")); | |
| let auth = read_auth_json(codex_home.path())?; | |
| assert_eq!(auth["OPENAI_API_KEY"], "sk-test"); | |
| assert!(auth.get("tokens").is_none()); | |
| assert!(auth.get("agent_identity").is_none()); | |
| Ok(()) | |
| } | |
| fn login_status_reports_auth_storage_errors() -> Result<()> { | |
| let codex_home = TempDir::new()?; | |
| write_file_auth_config(codex_home.path())?; | |
| std::fs::write(codex_home.path().join("auth.json"), "{invalid json")?; | |
| codex_command(codex_home.path())? | |
| .args(["login", "status"]) | |
| .assert() | |
| .failure() | |
| .stderr(contains("Error checking login status:")); | |
| Ok(()) | |
| } | |
| fn login_status_validates_configured_workload_identity() -> Result<()> { | |
| let codex_home = TempDir::new()?; | |
| write_file_auth_config(codex_home.path())?; | |
| let missing_assertion = codex_home.path().join("missing-identity-token"); | |
| codex_command(codex_home.path())? | |
| .env_remove(CODEX_ACCESS_TOKEN_ENV_VAR) | |
| .env(OPENAI_FEDERATION_RULE_ID_ENV_VAR, "rule-test") | |
| .env(OPENAI_IDENTITY_TOKEN_FILE_ENV_VAR, &missing_assertion) | |
| .args(["login", "status"]) | |
| .assert() | |
| .failure() | |
| .stderr(contains("could not read workload identity assertion file")); | |
| Ok(()) | |
| } | |
| fn logout_clears_only_the_selected_bedrock_provider() -> Result<()> { | |
| for (model_provider_id, managed_bedrock_auth, model) in [ | |
| ("amazon-bedrock", true, "openai.gpt-5.6-sol"), | |
| ("amazon-bedrock-runtime", true, "global.openai.gpt-5.6-sol"), | |
| ("openai", true, "gpt-5.6-sol"), | |
| ("amazon-bedrock", false, "gpt-5.6-sol"), | |
| ("amazon-bedrock-runtime", false, "us.openai.gpt-5.6-sol"), | |
| ("openai", false, "gpt-5.6-sol"), | |
| ] { | |
| let codex_home = TempDir::new()?; | |
| let config_path = codex_home.path().join("config.toml"); | |
| std::fs::write( | |
| &config_path, | |
| format!( | |
| "cli_auth_credentials_store = \"file\"\n\ | |
| model_provider = \"{model_provider_id}\"\n\ | |
| model = \"{model}\"\n\ | |
| model_reasoning_effort = \"high\"\n\ | |
| [model_providers.amazon-bedrock]\n\ | |
| base_url = \"https://mantle.example.com/v1\"\n\ | |
| [model_providers.amazon-bedrock.aws]\n\ | |
| profile = \"mantle-profile\"\n\ | |
| region = \"us-west-2\"\n\ | |
| auth_refresh = {{ command = \"aws\", args = [\"sso\", \"login\"] }}\n\ | |
| [model_providers.amazon-bedrock-runtime]\n\ | |
| base_url = \"https://runtime.example.com/v1\"\n\ | |
| [model_providers.amazon-bedrock-runtime.aws]\n\ | |
| profile = \"runtime-profile\"\n\ | |
| region = \"us-east-1\"\n\ | |
| auth_refresh = {{ command = \"aws\", args = [\"login\"] }}\n" | |
| ), | |
| )?; | |
| if managed_bedrock_auth { | |
| login_with_bedrock_access_keys( | |
| codex_home.path(), | |
| "managed-access-key-id", | |
| "managed-secret-access-key", | |
| Some("managed-session-token"), | |
| AuthCredentialsStoreMode::File, | |
| AuthKeyringBackendKind::default(), | |
| )?; | |
| } | |
| let mut expected_config: toml::Value = | |
| toml::from_str(&std::fs::read_to_string(&config_path)?)?; | |
| if model_provider_id != "openai" { | |
| let expected_root = expected_config | |
| .as_table_mut() | |
| .expect("config should be a table"); | |
| expected_root.remove("model_provider"); | |
| expected_root.remove("model"); | |
| expected_root["model_providers"][model_provider_id] | |
| .as_table_mut() | |
| .expect("selected Bedrock provider should be a table") | |
| .remove("aws"); | |
| } | |
| let expected_message = if managed_bedrock_auth || model_provider_id != "openai" { | |
| "Successfully logged out" | |
| } else { | |
| "Not logged in" | |
| }; | |
| codex_command(codex_home.path())? | |
| .env_remove(CODEX_ACCESS_TOKEN_ENV_VAR) | |
| .env("AWS_ACCESS_KEY_ID", "environment-access-key-id") | |
| .env("AWS_SECRET_ACCESS_KEY", "environment-secret-access-key") | |
| .args(["logout"]) | |
| .assert() | |
| .success() | |
| .stderr(contains(expected_message)); | |
| assert!(!codex_home.path().join("auth.json").exists()); | |
| let actual_config: toml::Value = toml::from_str(&std::fs::read_to_string(&config_path)?)?; | |
| assert_eq!(actual_config, expected_config); | |
| } | |
| Ok(()) | |
| } | |
| fn login_with_access_token_rejects_invalid_jwt() -> Result<()> { | |
| let codex_home = TempDir::new()?; | |
| write_file_auth_config(codex_home.path())?; | |
| let mut cmd = codex_command(codex_home.path())?; | |
| cmd.args(["login", "--with-access-token"]) | |
| .write_stdin("not-a-jwt\n") | |
| .assert() | |
| .failure() | |
| .stderr(contains("Error logging in with access token")); | |
| Ok(()) | |
| } | |
| async fn debug_prompt_input_follows_authenticated_attribution_setting() -> Result<()> { | |
| let server = MockServer::start().await; | |
| let request_count = Arc::new(AtomicUsize::new(0)); | |
| Mock::given(method("GET")) | |
| .and(path("/backend-api/wham/settings/user")) | |
| .and(header("chatgpt-account-id", "workspace-123")) | |
| .respond_with(move |_request: &wiremock::Request| { | |
| ResponseTemplate::new(200).set_body_json(json!({ | |
| "commit_attribution_enabled": request_count.fetch_add(1, Ordering::SeqCst) == 0, | |
| })) | |
| }) | |
| .expect(2) | |
| .mount(&server) | |
| .await; | |
| let codex_home = TempDir::new()?; | |
| std::fs::write( | |
| codex_home.path().join("config.toml"), | |
| format!( | |
| "cli_auth_credentials_store = \"file\"\nchatgpt_base_url = \"{}/backend-api\"\n", | |
| server.uri() | |
| ), | |
| )?; | |
| write_chatgpt_auth( | |
| codex_home.path(), | |
| ChatGptAuthFixture::new("chatgpt-token") | |
| .account_id("workspace-123") | |
| .plan_type("enterprise"), | |
| AuthCredentialsStoreMode::File, | |
| )?; | |
| for enabled in [true, false] { | |
| let output = codex_command(codex_home.path())? | |
| .env("NO_PROXY", "127.0.0.1,localhost") | |
| .env("no_proxy", "127.0.0.1,localhost") | |
| .env_remove("CODEX_ACCESS_TOKEN") | |
| .env_remove("OPENAI_API_KEY") | |
| .args(["debug", "prompt-input"]) | |
| .output()?; | |
| assert!(output.status.success()); | |
| let prompt = String::from_utf8(output.stdout)?; | |
| assert_eq!( | |
| prompt.contains("Co-authored-by: Codex <noreply@openai.com>"), | |
| enabled | |
| ); | |
| assert!(!prompt.contains("attribution is disabled for the current workspace")); | |
| } | |
| server.verify().await; | |
| Ok(()) | |
| } | |
| async fn device_login_revokes_existing_auth_before_requesting_new_tokens() -> Result<()> { | |
| let server = MockServer::start().await; | |
| Mock::given(method("POST")) | |
| .and(path("/oauth/revoke")) | |
| .respond_with(ResponseTemplate::new(200)) | |
| .expect(1) | |
| .mount(&server) | |
| .await; | |
| Mock::given(method("POST")) | |
| .and(path("/api/accounts/deviceauth/usercode")) | |
| .respond_with(ResponseTemplate::new(200).set_body_json(json!({ | |
| "device_auth_id": "device-auth-123", | |
| "user_code": "CODE-12345", | |
| "interval": "0", | |
| }))) | |
| .expect(1) | |
| .mount(&server) | |
| .await; | |
| Mock::given(method("POST")) | |
| .and(path("/api/accounts/deviceauth/token")) | |
| .respond_with(ResponseTemplate::new(200).set_body_json(json!({ | |
| "authorization_code": "authorization-code-123", | |
| "code_challenge": "code-challenge-123", | |
| "code_verifier": "code-verifier-123", | |
| }))) | |
| .expect(1) | |
| .mount(&server) | |
| .await; | |
| Mock::given(method("POST")) | |
| .and(path("/oauth/token")) | |
| .respond_with(ResponseTemplate::new(200).set_body_json(json!({ | |
| "id_token": "eyJhbGciOiJub25lIn0.e30.c2ln", | |
| "access_token": "new-access", | |
| "refresh_token": "new-refresh", | |
| }))) | |
| .expect(1) | |
| .mount(&server) | |
| .await; | |
| let codex_home = TempDir::new()?; | |
| write_file_auth_config(codex_home.path())?; | |
| std::fs::write( | |
| codex_home.path().join("auth.json"), | |
| serde_json::to_vec(&json!({ | |
| "auth_mode": "chatgpt", | |
| "OPENAI_API_KEY": null, | |
| "tokens": { | |
| "id_token": "eyJhbGciOiJub25lIn0.e30.c2ln", | |
| "access_token": "old-access", | |
| "refresh_token": "old-refresh", | |
| "account_id": "old-account", | |
| }, | |
| }))?, | |
| )?; | |
| let issuer = server.uri(); | |
| let mut cmd = codex_command(codex_home.path())?; | |
| cmd.env( | |
| REVOKE_TOKEN_URL_OVERRIDE_ENV_VAR, | |
| format!("{issuer}/oauth/revoke"), | |
| ) | |
| .env("NO_PROXY", "127.0.0.1,localhost") | |
| .env("no_proxy", "127.0.0.1,localhost") | |
| .env_remove("CODEX_ACCESS_TOKEN") | |
| .env_remove("OPENAI_API_KEY") | |
| .args(["login", "--device-auth", "--experimental_issuer", &issuer]) | |
| .assert() | |
| .success() | |
| .stderr(contains("Successfully logged in")); | |
| let requests = server | |
| .received_requests() | |
| .await | |
| .context("failed to read mock OAuth requests")?; | |
| let paths: Vec<&str> = requests.iter().map(|request| request.url.path()).collect(); | |
| assert_eq!( | |
| paths, | |
| vec![ | |
| "/oauth/revoke", | |
| "/api/accounts/deviceauth/usercode", | |
| "/api/accounts/deviceauth/token", | |
| "/oauth/token", | |
| ] | |
| ); | |
| assert_eq!( | |
| requests[0] | |
| .body_json::<Value>() | |
| .context("revoke request should be JSON")?, | |
| json!({ | |
| "token": "old-refresh", | |
| "token_type_hint": "refresh_token", | |
| "client_id": CLIENT_ID, | |
| }) | |
| ); | |
| let auth = read_auth_json(codex_home.path())?; | |
| assert_eq!(auth["tokens"]["refresh_token"], "new-refresh"); | |
| Ok(()) | |
| } | |