mirror of
https://github.com/openai/codex.git
synced 2026-03-20 04:46:31 +03:00
Compare commits
25 Commits
bot/update
...
auth-crate
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0461e2a98b | ||
|
|
112d231f85 | ||
|
|
178a0dc31c | ||
|
|
d3c83a408a | ||
|
|
e57552f3e9 | ||
|
|
fa2b69dc78 | ||
|
|
7c9e4b492f | ||
|
|
7c374c18e0 | ||
|
|
37e4e55475 | ||
|
|
b6c3ecfd8b | ||
|
|
019ab69ee5 | ||
|
|
c13f83d8de | ||
|
|
e73bd63352 | ||
|
|
589fe5ffd9 | ||
|
|
5c3bdad1a8 | ||
|
|
e2cd38a787 | ||
|
|
cfbec84964 | ||
|
|
499c3190e0 | ||
|
|
59c056e43d | ||
|
|
905255deff | ||
|
|
a7173968c6 | ||
|
|
4b9dde5ebb | ||
|
|
9e6c404012 | ||
|
|
3661c085de | ||
|
|
df7a587291 |
20
codex-rs/Cargo.lock
generated
20
codex-rs/Cargo.lock
generated
@@ -1841,7 +1841,6 @@ dependencies = [
|
||||
"codex-arg0",
|
||||
"codex-artifacts",
|
||||
"codex-async-utils",
|
||||
"codex-client",
|
||||
"codex-config",
|
||||
"codex-connectors",
|
||||
"codex-exec-server",
|
||||
@@ -1849,7 +1848,7 @@ dependencies = [
|
||||
"codex-file-search",
|
||||
"codex-git",
|
||||
"codex-hooks",
|
||||
"codex-keyring-store",
|
||||
"codex-login",
|
||||
"codex-network-proxy",
|
||||
"codex-otel",
|
||||
"codex-protocol",
|
||||
@@ -1886,7 +1885,6 @@ dependencies = [
|
||||
"image",
|
||||
"indexmap 2.13.0",
|
||||
"insta",
|
||||
"keyring",
|
||||
"landlock",
|
||||
"libc",
|
||||
"maplit",
|
||||
@@ -1895,7 +1893,6 @@ dependencies = [
|
||||
"openssl-sys",
|
||||
"opentelemetry",
|
||||
"opentelemetry_sdk",
|
||||
"os_info",
|
||||
"predicates",
|
||||
"pretty_assertions",
|
||||
"rand 0.9.2",
|
||||
@@ -1909,7 +1906,6 @@ dependencies = [
|
||||
"serde_yaml",
|
||||
"serial_test",
|
||||
"sha1",
|
||||
"sha2",
|
||||
"shlex",
|
||||
"similar",
|
||||
"tempfile",
|
||||
@@ -2172,19 +2168,30 @@ name = "codex-login"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"codex-app-server-protocol",
|
||||
"codex-client",
|
||||
"codex-core",
|
||||
"codex-config",
|
||||
"codex-keyring-store",
|
||||
"codex-protocol",
|
||||
"codex-terminal-detection",
|
||||
"core_test_support",
|
||||
"keyring",
|
||||
"once_cell",
|
||||
"os_info",
|
||||
"pretty_assertions",
|
||||
"rand 0.9.2",
|
||||
"regex-lite",
|
||||
"reqwest",
|
||||
"schemars 0.8.22",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serial_test",
|
||||
"sha2",
|
||||
"tempfile",
|
||||
"thiserror 2.0.18",
|
||||
"tiny_http",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -2276,6 +2283,7 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"codex-api",
|
||||
"codex-app-server-protocol",
|
||||
"codex-protocol",
|
||||
"codex-utils-absolute-path",
|
||||
"codex-utils-string",
|
||||
|
||||
@@ -191,7 +191,6 @@ use codex_core::ThreadSortKey as CoreThreadSortKey;
|
||||
use codex_core::auth::AuthMode as CoreAuthMode;
|
||||
use codex_core::auth::CLIENT_ID;
|
||||
use codex_core::auth::login_with_api_key;
|
||||
use codex_core::auth::login_with_chatgpt_auth_tokens;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config::ConfigOverrides;
|
||||
use codex_core::config::NetworkProxyAuditMetadata;
|
||||
@@ -242,6 +241,7 @@ use codex_core::windows_sandbox::WindowsSandboxSetupRequest;
|
||||
use codex_feedback::CodexFeedback;
|
||||
use codex_login::ServerOptions as LoginServerOptions;
|
||||
use codex_login::ShutdownHandle;
|
||||
use codex_login::auth::login_with_chatgpt_auth_tokens;
|
||||
use codex_login::run_login_server;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::config_types::CollaborationMode;
|
||||
@@ -1411,7 +1411,7 @@ impl CodexMessageProcessor {
|
||||
let account = match self.auth_manager.auth_cached() {
|
||||
Some(auth) => match auth.auth_mode() {
|
||||
CoreAuthMode::ApiKey => Some(Account::ApiKey {}),
|
||||
CoreAuthMode::Chatgpt => {
|
||||
CoreAuthMode::Chatgpt | CoreAuthMode::ChatgptAuthTokens => {
|
||||
let email = auth.get_account_email();
|
||||
let plan_type = auth.account_plan_type();
|
||||
|
||||
|
||||
@@ -50,10 +50,6 @@ use codex_arg0::Arg0DispatchPaths;
|
||||
use codex_core::AnalyticsEventsClient;
|
||||
use codex_core::AuthManager;
|
||||
use codex_core::ThreadManager;
|
||||
use codex_core::auth::ExternalAuthRefreshContext;
|
||||
use codex_core::auth::ExternalAuthRefreshReason;
|
||||
use codex_core::auth::ExternalAuthRefresher;
|
||||
use codex_core::auth::ExternalAuthTokens;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config_loader::CloudRequirementsLoader;
|
||||
use codex_core::config_loader::LoaderOverrides;
|
||||
@@ -64,6 +60,10 @@ use codex_core::default_client::set_default_client_residency_requirement;
|
||||
use codex_core::default_client::set_default_originator;
|
||||
use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use codex_feedback::CodexFeedback;
|
||||
use codex_login::auth::ExternalAuthRefreshContext;
|
||||
use codex_login::auth::ExternalAuthRefreshReason;
|
||||
use codex_login::auth::ExternalAuthRefresher;
|
||||
use codex_login::auth::ExternalAuthTokens;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::W3cTraceContext;
|
||||
|
||||
@@ -328,7 +328,7 @@ pub async fn run_login_status(cli_config_overrides: CliConfigOverrides) -> ! {
|
||||
std::process::exit(1);
|
||||
}
|
||||
},
|
||||
AuthMode::Chatgpt => {
|
||||
AuthMode::Chatgpt | AuthMode::ChatgptAuthTokens => {
|
||||
eprintln!("Logged in using ChatGPT");
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
@@ -31,17 +31,16 @@ codex-api = { workspace = true }
|
||||
codex-app-server-protocol = { workspace = true }
|
||||
codex-apply-patch = { workspace = true }
|
||||
codex-async-utils = { workspace = true }
|
||||
codex-client = { workspace = true }
|
||||
codex-connectors = { workspace = true }
|
||||
codex-config = { workspace = true }
|
||||
codex-exec-server = { workspace = true }
|
||||
codex-login = { workspace = true }
|
||||
codex-shell-command = { workspace = true }
|
||||
codex-skills = { workspace = true }
|
||||
codex-execpolicy = { workspace = true }
|
||||
codex-file-search = { workspace = true }
|
||||
codex-git = { workspace = true }
|
||||
codex-hooks = { workspace = true }
|
||||
codex-keyring-store = { workspace = true }
|
||||
codex-network-proxy = { workspace = true }
|
||||
codex-otel = { workspace = true }
|
||||
codex-artifacts = { workspace = true }
|
||||
@@ -70,11 +69,9 @@ http = { workspace = true }
|
||||
iana-time-zone = { workspace = true }
|
||||
image = { workspace = true, features = ["jpeg", "png", "webp"] }
|
||||
indexmap = { workspace = true }
|
||||
keyring = { workspace = true, features = ["crypto-rust"] }
|
||||
libc = { workspace = true }
|
||||
notify = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
os_info = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
regex-lite = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["json", "stream"] }
|
||||
@@ -89,7 +86,6 @@ serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
serde_yaml = { workspace = true }
|
||||
sha1 = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
shlex = { workspace = true }
|
||||
similar = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
@@ -120,13 +116,11 @@ wildmatch = { workspace = true }
|
||||
zip = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
keyring = { workspace = true, features = ["linux-native-async-persistent"] }
|
||||
landlock = { workspace = true }
|
||||
seccompiler = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-foundation = "0.9"
|
||||
keyring = { workspace = true, features = ["apple-native"] }
|
||||
|
||||
# Build OpenSSL from source for musl builds.
|
||||
[target.x86_64-unknown-linux-musl.dependencies]
|
||||
@@ -137,16 +131,12 @@ openssl-sys = { workspace = true, features = ["vendored"] }
|
||||
openssl-sys = { workspace = true, features = ["vendored"] }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
keyring = { workspace = true, features = ["windows-native"] }
|
||||
windows-sys = { version = "0.52", features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_System_Com",
|
||||
"Win32_UI_Shell",
|
||||
] }
|
||||
|
||||
[target.'cfg(any(target_os = "freebsd", target_os = "openbsd"))'.dependencies]
|
||||
keyring = { workspace = true, features = ["sync-secret-service"] }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
codex-shell-escalation = { workspace = true }
|
||||
|
||||
|
||||
@@ -1530,7 +1530,7 @@ impl AuthRequestTelemetryContext {
|
||||
Self {
|
||||
auth_mode: auth_mode.map(|mode| match mode {
|
||||
AuthMode::ApiKey => "ApiKey",
|
||||
AuthMode::Chatgpt => "Chatgpt",
|
||||
AuthMode::Chatgpt | AuthMode::ChatgptAuthTokens => "Chatgpt",
|
||||
}),
|
||||
auth_header_attached: api_auth.auth_header_attached(),
|
||||
auth_header_name: api_auth.auth_header_name(),
|
||||
|
||||
2
codex-rs/core/src/default_client_forwarding.rs
Normal file
2
codex-rs/core/src/default_client_forwarding.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
// Re-exported as `crate::default_client` from `lib.rs`.
|
||||
pub use codex_login::default_client::*;
|
||||
@@ -9,6 +9,8 @@ use chrono::Datelike;
|
||||
use chrono::Local;
|
||||
use chrono::Utc;
|
||||
use codex_async_utils::CancelErr;
|
||||
pub use codex_login::auth::RefreshTokenFailedError;
|
||||
pub use codex_login::auth::RefreshTokenFailedReason;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::protocol::CodexErrorInfo;
|
||||
use codex_protocol::protocol::ErrorEvent;
|
||||
@@ -261,30 +263,6 @@ impl std::fmt::Display for ResponseStreamFailed {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Error)]
|
||||
#[error("{message}")]
|
||||
pub struct RefreshTokenFailedError {
|
||||
pub reason: RefreshTokenFailedReason,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl RefreshTokenFailedError {
|
||||
pub fn new(reason: RefreshTokenFailedReason, message: impl Into<String>) -> Self {
|
||||
Self {
|
||||
reason,
|
||||
message: message.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RefreshTokenFailedReason {
|
||||
Expired,
|
||||
Exhausted,
|
||||
Revoked,
|
||||
Other,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UnexpectedResponseError {
|
||||
pub status: StatusCode,
|
||||
|
||||
@@ -10,7 +10,7 @@ pub mod api_bridge;
|
||||
mod apply_patch;
|
||||
mod apps;
|
||||
mod arc_monitor;
|
||||
pub mod auth;
|
||||
pub use codex_login as auth;
|
||||
mod auth_env_telemetry;
|
||||
mod client;
|
||||
mod client_common;
|
||||
@@ -76,7 +76,7 @@ mod shell_detect;
|
||||
mod stream_events_utils;
|
||||
pub mod test_support;
|
||||
mod text_encoding;
|
||||
pub mod token_data;
|
||||
pub use codex_login::token_data;
|
||||
mod truncate;
|
||||
mod unified_exec;
|
||||
pub mod windows_sandbox;
|
||||
@@ -110,7 +110,15 @@ pub type CodexConversation = CodexThread;
|
||||
pub use analytics_client::AnalyticsEventsClient;
|
||||
pub use auth::AuthManager;
|
||||
pub use auth::CodexAuth;
|
||||
pub mod default_client;
|
||||
mod default_client_forwarding;
|
||||
|
||||
/// Default Codex HTTP client headers and reqwest construction.
|
||||
///
|
||||
/// Implemented in [`codex_login::default_client`]; this module re-exports that API for crates
|
||||
/// that import `codex_core::default_client`.
|
||||
pub mod default_client {
|
||||
pub use super::default_client_forwarding::*;
|
||||
}
|
||||
pub mod project_doc;
|
||||
mod rollout;
|
||||
pub(crate) mod safety;
|
||||
|
||||
@@ -4,7 +4,6 @@ use std::time::Duration;
|
||||
|
||||
use codex_protocol::ThreadId;
|
||||
use rand::Rng;
|
||||
use tracing::debug;
|
||||
use tracing::error;
|
||||
|
||||
use crate::auth_env_telemetry::AuthEnvTelemetry;
|
||||
@@ -217,21 +216,6 @@ pub(crate) fn error_or_panic(message: impl std::string::ToString) {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn try_parse_error_message(text: &str) -> String {
|
||||
debug!("Parsing server error response: {}", text);
|
||||
let json = serde_json::from_str::<serde_json::Value>(text).unwrap_or_default();
|
||||
if let Some(error) = json.get("error")
|
||||
&& let Some(message) = error.get("message")
|
||||
&& let Some(message_str) = message.as_str()
|
||||
{
|
||||
return message_str.to_string();
|
||||
}
|
||||
if text.is_empty() {
|
||||
return "Unknown error".to_string();
|
||||
}
|
||||
text.to_string()
|
||||
}
|
||||
|
||||
pub fn resolve_path(base: &Path, path: &PathBuf) -> PathBuf {
|
||||
if path.is_absolute() {
|
||||
path.clone()
|
||||
|
||||
@@ -12,30 +12,6 @@ use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::registry::LookupSpan;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
|
||||
#[test]
|
||||
fn test_try_parse_error_message() {
|
||||
let text = r#"{
|
||||
"error": {
|
||||
"message": "Your refresh token has already been used to generate a new access token. Please try signing in again.",
|
||||
"type": "invalid_request_error",
|
||||
"param": null,
|
||||
"code": "refresh_token_reused"
|
||||
}
|
||||
}"#;
|
||||
let message = try_parse_error_message(text);
|
||||
assert_eq!(
|
||||
message,
|
||||
"Your refresh token has already been used to generate a new access token. Please try signing in again."
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_try_parse_error_message_no_error() {
|
||||
let text = r#"{"message": "test"}"#;
|
||||
let message = try_parse_error_message(text);
|
||||
assert_eq!(message, r#"{"message": "test"}"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feedback_tags_macro_compiles() {
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -789,8 +789,10 @@ fn minimal_jwt() -> String {
|
||||
}
|
||||
|
||||
fn build_tokens(access_token: &str, refresh_token: &str) -> TokenData {
|
||||
let mut id_token = IdTokenInfo::default();
|
||||
id_token.raw_jwt = minimal_jwt();
|
||||
let id_token = IdTokenInfo {
|
||||
raw_jwt: minimal_jwt(),
|
||||
..Default::default()
|
||||
};
|
||||
TokenData {
|
||||
id_token,
|
||||
access_token: access_token.to_string(),
|
||||
|
||||
@@ -45,6 +45,7 @@ use codex_cloud_requirements::cloud_requirements_loader;
|
||||
use codex_core::AuthManager;
|
||||
use codex_core::LMSTUDIO_OSS_PROVIDER_ID;
|
||||
use codex_core::OLLAMA_OSS_PROVIDER_ID;
|
||||
use codex_core::auth::AuthConfig;
|
||||
use codex_core::auth::enforce_login_restrictions;
|
||||
use codex_core::check_execpolicy_for_warnings;
|
||||
use codex_core::config::Config;
|
||||
@@ -381,7 +382,12 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
|
||||
|
||||
set_default_client_residency_requirement(config.enforce_residency.value());
|
||||
|
||||
if let Err(err) = enforce_login_restrictions(&config) {
|
||||
if let Err(err) = enforce_login_restrictions(&AuthConfig {
|
||||
codex_home: config.codex_home.clone(),
|
||||
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(),
|
||||
}) {
|
||||
eprintln!("{err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
@@ -8,16 +8,24 @@ license.workspace = true
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
codex-client = { workspace = true }
|
||||
codex-core = { workspace = true }
|
||||
codex-app-server-protocol = { workspace = true }
|
||||
codex-client = { workspace = true }
|
||||
codex-config = { workspace = true }
|
||||
codex-keyring-store = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
codex-terminal-detection = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
os_info = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["json", "blocking"] }
|
||||
schemars = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tiny_http = { workspace = true }
|
||||
tokio = { workspace = true, features = [
|
||||
"io-std",
|
||||
@@ -34,6 +42,9 @@ webbrowser = { workspace = true }
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
core_test_support = { workspace = true }
|
||||
keyring = { workspace = true }
|
||||
pretty_assertions = { workspace = true }
|
||||
regex-lite = { workspace = true }
|
||||
serial_test = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
wiremock = { workspace = true }
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use super::*;
|
||||
use crate::auth::storage::FileAuthStorage;
|
||||
use crate::auth::storage::get_auth_file;
|
||||
use crate::config::Config;
|
||||
use crate::config::ConfigBuilder;
|
||||
use crate::token_data::IdTokenInfo;
|
||||
use crate::token_data::KnownPlan as InternalKnownPlan;
|
||||
use crate::token_data::PlanType as InternalPlanType;
|
||||
@@ -103,7 +101,7 @@ async fn pro_account_with_no_api_key_uses_chatgpt_auth() {
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(None, auth.api_key());
|
||||
assert_eq!(AuthMode::Chatgpt, auth.auth_mode());
|
||||
assert_eq!(crate::AuthMode::Chatgpt, auth.auth_mode());
|
||||
assert_eq!(auth.get_chatgpt_user_id().as_deref(), Some("user-12345"));
|
||||
|
||||
let auth_dot_json = auth
|
||||
@@ -149,7 +147,7 @@ async fn loads_api_key_from_auth_json() {
|
||||
let auth = super::load_auth(dir.path(), false, AuthCredentialsStoreMode::File)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(auth.auth_mode(), AuthMode::ApiKey);
|
||||
assert_eq!(auth.auth_mode(), crate::AuthMode::ApiKey);
|
||||
assert_eq!(auth.api_key(), Some("sk-test-key"));
|
||||
|
||||
assert!(auth.get_token_data().is_err());
|
||||
@@ -260,15 +258,13 @@ async fn build_config(
|
||||
codex_home: &Path,
|
||||
forced_login_method: Option<ForcedLoginMethod>,
|
||||
forced_chatgpt_workspace_id: Option<String>,
|
||||
) -> Config {
|
||||
let mut config = ConfigBuilder::default()
|
||||
.codex_home(codex_home.to_path_buf())
|
||||
.build()
|
||||
.await
|
||||
.expect("config should load");
|
||||
config.forced_login_method = forced_login_method;
|
||||
config.forced_chatgpt_workspace_id = forced_chatgpt_workspace_id;
|
||||
config
|
||||
) -> AuthConfig {
|
||||
AuthConfig {
|
||||
codex_home: codex_home.to_path_buf(),
|
||||
auth_credentials_store_mode: AuthCredentialsStoreMode::File,
|
||||
forced_login_method,
|
||||
forced_chatgpt_workspace_id,
|
||||
}
|
||||
}
|
||||
|
||||
/// Use sparingly.
|
||||
@@ -1,5 +1,9 @@
|
||||
use crate::config_loader::ResidencyRequirement;
|
||||
use crate::spawn::CODEX_SANDBOX_ENV_VAR;
|
||||
//! Default Codex HTTP client: shared `User-Agent`, `originator`, optional residency header, and
|
||||
//! reqwest/`CodexHttpClient` construction.
|
||||
//!
|
||||
//! Use [`crate::default_client`] or [`codex_login::default_client`] from other crates in this
|
||||
//! workspace.
|
||||
|
||||
use codex_client::BuildCustomCaTransportError;
|
||||
use codex_client::CodexHttpClient;
|
||||
pub use codex_client::CodexRequestBuilder;
|
||||
@@ -31,6 +35,8 @@ pub const DEFAULT_ORIGINATOR: &str = "codex_cli_rs";
|
||||
pub const CODEX_INTERNAL_ORIGINATOR_OVERRIDE_ENV_VAR: &str = "CODEX_INTERNAL_ORIGINATOR_OVERRIDE";
|
||||
pub const RESIDENCY_HEADER_NAME: &str = "x-openai-internal-codex-residency";
|
||||
|
||||
pub use codex_config::ResidencyRequirement;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Originator {
|
||||
pub value: String,
|
||||
@@ -232,7 +238,7 @@ pub fn default_headers() -> HeaderMap {
|
||||
}
|
||||
|
||||
fn is_sandboxed() -> bool {
|
||||
std::env::var(CODEX_SANDBOX_ENV_VAR).as_deref() == Ok("seatbelt")
|
||||
std::env::var("CODEX_SANDBOX").as_deref() == Ok("seatbelt")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -1,3 +1,4 @@
|
||||
use super::sanitize_user_agent;
|
||||
use super::*;
|
||||
use core_test_support::skip_if_no_network;
|
||||
use pretty_assertions::assert_eq;
|
||||
25
codex-rs/login/src/auth/error.rs
Normal file
25
codex-rs/login/src/auth/error.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Error)]
|
||||
#[error("{message}")]
|
||||
pub struct RefreshTokenFailedError {
|
||||
pub reason: RefreshTokenFailedReason,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl RefreshTokenFailedError {
|
||||
pub fn new(reason: RefreshTokenFailedReason, message: impl Into<String>) -> Self {
|
||||
Self {
|
||||
reason,
|
||||
message: message.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RefreshTokenFailedReason {
|
||||
Expired,
|
||||
Exhausted,
|
||||
Revoked,
|
||||
Other,
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
mod storage;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use chrono::Utc;
|
||||
use reqwest::StatusCode;
|
||||
@@ -16,46 +14,25 @@ use std::sync::Mutex;
|
||||
use std::sync::RwLock;
|
||||
|
||||
use codex_app_server_protocol::AuthMode as ApiAuthMode;
|
||||
use codex_otel::TelemetryAuthMode;
|
||||
use codex_protocol::config_types::ForcedLoginMethod;
|
||||
|
||||
use crate::auth::error::RefreshTokenFailedError;
|
||||
use crate::auth::error::RefreshTokenFailedReason;
|
||||
pub use crate::auth::storage::AuthCredentialsStoreMode;
|
||||
pub use crate::auth::storage::AuthDotJson;
|
||||
use crate::auth::storage::AuthStorageBackend;
|
||||
use crate::auth::storage::create_auth_storage;
|
||||
use crate::config::Config;
|
||||
use crate::error::RefreshTokenFailedError;
|
||||
use crate::error::RefreshTokenFailedReason;
|
||||
use crate::auth::util::try_parse_error_message;
|
||||
use crate::default_client::create_client;
|
||||
use crate::token_data::KnownPlan as InternalKnownPlan;
|
||||
use crate::token_data::PlanType as InternalPlanType;
|
||||
use crate::token_data::TokenData;
|
||||
use crate::token_data::parse_chatgpt_jwt_claims;
|
||||
use crate::util::try_parse_error_message;
|
||||
use codex_client::CodexHttpClient;
|
||||
use codex_protocol::account::PlanType as AccountPlanType;
|
||||
use serde_json::Value;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Account type for the current user.
|
||||
///
|
||||
/// This is used internally to determine the base URL for generating responses,
|
||||
/// and to gate ChatGPT-only behaviors like rate limits and available models (as
|
||||
/// opposed to API key-based auth).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum AuthMode {
|
||||
ApiKey,
|
||||
Chatgpt,
|
||||
}
|
||||
|
||||
impl From<AuthMode> for TelemetryAuthMode {
|
||||
fn from(mode: AuthMode) -> Self {
|
||||
match mode {
|
||||
AuthMode::ApiKey => TelemetryAuthMode::ApiKey,
|
||||
AuthMode::Chatgpt => TelemetryAuthMode::Chatgpt,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Authentication mechanism used by the current user.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CodexAuth {
|
||||
@@ -161,14 +138,14 @@ impl CodexAuth {
|
||||
codex_home: &Path,
|
||||
auth_dot_json: AuthDotJson,
|
||||
auth_credentials_store_mode: AuthCredentialsStoreMode,
|
||||
client: CodexHttpClient,
|
||||
) -> std::io::Result<Self> {
|
||||
let auth_mode = auth_dot_json.resolved_mode();
|
||||
let client = create_client();
|
||||
if auth_mode == ApiAuthMode::ApiKey {
|
||||
let Some(api_key) = auth_dot_json.openai_api_key.as_deref() else {
|
||||
return Err(std::io::Error::other("API key auth is missing a key."));
|
||||
};
|
||||
return Ok(CodexAuth::from_api_key_with_client(api_key, client));
|
||||
return Ok(Self::from_api_key(api_key));
|
||||
}
|
||||
|
||||
let storage_mode = auth_dot_json.storage_mode(auth_credentials_store_mode);
|
||||
@@ -189,7 +166,6 @@ impl CodexAuth {
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads the available auth information from auth storage.
|
||||
pub fn from_auth_storage(
|
||||
codex_home: &Path,
|
||||
auth_credentials_store_mode: AuthCredentialsStoreMode,
|
||||
@@ -201,10 +177,10 @@ impl CodexAuth {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn auth_mode(&self) -> AuthMode {
|
||||
pub fn auth_mode(&self) -> crate::AuthMode {
|
||||
match self {
|
||||
Self::ApiKey(_) => AuthMode::ApiKey,
|
||||
Self::Chatgpt(_) | Self::ChatgptAuthTokens(_) => AuthMode::Chatgpt,
|
||||
Self::ApiKey(_) => crate::AuthMode::ApiKey,
|
||||
Self::Chatgpt(_) | Self::ChatgptAuthTokens(_) => crate::AuthMode::Chatgpt,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,11 +193,11 @@ impl CodexAuth {
|
||||
}
|
||||
|
||||
pub fn is_api_key_auth(&self) -> bool {
|
||||
self.auth_mode() == AuthMode::ApiKey
|
||||
self.auth_mode() == crate::AuthMode::ApiKey
|
||||
}
|
||||
|
||||
pub fn is_chatgpt_auth(&self) -> bool {
|
||||
self.auth_mode() == AuthMode::Chatgpt
|
||||
self.auth_mode() == crate::AuthMode::Chatgpt
|
||||
}
|
||||
|
||||
pub fn is_external_chatgpt_tokens(&self) -> bool {
|
||||
@@ -335,7 +311,7 @@ impl CodexAuth {
|
||||
last_refresh: Some(Utc::now()),
|
||||
};
|
||||
|
||||
let client = crate::default_client::create_client();
|
||||
let client = create_client();
|
||||
let state = ChatgptAuthState {
|
||||
auth_dot_json: Arc::new(Mutex::new(Some(auth_dot_json))),
|
||||
client,
|
||||
@@ -344,15 +320,11 @@ impl CodexAuth {
|
||||
Self::Chatgpt(ChatgptAuth { state, storage })
|
||||
}
|
||||
|
||||
fn from_api_key_with_client(api_key: &str, _client: CodexHttpClient) -> Self {
|
||||
pub fn from_api_key(api_key: &str) -> Self {
|
||||
Self::ApiKey(ApiKeyAuth {
|
||||
api_key: api_key.to_owned(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_api_key(api_key: &str) -> Self {
|
||||
Self::from_api_key_with_client(api_key, crate::default_client::create_client())
|
||||
}
|
||||
}
|
||||
|
||||
impl ChatgptAuth {
|
||||
@@ -458,11 +430,19 @@ pub fn load_auth_dot_json(
|
||||
storage.load()
|
||||
}
|
||||
|
||||
pub fn enforce_login_restrictions(config: &Config) -> std::io::Result<()> {
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AuthConfig {
|
||||
pub codex_home: PathBuf,
|
||||
pub auth_credentials_store_mode: AuthCredentialsStoreMode,
|
||||
pub forced_login_method: Option<ForcedLoginMethod>,
|
||||
pub forced_chatgpt_workspace_id: Option<String>,
|
||||
}
|
||||
|
||||
pub fn enforce_login_restrictions(config: &AuthConfig) -> std::io::Result<()> {
|
||||
let Some(auth) = load_auth(
|
||||
&config.codex_home,
|
||||
/*enable_codex_api_key_env*/ true,
|
||||
config.cli_auth_credentials_store_mode,
|
||||
config.auth_credentials_store_mode,
|
||||
)?
|
||||
else {
|
||||
return Ok(());
|
||||
@@ -470,13 +450,15 @@ pub fn enforce_login_restrictions(config: &Config) -> std::io::Result<()> {
|
||||
|
||||
if let Some(required_method) = config.forced_login_method {
|
||||
let method_violation = match (required_method, auth.auth_mode()) {
|
||||
(ForcedLoginMethod::Api, AuthMode::ApiKey) => None,
|
||||
(ForcedLoginMethod::Chatgpt, AuthMode::Chatgpt) => None,
|
||||
(ForcedLoginMethod::Api, AuthMode::Chatgpt) => Some(
|
||||
(ForcedLoginMethod::Api, crate::AuthMode::ApiKey) => None,
|
||||
(ForcedLoginMethod::Chatgpt, crate::AuthMode::Chatgpt)
|
||||
| (ForcedLoginMethod::Chatgpt, crate::AuthMode::ChatgptAuthTokens) => None,
|
||||
(ForcedLoginMethod::Api, crate::AuthMode::Chatgpt)
|
||||
| (ForcedLoginMethod::Api, crate::AuthMode::ChatgptAuthTokens) => Some(
|
||||
"API key login is required, but ChatGPT is currently being used. Logging out."
|
||||
.to_string(),
|
||||
),
|
||||
(ForcedLoginMethod::Chatgpt, AuthMode::ApiKey) => Some(
|
||||
(ForcedLoginMethod::Chatgpt, crate::AuthMode::ApiKey) => Some(
|
||||
"ChatGPT login is required, but an API key is currently being used. Logging out."
|
||||
.to_string(),
|
||||
),
|
||||
@@ -486,7 +468,7 @@ pub fn enforce_login_restrictions(config: &Config) -> std::io::Result<()> {
|
||||
return logout_with_message(
|
||||
&config.codex_home,
|
||||
message,
|
||||
config.cli_auth_credentials_store_mode,
|
||||
config.auth_credentials_store_mode,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -504,7 +486,7 @@ pub fn enforce_login_restrictions(config: &Config) -> std::io::Result<()> {
|
||||
format!(
|
||||
"Failed to load ChatGPT credentials while enforcing workspace restrictions: {err}. Logging out."
|
||||
),
|
||||
config.cli_auth_credentials_store_mode,
|
||||
config.auth_credentials_store_mode,
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -523,7 +505,7 @@ pub fn enforce_login_restrictions(config: &Config) -> std::io::Result<()> {
|
||||
return logout_with_message(
|
||||
&config.codex_home,
|
||||
message,
|
||||
config.cli_auth_credentials_store_mode,
|
||||
config.auth_credentials_store_mode,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -564,17 +546,12 @@ fn load_auth(
|
||||
auth_credentials_store_mode: AuthCredentialsStoreMode,
|
||||
) -> std::io::Result<Option<CodexAuth>> {
|
||||
let build_auth = |auth_dot_json: AuthDotJson, storage_mode| {
|
||||
let client = crate::default_client::create_client();
|
||||
CodexAuth::from_auth_dot_json(codex_home, auth_dot_json, storage_mode, client)
|
||||
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() {
|
||||
let client = crate::default_client::create_client();
|
||||
return Ok(Some(CodexAuth::from_api_key_with_client(
|
||||
api_key.as_str(),
|
||||
client,
|
||||
)));
|
||||
return Ok(Some(CodexAuth::from_api_key(api_key.as_str())));
|
||||
}
|
||||
|
||||
// External ChatGPT auth tokens live in the in-memory (ephemeral) store. Always check this
|
||||
@@ -1077,7 +1054,7 @@ impl AuthManager {
|
||||
}
|
||||
|
||||
/// Create an AuthManager with a specific CodexAuth, for testing only.
|
||||
pub(crate) fn from_auth_for_testing(auth: CodexAuth) -> Arc<Self> {
|
||||
pub fn from_auth_for_testing(auth: CodexAuth) -> Arc<Self> {
|
||||
let cached = CachedAuth {
|
||||
auth: Some(auth),
|
||||
external_refresher: None,
|
||||
@@ -1093,10 +1070,7 @@ impl AuthManager {
|
||||
}
|
||||
|
||||
/// Create an AuthManager with a specific CodexAuth and codex home, for testing only.
|
||||
pub(crate) fn from_auth_for_testing_with_home(
|
||||
auth: CodexAuth,
|
||||
codex_home: PathBuf,
|
||||
) -> Arc<Self> {
|
||||
pub fn from_auth_for_testing_with_home(auth: CodexAuth, codex_home: PathBuf) -> Arc<Self> {
|
||||
let cached = CachedAuth {
|
||||
auth: Some(auth),
|
||||
external_refresher: None,
|
||||
@@ -1342,7 +1316,7 @@ impl AuthManager {
|
||||
self.auth_cached().as_ref().map(CodexAuth::api_auth_mode)
|
||||
}
|
||||
|
||||
pub fn auth_mode(&self) -> Option<AuthMode> {
|
||||
pub fn auth_mode(&self) -> Option<crate::AuthMode> {
|
||||
self.auth_cached().as_ref().map(CodexAuth::auth_mode)
|
||||
}
|
||||
|
||||
10
codex-rs/login/src/auth/mod.rs
Normal file
10
codex-rs/login/src/auth/mod.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
pub mod default_client;
|
||||
pub mod error;
|
||||
mod storage;
|
||||
mod util;
|
||||
|
||||
mod manager;
|
||||
|
||||
pub use error::RefreshTokenFailedError;
|
||||
pub use error::RefreshTokenFailedReason;
|
||||
pub use manager::*;
|
||||
45
codex-rs/login/src/auth/util.rs
Normal file
45
codex-rs/login/src/auth/util.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use tracing::debug;
|
||||
|
||||
pub(crate) fn try_parse_error_message(text: &str) -> String {
|
||||
debug!("Parsing server error response: {}", text);
|
||||
let json = serde_json::from_str::<serde_json::Value>(text).unwrap_or_default();
|
||||
if let Some(error) = json.get("error")
|
||||
&& let Some(message) = error.get("message")
|
||||
&& let Some(message_str) = message.as_str()
|
||||
{
|
||||
return message_str.to_string();
|
||||
}
|
||||
if text.is_empty() {
|
||||
return "Unknown error".to_string();
|
||||
}
|
||||
text.to_string()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::try_parse_error_message;
|
||||
|
||||
#[test]
|
||||
fn try_parse_error_message_extracts_openai_error_message() {
|
||||
let text = r#"{
|
||||
"error": {
|
||||
"message": "Your refresh token has already been used to generate a new access token. Please try signing in again.",
|
||||
"type": "invalid_request_error",
|
||||
"param": null,
|
||||
"code": "refresh_token_reused"
|
||||
}
|
||||
}"#;
|
||||
let message = try_parse_error_message(text);
|
||||
assert_eq!(
|
||||
message,
|
||||
"Your refresh token has already been used to generate a new access token. Please try signing in again."
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_parse_error_message_falls_back_to_raw_text() {
|
||||
let text = r#"{"message": "test"}"#;
|
||||
let message = try_parse_error_message(text);
|
||||
assert_eq!(message, r#"{"message": "test"}"#);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
pub mod auth;
|
||||
pub mod token_data;
|
||||
|
||||
mod device_code_auth;
|
||||
mod pkce;
|
||||
mod server;
|
||||
@@ -12,15 +15,23 @@ pub use server::ServerOptions;
|
||||
pub use server::ShutdownHandle;
|
||||
pub use server::run_login_server;
|
||||
|
||||
// Re-export commonly used auth types and helpers from codex-core for compatibility
|
||||
pub use auth::AuthConfig;
|
||||
pub use auth::AuthCredentialsStoreMode;
|
||||
pub use auth::AuthDotJson;
|
||||
pub use auth::AuthManager;
|
||||
pub use auth::CLIENT_ID;
|
||||
pub use auth::CODEX_API_KEY_ENV_VAR;
|
||||
pub use auth::CodexAuth;
|
||||
pub use auth::OPENAI_API_KEY_ENV_VAR;
|
||||
pub use auth::REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR;
|
||||
pub use auth::RefreshTokenError;
|
||||
pub use auth::UnauthorizedRecovery;
|
||||
pub use auth::default_client;
|
||||
pub use auth::enforce_login_restrictions;
|
||||
pub use auth::load_auth_dot_json;
|
||||
pub use auth::login_with_api_key;
|
||||
pub use auth::logout;
|
||||
pub use auth::read_openai_api_key_from_env;
|
||||
pub use auth::save_auth;
|
||||
pub use codex_app_server_protocol::AuthMode;
|
||||
pub use codex_core::AuthManager;
|
||||
pub use codex_core::CodexAuth;
|
||||
pub use codex_core::auth::AuthDotJson;
|
||||
pub use codex_core::auth::CLIENT_ID;
|
||||
pub use codex_core::auth::CODEX_API_KEY_ENV_VAR;
|
||||
pub use codex_core::auth::OPENAI_API_KEY_ENV_VAR;
|
||||
pub use codex_core::auth::login_with_api_key;
|
||||
pub use codex_core::auth::logout;
|
||||
pub use codex_core::auth::save_auth;
|
||||
pub use codex_core::token_data::TokenData;
|
||||
pub use token_data::TokenData;
|
||||
|
||||
@@ -23,18 +23,18 @@ use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::auth::AuthCredentialsStoreMode;
|
||||
use crate::auth::AuthDotJson;
|
||||
use crate::auth::save_auth;
|
||||
use crate::default_client::originator;
|
||||
use crate::pkce::PkceCodes;
|
||||
use crate::pkce::generate_pkce;
|
||||
use crate::token_data::TokenData;
|
||||
use crate::token_data::parse_chatgpt_jwt_claims;
|
||||
use base64::Engine;
|
||||
use chrono::Utc;
|
||||
use codex_app_server_protocol::AuthMode;
|
||||
use codex_client::build_reqwest_client_with_custom_ca;
|
||||
use codex_core::auth::AuthCredentialsStoreMode;
|
||||
use codex_core::auth::AuthDotJson;
|
||||
use codex_core::auth::save_auth;
|
||||
use codex_core::default_client::originator;
|
||||
use codex_core::token_data::TokenData;
|
||||
use codex_core::token_data::parse_chatgpt_jwt_claims;
|
||||
use rand::RngCore;
|
||||
use serde_json::Value as JsonValue;
|
||||
use tiny_http::Header;
|
||||
@@ -484,10 +484,7 @@ fn build_authorize_url(
|
||||
("id_token_add_organizations".to_string(), "true".to_string()),
|
||||
("codex_cli_simplified_flow".to_string(), "true".to_string()),
|
||||
("state".to_string(), state.to_string()),
|
||||
(
|
||||
"originator".to_string(),
|
||||
originator().value.as_str().to_string(),
|
||||
),
|
||||
("originator".to_string(), originator().value),
|
||||
];
|
||||
if let Some(workspace_id) = forced_chatgpt_workspace_id {
|
||||
query.push(("allowed_workspace_id".to_string(), workspace_id.to_string()));
|
||||
|
||||
@@ -27,7 +27,7 @@ pub struct IdTokenInfo {
|
||||
/// The ChatGPT subscription plan type
|
||||
/// (e.g., "free", "plus", "pro", "business", "enterprise", "edu").
|
||||
/// (Note: values may vary by backend.)
|
||||
pub(crate) chatgpt_plan_type: Option<PlanType>,
|
||||
pub chatgpt_plan_type: Option<PlanType>,
|
||||
/// ChatGPT user identifier associated with the token, if present.
|
||||
pub chatgpt_user_id: Option<String>,
|
||||
/// Organization/workspace identifier associated with the token, if present.
|
||||
@@ -55,13 +55,13 @@ impl IdTokenInfo {
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub(crate) enum PlanType {
|
||||
pub enum PlanType {
|
||||
Known(KnownPlan),
|
||||
Unknown(String),
|
||||
}
|
||||
|
||||
impl PlanType {
|
||||
pub(crate) fn from_raw_value(raw: &str) -> Self {
|
||||
pub fn from_raw_value(raw: &str) -> Self {
|
||||
match raw.to_ascii_lowercase().as_str() {
|
||||
"free" => Self::Known(KnownPlan::Free),
|
||||
"go" => Self::Known(KnownPlan::Go),
|
||||
@@ -78,7 +78,7 @@ impl PlanType {
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub(crate) enum KnownPlan {
|
||||
pub enum KnownPlan {
|
||||
Free,
|
||||
Go,
|
||||
Plus,
|
||||
@@ -3,9 +3,9 @@
|
||||
use anyhow::Context;
|
||||
use base64::Engine;
|
||||
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
|
||||
use codex_core::auth::AuthCredentialsStoreMode;
|
||||
use codex_core::auth::load_auth_dot_json;
|
||||
use codex_login::ServerOptions;
|
||||
use codex_login::auth::AuthCredentialsStoreMode;
|
||||
use codex_login::auth::load_auth_dot_json;
|
||||
use codex_login::run_device_code_login;
|
||||
use serde_json::json;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -7,8 +7,8 @@ use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use base64::Engine;
|
||||
use codex_core::auth::AuthCredentialsStoreMode;
|
||||
use codex_login::ServerOptions;
|
||||
use codex_login::auth::AuthCredentialsStoreMode;
|
||||
use codex_login::run_login_server;
|
||||
use core_test_support::skip_if_no_network;
|
||||
use tempfile::tempdir;
|
||||
|
||||
@@ -24,6 +24,7 @@ chrono = { workspace = true }
|
||||
codex-utils-absolute-path = { workspace = true }
|
||||
codex-utils-string = { workspace = true }
|
||||
codex-api = { workspace = true }
|
||||
codex-app-server-protocol = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
eventsource-stream = { workspace = true }
|
||||
gethostname = { workspace = true }
|
||||
|
||||
@@ -36,13 +36,23 @@ pub enum ToolDecisionSource {
|
||||
User,
|
||||
}
|
||||
|
||||
/// Maps to core AuthMode to avoid a circular dependency on codex-core.
|
||||
/// Maps to API/auth `AuthMode` to avoid a circular dependency on codex-core.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Display)]
|
||||
pub enum TelemetryAuthMode {
|
||||
ApiKey,
|
||||
Chatgpt,
|
||||
}
|
||||
|
||||
impl From<codex_app_server_protocol::AuthMode> for TelemetryAuthMode {
|
||||
fn from(mode: codex_app_server_protocol::AuthMode) -> Self {
|
||||
match mode {
|
||||
codex_app_server_protocol::AuthMode::ApiKey => Self::ApiKey,
|
||||
codex_app_server_protocol::AuthMode::Chatgpt
|
||||
| codex_app_server_protocol::AuthMode::ChatgptAuthTokens => Self::Chatgpt,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Start a metrics timer using the globally installed metrics client.
|
||||
pub fn start_global_timer(name: &str, tags: &[(&str, &str)]) -> MetricsResult<Timer> {
|
||||
let Some(metrics) = crate::metrics::global() else {
|
||||
|
||||
@@ -13,6 +13,7 @@ use codex_core::CodexAuth;
|
||||
use codex_core::INTERACTIVE_SESSION_SOURCES;
|
||||
use codex_core::RolloutRecorder;
|
||||
use codex_core::ThreadSortKey;
|
||||
use codex_core::auth::AuthConfig;
|
||||
use codex_core::auth::AuthMode;
|
||||
use codex_core::auth::enforce_login_restrictions;
|
||||
use codex_core::check_execpolicy_for_warnings;
|
||||
@@ -454,7 +455,12 @@ pub async fn run_main(
|
||||
}
|
||||
|
||||
#[allow(clippy::print_stderr)]
|
||||
if let Err(err) = enforce_login_restrictions(&config) {
|
||||
if let Err(err) = enforce_login_restrictions(&AuthConfig {
|
||||
codex_home: config.codex_home.clone(),
|
||||
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(),
|
||||
}) {
|
||||
eprintln!("{err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ pub(crate) fn compose_account_display(
|
||||
|
||||
match auth.auth_mode() {
|
||||
CoreAuthMode::ApiKey => Some(StatusAccountDisplay::ApiKey),
|
||||
CoreAuthMode::Chatgpt => {
|
||||
CoreAuthMode::Chatgpt | CoreAuthMode::ChatgptAuthTokens => {
|
||||
let email = auth.get_account_email();
|
||||
let plan = plan
|
||||
.map(|plan_type| title_case(format!("{plan_type:?}").as_str()))
|
||||
|
||||
@@ -21,6 +21,7 @@ use codex_app_server_protocol::ThreadListParams;
|
||||
use codex_app_server_protocol::ThreadSortKey as AppServerThreadSortKey;
|
||||
use codex_app_server_protocol::ThreadSourceKind;
|
||||
use codex_cloud_requirements::cloud_requirements_loader_for_storage;
|
||||
use codex_core::auth::AuthConfig;
|
||||
use codex_core::auth::enforce_login_restrictions;
|
||||
use codex_core::check_execpolicy_for_warnings;
|
||||
use codex_core::config::Config;
|
||||
@@ -777,7 +778,12 @@ pub async fn run_main(
|
||||
|
||||
if matches!(app_server_target, AppServerTarget::Embedded) {
|
||||
#[allow(clippy::print_stderr)]
|
||||
if let Err(err) = enforce_login_restrictions(&config) {
|
||||
if let Err(err) = enforce_login_restrictions(&AuthConfig {
|
||||
codex_home: config.codex_home.clone(),
|
||||
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(),
|
||||
}) {
|
||||
eprintln!("{err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
@@ -70,9 +70,9 @@ mod tests {
|
||||
use chrono::Utc;
|
||||
use codex_app_server_protocol::AuthMode;
|
||||
use codex_core::auth::AuthDotJson;
|
||||
use codex_core::auth::login_with_chatgpt_auth_tokens;
|
||||
use codex_core::auth::save_auth;
|
||||
use codex_core::token_data::TokenData;
|
||||
use codex_login::auth::login_with_chatgpt_auth_tokens;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde::Serialize;
|
||||
use serde_json::json;
|
||||
|
||||
Reference in New Issue
Block a user