Skip to content

Commit 184fb02

Browse files
aibrahim-oaicodex
andcommitted
Extract codex-auth from codex-core
Co-authored-by: Codex <noreply@openai.com>
1 parent 1cf68f9 commit 184fb02

File tree

18 files changed

+1119
-728
lines changed

18 files changed

+1119
-728
lines changed

codex-rs/Cargo.lock

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codex-rs/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
members = [
33
"backend-client",
44
"ansi-escape",
5+
"auth",
56
"async-utils",
67
"app-server",
78
"app-server-client",
@@ -86,6 +87,7 @@ license = "Apache-2.0"
8687
app_test_support = { path = "app-server/tests/common" }
8788
codex-ansi-escape = { path = "ansi-escape" }
8889
codex-api = { path = "codex-api" }
90+
codex-auth = { path = "auth" }
8991
codex-artifacts = { path = "artifacts" }
9092
codex-package-manager = { path = "package-manager" }
9193
codex-app-server = { path = "app-server" }

codex-rs/auth/BUILD.bazel

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
load("//:defs.bzl", "codex_rust_crate")
2+
3+
codex_rust_crate(
4+
name = "auth",
5+
crate_name = "codex_auth",
6+
)

codex-rs/auth/Cargo.toml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
[package]
2+
name = "codex-auth"
3+
version.workspace = true
4+
edition.workspace = true
5+
license.workspace = true
6+
7+
[lints]
8+
workspace = true
9+
10+
[dependencies]
11+
base64 = { workspace = true }
12+
chrono = { workspace = true, features = ["serde"] }
13+
codex-app-server-protocol = { workspace = true }
14+
codex-keyring-store = { workspace = true }
15+
once_cell = { workspace = true }
16+
schemars = { workspace = true }
17+
serde = { workspace = true, features = ["derive"] }
18+
serde_json = { workspace = true }
19+
sha2 = { workspace = true }
20+
thiserror = { workspace = true }
21+
tracing = { workspace = true }
22+
23+
[target.'cfg(target_os = "linux")'.dependencies]
24+
keyring = { workspace = true, features = ["linux-native-async-persistent"] }
25+
26+
[target.'cfg(target_os = "macos")'.dependencies]
27+
keyring = { workspace = true, features = ["apple-native"] }
28+
29+
[target.'cfg(target_os = "windows")'.dependencies]
30+
keyring = { workspace = true, features = ["windows-native"] }
31+
32+
[target.'cfg(any(target_os = "freebsd", target_os = "openbsd"))'.dependencies]
33+
keyring = { workspace = true, features = ["sync-secret-service"] }
34+
35+
[dev-dependencies]
36+
anyhow = { workspace = true }
37+
pretty_assertions = { workspace = true }
38+
tempfile = { workspace = true }
39+
tokio = { workspace = true, features = ["macros", "rt"] }

codex-rs/auth/src/env_telemetry.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use crate::CODEX_API_KEY_ENV_VAR;
2+
use crate::OPENAI_API_KEY_ENV_VAR;
3+
use crate::REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR;
4+
5+
#[derive(Debug, Clone, Default, PartialEq, Eq)]
6+
pub struct AuthEnvTelemetry {
7+
pub openai_api_key_env_present: bool,
8+
pub codex_api_key_env_present: bool,
9+
pub codex_api_key_env_enabled: bool,
10+
pub provider_env_key_name: Option<String>,
11+
pub provider_env_key_present: Option<bool>,
12+
pub refresh_token_url_override_present: bool,
13+
}
14+
15+
pub fn collect_auth_env_telemetry(
16+
provider_env_key_configured: bool,
17+
provider_env_key: Option<&str>,
18+
codex_api_key_env_enabled: bool,
19+
) -> AuthEnvTelemetry {
20+
AuthEnvTelemetry {
21+
openai_api_key_env_present: env_var_present(OPENAI_API_KEY_ENV_VAR),
22+
codex_api_key_env_present: env_var_present(CODEX_API_KEY_ENV_VAR),
23+
codex_api_key_env_enabled,
24+
provider_env_key_name: provider_env_key_configured.then(|| "configured".to_string()),
25+
provider_env_key_present: provider_env_key.map(env_var_present),
26+
refresh_token_url_override_present: env_var_present(REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR),
27+
}
28+
}
29+
30+
fn env_var_present(name: &str) -> bool {
31+
match std::env::var(name) {
32+
Ok(value) => !value.trim().is_empty(),
33+
Err(std::env::VarError::NotUnicode(_)) => true,
34+
Err(std::env::VarError::NotPresent) => false,
35+
}
36+
}
37+
38+
#[cfg(test)]
39+
mod tests {
40+
use super::*;
41+
use pretty_assertions::assert_eq;
42+
43+
#[test]
44+
fn collect_auth_env_telemetry_buckets_provider_env_key_name() {
45+
let telemetry = collect_auth_env_telemetry(true, Some("sk-should-not-leak"), false);
46+
47+
assert_eq!(
48+
telemetry.provider_env_key_name,
49+
Some("configured".to_string())
50+
);
51+
}
52+
}

codex-rs/auth/src/lib.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
mod env_telemetry;
2+
pub mod storage;
3+
pub mod token_data;
4+
5+
use std::env;
6+
use std::path::Path;
7+
8+
use codex_app_server_protocol::AuthMode;
9+
10+
pub use env_telemetry::AuthEnvTelemetry;
11+
pub use env_telemetry::collect_auth_env_telemetry;
12+
pub use storage::AuthCredentialsStoreMode;
13+
pub use storage::AuthDotJson;
14+
pub use storage::AuthStorageBackend;
15+
pub use storage::create_auth_storage;
16+
pub use token_data::IdTokenInfo;
17+
pub use token_data::IdTokenInfoError;
18+
pub use token_data::KnownPlan;
19+
pub use token_data::PlanType;
20+
pub use token_data::TokenData;
21+
pub use token_data::parse_chatgpt_jwt_claims;
22+
23+
pub const OPENAI_API_KEY_ENV_VAR: &str = "OPENAI_API_KEY";
24+
pub const CODEX_API_KEY_ENV_VAR: &str = "CODEX_API_KEY";
25+
pub const REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR: &str = "CODEX_REFRESH_TOKEN_URL_OVERRIDE";
26+
27+
#[derive(Clone, Debug, PartialEq, Eq)]
28+
pub struct ExternalAuthTokens {
29+
pub access_token: String,
30+
pub chatgpt_account_id: String,
31+
pub chatgpt_plan_type: Option<String>,
32+
}
33+
34+
pub fn read_openai_api_key_from_env() -> Option<String> {
35+
env::var(OPENAI_API_KEY_ENV_VAR)
36+
.ok()
37+
.map(|value| value.trim().to_string())
38+
.filter(|value| !value.is_empty())
39+
}
40+
41+
pub fn read_codex_api_key_from_env() -> Option<String> {
42+
env::var(CODEX_API_KEY_ENV_VAR)
43+
.ok()
44+
.map(|value| value.trim().to_string())
45+
.filter(|value| !value.is_empty())
46+
}
47+
48+
pub fn logout(
49+
codex_home: &Path,
50+
auth_credentials_store_mode: AuthCredentialsStoreMode,
51+
) -> std::io::Result<bool> {
52+
let storage = create_auth_storage(codex_home.to_path_buf(), auth_credentials_store_mode);
53+
storage.delete()
54+
}
55+
56+
pub fn login_with_api_key(
57+
codex_home: &Path,
58+
api_key: &str,
59+
auth_credentials_store_mode: AuthCredentialsStoreMode,
60+
) -> std::io::Result<()> {
61+
let auth_dot_json = AuthDotJson {
62+
auth_mode: Some(AuthMode::ApiKey),
63+
openai_api_key: Some(api_key.to_string()),
64+
tokens: None,
65+
last_refresh: None,
66+
};
67+
save_auth(codex_home, &auth_dot_json, auth_credentials_store_mode)
68+
}
69+
70+
pub fn login_with_chatgpt_auth_tokens(
71+
codex_home: &Path,
72+
access_token: &str,
73+
chatgpt_account_id: &str,
74+
chatgpt_plan_type: Option<&str>,
75+
) -> std::io::Result<()> {
76+
let auth_dot_json = AuthDotJson::from_external_access_token(
77+
access_token,
78+
chatgpt_account_id,
79+
chatgpt_plan_type,
80+
)?;
81+
save_auth(
82+
codex_home,
83+
&auth_dot_json,
84+
AuthCredentialsStoreMode::Ephemeral,
85+
)
86+
}
87+
88+
pub fn save_auth(
89+
codex_home: &Path,
90+
auth: &AuthDotJson,
91+
auth_credentials_store_mode: AuthCredentialsStoreMode,
92+
) -> std::io::Result<()> {
93+
let storage = create_auth_storage(codex_home.to_path_buf(), auth_credentials_store_mode);
94+
storage.save(auth)
95+
}
96+
97+
pub fn load_auth_dot_json(
98+
codex_home: &Path,
99+
auth_credentials_store_mode: AuthCredentialsStoreMode,
100+
) -> std::io::Result<Option<AuthDotJson>> {
101+
let storage = create_auth_storage(codex_home.to_path_buf(), auth_credentials_store_mode);
102+
storage.load()
103+
}

0 commit comments

Comments
 (0)