mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Refactor external auth to use a single trait (#16356)
## Summary - Replace the separate external auth enum and refresher trait with a single `ExternalAuth` trait in login auth flow - Move bearer token auth behind `BearerTokenRefresher` and update `AuthManager` and app-server wiring to use the generic external auth API
This commit is contained in:
committed by
GitHub
Unverified
parent
0fe873ad5f
commit
103acdfb06
@@ -17,6 +17,7 @@ In the codex-rs folder where the rust code lives:
|
||||
- Do not add these comments for string or char literals unless the comment adds real clarity; those literals are intentionally exempt from the lint.
|
||||
- If you add one of these comments, the parameter name must exactly match the callee signature.
|
||||
- When possible, make `match` statements exhaustive and avoid wildcard arms.
|
||||
- Newly added traits should include doc comments that explain their role and how implementations are expected to use them.
|
||||
- When writing tests, prefer comparing the equality of entire objects over fields one by one.
|
||||
- When making a change that adds or changes an API, ensure that the documentation in the `docs/` folder is up to date if applicable.
|
||||
- If you change `ConfigToml` or nested config types, run `just write-config-schema` to update `codex-rs/core/config.schema.json`.
|
||||
|
||||
@@ -71,9 +71,10 @@ use codex_core::models_manager::collaboration_mode_presets::CollaborationModesCo
|
||||
use codex_exec_server::EnvironmentManager;
|
||||
use codex_features::Feature;
|
||||
use codex_feedback::CodexFeedback;
|
||||
use codex_login::AuthMode as LoginAuthMode;
|
||||
use codex_login::auth::ExternalAuth;
|
||||
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;
|
||||
@@ -103,7 +104,11 @@ impl ExternalAuthRefreshBridge {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ExternalAuthRefresher for ExternalAuthRefreshBridge {
|
||||
impl ExternalAuth for ExternalAuthRefreshBridge {
|
||||
fn auth_mode(&self) -> LoginAuthMode {
|
||||
LoginAuthMode::Chatgpt
|
||||
}
|
||||
|
||||
async fn refresh(
|
||||
&self,
|
||||
context: ExternalAuthRefreshContext,
|
||||
@@ -211,7 +216,7 @@ impl MessageProcessor {
|
||||
enable_codex_api_key_env,
|
||||
rpc_transport,
|
||||
} = args;
|
||||
let auth_manager = AuthManager::shared_with_external_chatgpt_auth_refresher(
|
||||
let auth_manager = AuthManager::shared_with_external_auth(
|
||||
config.codex_home.clone(),
|
||||
enable_codex_api_key_env,
|
||||
config.cli_auth_credentials_store_mode,
|
||||
@@ -290,7 +295,7 @@ impl MessageProcessor {
|
||||
}
|
||||
|
||||
pub(crate) fn clear_runtime_references(&self) {
|
||||
self.auth_manager.clear_external_chatgpt_auth_refresher();
|
||||
self.auth_manager.clear_external_auth();
|
||||
}
|
||||
|
||||
pub(crate) async fn process_request(
|
||||
|
||||
@@ -283,6 +283,8 @@ async fn external_bearer_only_auth_manager_uses_cached_provider_token() {
|
||||
|
||||
assert_eq!(first.as_deref(), Some("provider-token"));
|
||||
assert_eq!(second.as_deref(), Some("provider-token"));
|
||||
assert_eq!(manager.auth_mode(), Some(crate::AuthMode::ApiKey));
|
||||
assert_eq!(manager.get_api_auth_mode(), Some(ApiAuthMode::ApiKey));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -448,42 +450,8 @@ struct AuthFileParams {
|
||||
}
|
||||
|
||||
fn write_auth_file(params: AuthFileParams, codex_home: &Path) -> std::io::Result<String> {
|
||||
let fake_jwt = fake_jwt_for_auth_file_params(¶ms)?;
|
||||
let auth_file = get_auth_file(codex_home);
|
||||
// Create a minimal valid JWT for the id_token field.
|
||||
#[derive(Serialize)]
|
||||
struct Header {
|
||||
alg: &'static str,
|
||||
typ: &'static str,
|
||||
}
|
||||
let header = Header {
|
||||
alg: "none",
|
||||
typ: "JWT",
|
||||
};
|
||||
let mut auth_payload = serde_json::json!({
|
||||
"chatgpt_user_id": "user-12345",
|
||||
"user_id": "user-12345",
|
||||
});
|
||||
|
||||
if let Some(chatgpt_plan_type) = params.chatgpt_plan_type {
|
||||
auth_payload["chatgpt_plan_type"] = serde_json::Value::String(chatgpt_plan_type);
|
||||
}
|
||||
|
||||
if let Some(chatgpt_account_id) = params.chatgpt_account_id {
|
||||
let org_value = serde_json::Value::String(chatgpt_account_id);
|
||||
auth_payload["chatgpt_account_id"] = org_value;
|
||||
}
|
||||
|
||||
let payload = serde_json::json!({
|
||||
"email": "user@example.com",
|
||||
"email_verified": true,
|
||||
"https://api.openai.com/auth": auth_payload,
|
||||
});
|
||||
let b64 = |b: &[u8]| base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(b);
|
||||
let header_b64 = b64(&serde_json::to_vec(&header)?);
|
||||
let payload_b64 = b64(&serde_json::to_vec(&payload)?);
|
||||
let signature_b64 = b64(b"sig");
|
||||
let fake_jwt = format!("{header_b64}.{payload_b64}.{signature_b64}");
|
||||
|
||||
let auth_json_data = json!({
|
||||
"OPENAI_API_KEY": params.openai_api_key,
|
||||
"tokens": {
|
||||
@@ -498,6 +466,42 @@ fn write_auth_file(params: AuthFileParams, codex_home: &Path) -> std::io::Result
|
||||
Ok(fake_jwt)
|
||||
}
|
||||
|
||||
fn fake_jwt_for_auth_file_params(params: &AuthFileParams) -> std::io::Result<String> {
|
||||
#[derive(Serialize)]
|
||||
struct Header {
|
||||
alg: &'static str,
|
||||
typ: &'static str,
|
||||
}
|
||||
|
||||
let header = Header {
|
||||
alg: "none",
|
||||
typ: "JWT",
|
||||
};
|
||||
let mut auth_payload = serde_json::json!({
|
||||
"chatgpt_user_id": "user-12345",
|
||||
"user_id": "user-12345",
|
||||
});
|
||||
|
||||
if let Some(chatgpt_plan_type) = params.chatgpt_plan_type.as_ref() {
|
||||
auth_payload["chatgpt_plan_type"] = serde_json::Value::String(chatgpt_plan_type.clone());
|
||||
}
|
||||
|
||||
if let Some(chatgpt_account_id) = params.chatgpt_account_id.as_ref() {
|
||||
auth_payload["chatgpt_account_id"] = serde_json::Value::String(chatgpt_account_id.clone());
|
||||
}
|
||||
|
||||
let payload = serde_json::json!({
|
||||
"email": "user@example.com",
|
||||
"email_verified": true,
|
||||
"https://api.openai.com/auth": auth_payload,
|
||||
});
|
||||
let b64 = |b: &[u8]| base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(b);
|
||||
let header_b64 = b64(&serde_json::to_vec(&header)?);
|
||||
let payload_b64 = b64(&serde_json::to_vec(&payload)?);
|
||||
let signature_b64 = b64(b"sig");
|
||||
Ok(format!("{header_b64}.{payload_b64}.{signature_b64}"))
|
||||
}
|
||||
|
||||
async fn build_config(
|
||||
codex_home: &Path,
|
||||
forced_login_method: Option<ForcedLoginMethod>,
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
use super::manager::ExternalAuth;
|
||||
use super::manager::ExternalAuthRefreshContext;
|
||||
use super::manager::ExternalAuthTokens;
|
||||
use async_trait::async_trait;
|
||||
use codex_protocol::config_types::ModelProviderAuthInfo;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
@@ -10,47 +14,61 @@ use tokio::process::Command;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ExternalBearerAuth {
|
||||
pub(crate) struct BearerTokenRefresher {
|
||||
state: Arc<ExternalBearerAuthState>,
|
||||
}
|
||||
|
||||
impl ExternalBearerAuth {
|
||||
impl BearerTokenRefresher {
|
||||
pub(crate) fn new(config: ModelProviderAuthInfo) -> Self {
|
||||
Self {
|
||||
state: Arc::new(ExternalBearerAuthState::new(config)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn resolve_access_token(&self) -> io::Result<String> {
|
||||
let mut cached = self.state.cached_token.lock().await;
|
||||
if let Some(cached_token) = cached.as_ref()
|
||||
&& cached_token.fetched_at.elapsed() < self.state.config.refresh_interval()
|
||||
{
|
||||
return Ok(cached_token.access_token.clone());
|
||||
}
|
||||
#[async_trait]
|
||||
impl ExternalAuth for BearerTokenRefresher {
|
||||
fn auth_mode(&self) -> crate::AuthMode {
|
||||
crate::AuthMode::ApiKey
|
||||
}
|
||||
|
||||
async fn resolve(&self) -> io::Result<Option<ExternalAuthTokens>> {
|
||||
let access_token = {
|
||||
let mut cached = self.state.cached_token.lock().await;
|
||||
if let Some(cached_token) = cached.as_ref()
|
||||
&& cached_token.fetched_at.elapsed() < self.state.config.refresh_interval()
|
||||
{
|
||||
cached_token.access_token.clone()
|
||||
} else {
|
||||
let access_token = run_provider_auth_command(&self.state.config).await?;
|
||||
*cached = Some(CachedExternalBearerToken {
|
||||
access_token: access_token.clone(),
|
||||
fetched_at: Instant::now(),
|
||||
});
|
||||
access_token
|
||||
}
|
||||
};
|
||||
Ok(Some(ExternalAuthTokens::access_token_only(access_token)))
|
||||
}
|
||||
|
||||
async fn refresh(
|
||||
&self,
|
||||
_context: ExternalAuthRefreshContext,
|
||||
) -> io::Result<ExternalAuthTokens> {
|
||||
let access_token = run_provider_auth_command(&self.state.config).await?;
|
||||
let mut cached = self.state.cached_token.lock().await;
|
||||
*cached = Some(CachedExternalBearerToken {
|
||||
access_token: access_token.clone(),
|
||||
fetched_at: Instant::now(),
|
||||
});
|
||||
Ok(access_token)
|
||||
}
|
||||
|
||||
pub(crate) async fn refresh_after_unauthorized(&self) -> io::Result<()> {
|
||||
let access_token = run_provider_auth_command(&self.state.config).await?;
|
||||
let mut cached = self.state.cached_token.lock().await;
|
||||
*cached = Some(CachedExternalBearerToken {
|
||||
access_token,
|
||||
fetched_at: Instant::now(),
|
||||
});
|
||||
Ok(())
|
||||
Ok(ExternalAuthTokens::access_token_only(access_token))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ExternalBearerAuth {
|
||||
impl fmt::Debug for BearerTokenRefresher {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("ExternalBearerAuth").finish_non_exhaustive()
|
||||
f.debug_struct("BearerTokenRefresher")
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ use codex_app_server_protocol::AuthMode as ApiAuthMode;
|
||||
use codex_protocol::config_types::ForcedLoginMethod;
|
||||
use codex_protocol::config_types::ModelProviderAuthInfo;
|
||||
|
||||
use super::external_bearer::ExternalBearerAuth;
|
||||
use super::external_bearer::BearerTokenRefresher;
|
||||
use crate::auth::error::RefreshTokenFailedError;
|
||||
use crate::auth::error::RefreshTokenFailedReason;
|
||||
pub use crate::auth::storage::AuthCredentialsStoreMode;
|
||||
@@ -143,11 +143,21 @@ pub struct ExternalAuthRefreshContext {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait ExternalAuthRefresher: Send + Sync {
|
||||
/// Pluggable auth provider used by `AuthManager` for externally managed auth flows.
|
||||
///
|
||||
/// Implementations may either resolve auth eagerly via `resolve()` or provide refreshed
|
||||
/// credentials on demand via `refresh()`.
|
||||
pub trait ExternalAuth: Send + Sync {
|
||||
/// Indicates which top-level auth mode this external provider supplies.
|
||||
fn auth_mode(&self) -> crate::AuthMode;
|
||||
|
||||
/// Returns cached or immediately available auth, if this provider can resolve it synchronously
|
||||
/// from the caller's perspective.
|
||||
async fn resolve(&self) -> std::io::Result<Option<ExternalAuthTokens>> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Refreshes auth in response to a manager-driven refresh attempt.
|
||||
async fn refresh(
|
||||
&self,
|
||||
context: ExternalAuthRefreshContext,
|
||||
@@ -853,27 +863,6 @@ struct AuthScopedRefreshFailure {
|
||||
error: RefreshTokenFailedError,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum ExternalAuth {
|
||||
Bearer(ExternalBearerAuth),
|
||||
ChatgptRefresher(Arc<dyn ExternalAuthRefresher>),
|
||||
}
|
||||
|
||||
impl Debug for ExternalAuth {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Bearer(_) => f
|
||||
.debug_tuple("ExternalAuth::Bearer")
|
||||
.field(&"present")
|
||||
.finish(),
|
||||
Self::ChatgptRefresher(_) => f
|
||||
.debug_tuple("ExternalAuth::ChatgptRefresher")
|
||||
.field(&"present")
|
||||
.finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for CachedAuth {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("CachedAuth")
|
||||
@@ -928,7 +917,7 @@ enum UnauthorizedRecoveryMode {
|
||||
//
|
||||
// - External ChatGPT auth tokens (`chatgptAuthTokens`) are refreshed by asking
|
||||
// the parent app for new tokens through the configured
|
||||
// `ExternalAuthRefresher`, persisting them in the ephemeral auth store, and
|
||||
// `ExternalAuth`, persisting them in the ephemeral auth store, and
|
||||
// reloading the cached auth snapshot.
|
||||
// - External bearer auth sources for custom model providers rerun the provider
|
||||
// auth command without touching disk.
|
||||
@@ -954,7 +943,7 @@ impl UnauthorizedRecovery {
|
||||
fn new(manager: Arc<AuthManager>) -> Self {
|
||||
let cached_auth = manager.auth_cached();
|
||||
let expected_account_id = cached_auth.as_ref().and_then(CodexAuth::get_account_id);
|
||||
let mode = if manager.has_external_bearer_auth()
|
||||
let mode = if manager.has_external_api_key_auth()
|
||||
|| cached_auth
|
||||
.as_ref()
|
||||
.is_some_and(CodexAuth::is_external_chatgpt_tokens)
|
||||
@@ -976,7 +965,7 @@ impl UnauthorizedRecovery {
|
||||
}
|
||||
|
||||
pub fn has_next(&self) -> bool {
|
||||
if self.manager.has_external_bearer_auth() {
|
||||
if self.manager.has_external_api_key_auth() {
|
||||
return !matches!(self.step, UnauthorizedRecoveryStep::Done);
|
||||
}
|
||||
|
||||
@@ -989,9 +978,7 @@ impl UnauthorizedRecovery {
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.mode == UnauthorizedRecoveryMode::External
|
||||
&& !self.manager.has_external_chatgpt_auth_refresher()
|
||||
{
|
||||
if self.mode == UnauthorizedRecoveryMode::External && !self.manager.has_external_auth() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -999,7 +986,7 @@ impl UnauthorizedRecovery {
|
||||
}
|
||||
|
||||
pub fn unavailable_reason(&self) -> &'static str {
|
||||
if self.manager.has_external_bearer_auth() {
|
||||
if self.manager.has_external_api_key_auth() {
|
||||
return if matches!(self.step, UnauthorizedRecoveryStep::Done) {
|
||||
"recovery_exhausted"
|
||||
} else {
|
||||
@@ -1016,10 +1003,8 @@ impl UnauthorizedRecovery {
|
||||
return "not_chatgpt_auth";
|
||||
}
|
||||
|
||||
if self.mode == UnauthorizedRecoveryMode::External
|
||||
&& !self.manager.has_external_chatgpt_auth_refresher()
|
||||
{
|
||||
return "no_external_refresher";
|
||||
if self.mode == UnauthorizedRecoveryMode::External && !self.manager.has_external_auth() {
|
||||
return "no_external_auth";
|
||||
}
|
||||
|
||||
if matches!(self.step, UnauthorizedRecoveryStep::Done) {
|
||||
@@ -1112,7 +1097,6 @@ impl UnauthorizedRecovery {
|
||||
/// External modifications to `auth.json` will NOT be observed until
|
||||
/// `reload()` is called explicitly. This matches the design goal of avoiding
|
||||
/// different parts of the program seeing inconsistent auth data mid‑run.
|
||||
#[derive(Debug)]
|
||||
pub struct AuthManager {
|
||||
codex_home: PathBuf,
|
||||
inner: RwLock<CachedAuth>,
|
||||
@@ -1120,7 +1104,26 @@ pub struct AuthManager {
|
||||
auth_credentials_store_mode: AuthCredentialsStoreMode,
|
||||
forced_chatgpt_workspace_id: RwLock<Option<String>>,
|
||||
refresh_lock: AsyncMutex<()>,
|
||||
external_auth: RwLock<Option<ExternalAuth>>,
|
||||
external_auth: RwLock<Option<Arc<dyn ExternalAuth>>>,
|
||||
}
|
||||
|
||||
impl Debug for AuthManager {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("AuthManager")
|
||||
.field("codex_home", &self.codex_home)
|
||||
.field("inner", &self.inner)
|
||||
.field("enable_codex_api_key_env", &self.enable_codex_api_key_env)
|
||||
.field(
|
||||
"auth_credentials_store_mode",
|
||||
&self.auth_credentials_store_mode,
|
||||
)
|
||||
.field(
|
||||
"forced_chatgpt_workspace_id",
|
||||
&self.forced_chatgpt_workspace_id,
|
||||
)
|
||||
.field("has_external_auth", &self.has_external_auth())
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl AuthManager {
|
||||
@@ -1200,7 +1203,9 @@ impl AuthManager {
|
||||
auth_credentials_store_mode: AuthCredentialsStoreMode::File,
|
||||
forced_chatgpt_workspace_id: RwLock::new(None),
|
||||
refresh_lock: AsyncMutex::new(()),
|
||||
external_auth: RwLock::new(Some(ExternalAuth::Bearer(ExternalBearerAuth::new(config)))),
|
||||
external_auth: RwLock::new(Some(
|
||||
Arc::new(BearerTokenRefresher::new(config)) as Arc<dyn ExternalAuth>
|
||||
)),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1223,7 +1228,7 @@ impl AuthManager {
|
||||
/// For stale managed ChatGPT auth, first performs a guarded reload and then
|
||||
/// refreshes only if the on-disk auth is unchanged.
|
||||
pub async fn auth(&self) -> Option<CodexAuth> {
|
||||
if let Some(auth) = self.resolve_external_bearer_auth().await {
|
||||
if let Some(auth) = self.resolve_external_api_key_auth().await {
|
||||
return Some(auth);
|
||||
}
|
||||
|
||||
@@ -1346,13 +1351,13 @@ impl AuthManager {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_external_chatgpt_auth_refresher(&self, refresher: Arc<dyn ExternalAuthRefresher>) {
|
||||
pub fn set_external_auth(&self, external_auth: Arc<dyn ExternalAuth>) {
|
||||
if let Ok(mut guard) = self.external_auth.write() {
|
||||
*guard = Some(ExternalAuth::ChatgptRefresher(refresher));
|
||||
*guard = Some(external_auth);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_external_chatgpt_auth_refresher(&self) {
|
||||
pub fn clear_external_auth(&self) {
|
||||
if let Ok(mut guard) = self.external_auth.write() {
|
||||
*guard = None;
|
||||
}
|
||||
@@ -1371,12 +1376,8 @@ impl AuthManager {
|
||||
.and_then(|guard| guard.clone())
|
||||
}
|
||||
|
||||
pub fn has_external_chatgpt_auth_refresher(&self) -> bool {
|
||||
self.external_auth
|
||||
.read()
|
||||
.ok()
|
||||
.map(|guard| matches!(guard.as_ref(), Some(ExternalAuth::ChatgptRefresher(_))))
|
||||
.unwrap_or(false)
|
||||
pub fn has_external_auth(&self) -> bool {
|
||||
self.external_auth().is_some()
|
||||
}
|
||||
|
||||
pub fn is_external_chatgpt_auth_active(&self) -> bool {
|
||||
@@ -1402,18 +1403,18 @@ impl AuthManager {
|
||||
))
|
||||
}
|
||||
|
||||
pub fn shared_with_external_chatgpt_auth_refresher(
|
||||
pub fn shared_with_external_auth(
|
||||
codex_home: PathBuf,
|
||||
enable_codex_api_key_env: bool,
|
||||
auth_credentials_store_mode: AuthCredentialsStoreMode,
|
||||
refresher: Arc<dyn ExternalAuthRefresher>,
|
||||
external_auth: Arc<dyn ExternalAuth>,
|
||||
) -> Arc<Self> {
|
||||
let manager = Self::shared(
|
||||
codex_home,
|
||||
enable_codex_api_key_env,
|
||||
auth_credentials_store_mode,
|
||||
);
|
||||
manager.set_external_chatgpt_auth_refresher(refresher);
|
||||
manager.set_external_auth(external_auth);
|
||||
manager
|
||||
}
|
||||
|
||||
@@ -1421,26 +1422,35 @@ impl AuthManager {
|
||||
UnauthorizedRecovery::new(Arc::clone(self))
|
||||
}
|
||||
|
||||
fn external_auth(&self) -> Option<ExternalAuth> {
|
||||
fn external_auth(&self) -> Option<Arc<dyn ExternalAuth>> {
|
||||
self.external_auth
|
||||
.read()
|
||||
.ok()
|
||||
.and_then(|guard| guard.clone())
|
||||
.and_then(|guard| guard.as_ref().cloned())
|
||||
}
|
||||
|
||||
fn has_external_bearer_auth(&self) -> bool {
|
||||
matches!(self.external_auth(), Some(ExternalAuth::Bearer(_)))
|
||||
fn external_auth_mode(&self) -> Option<crate::AuthMode> {
|
||||
self.external_auth()
|
||||
.as_ref()
|
||||
.map(|external_auth| external_auth.auth_mode())
|
||||
}
|
||||
|
||||
async fn resolve_external_bearer_auth(&self) -> Option<CodexAuth> {
|
||||
let ExternalAuth::Bearer(bearer_auth) = self.external_auth()? else {
|
||||
fn has_external_api_key_auth(&self) -> bool {
|
||||
self.external_auth_mode() == Some(crate::AuthMode::ApiKey)
|
||||
}
|
||||
|
||||
async fn resolve_external_api_key_auth(&self) -> Option<CodexAuth> {
|
||||
if !self.has_external_api_key_auth() {
|
||||
return None;
|
||||
};
|
||||
}
|
||||
|
||||
match bearer_auth.resolve_access_token().await {
|
||||
Ok(access_token) => Some(CodexAuth::from_api_key(&access_token)),
|
||||
let external_auth = self.external_auth()?;
|
||||
|
||||
match external_auth.resolve().await {
|
||||
Ok(Some(tokens)) => Some(CodexAuth::from_api_key(&tokens.access_token)),
|
||||
Ok(None) => None,
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to resolve external bearer auth: {err}");
|
||||
tracing::error!("Failed to resolve external API key auth: {err}");
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -1534,14 +1544,14 @@ impl AuthManager {
|
||||
}
|
||||
|
||||
pub fn get_api_auth_mode(&self) -> Option<ApiAuthMode> {
|
||||
if self.has_external_bearer_auth() {
|
||||
if self.has_external_api_key_auth() {
|
||||
return Some(ApiAuthMode::ApiKey);
|
||||
}
|
||||
self.auth_cached().as_ref().map(CodexAuth::api_auth_mode)
|
||||
}
|
||||
|
||||
pub fn auth_mode(&self) -> Option<crate::AuthMode> {
|
||||
if self.has_external_bearer_auth() {
|
||||
if self.has_external_api_key_auth() {
|
||||
return Some(crate::AuthMode::ApiKey);
|
||||
}
|
||||
self.auth_cached().as_ref().map(CodexAuth::auth_mode)
|
||||
@@ -1573,20 +1583,12 @@ impl AuthManager {
|
||||
&self,
|
||||
reason: ExternalAuthRefreshReason,
|
||||
) -> Result<(), RefreshTokenError> {
|
||||
if let Some(ExternalAuth::Bearer(bearer_auth)) = self.external_auth() {
|
||||
return bearer_auth
|
||||
.refresh_after_unauthorized()
|
||||
.await
|
||||
.map_err(RefreshTokenError::Transient);
|
||||
}
|
||||
|
||||
let forced_chatgpt_workspace_id = self.forced_chatgpt_workspace_id();
|
||||
let Some(ExternalAuth::ChatgptRefresher(refresher)) = self.external_auth() else {
|
||||
let Some(external_auth) = self.external_auth() else {
|
||||
return Err(RefreshTokenError::Transient(std::io::Error::other(
|
||||
"external auth refresher is not configured",
|
||||
"external auth is not configured",
|
||||
)));
|
||||
};
|
||||
|
||||
let forced_chatgpt_workspace_id = self.forced_chatgpt_workspace_id();
|
||||
let previous_account_id = self
|
||||
.auth_cached()
|
||||
.as_ref()
|
||||
@@ -1596,7 +1598,13 @@ impl AuthManager {
|
||||
previous_account_id,
|
||||
};
|
||||
|
||||
let refreshed = refresher.refresh(context).await?;
|
||||
let refreshed = external_auth
|
||||
.refresh(context)
|
||||
.await
|
||||
.map_err(RefreshTokenError::Transient)?;
|
||||
if external_auth.auth_mode() == crate::AuthMode::ApiKey {
|
||||
return Ok(());
|
||||
}
|
||||
let Some(chatgpt_metadata) = refreshed.chatgpt_metadata() else {
|
||||
return Err(RefreshTokenError::Transient(std::io::Error::other(
|
||||
"external auth refresh did not return ChatGPT metadata",
|
||||
|
||||
@@ -22,10 +22,10 @@ pub use auth::AuthManager;
|
||||
pub use auth::CLIENT_ID;
|
||||
pub use auth::CODEX_API_KEY_ENV_VAR;
|
||||
pub use auth::CodexAuth;
|
||||
pub use auth::ExternalAuth;
|
||||
pub use auth::ExternalAuthChatgptMetadata;
|
||||
pub use auth::ExternalAuthRefreshContext;
|
||||
pub use auth::ExternalAuthRefreshReason;
|
||||
pub use auth::ExternalAuthRefresher;
|
||||
pub use auth::ExternalAuthTokens;
|
||||
pub use auth::OPENAI_API_KEY_ENV_VAR;
|
||||
pub use auth::REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR;
|
||||
|
||||
Reference in New Issue
Block a user