Skip to content

Commit 3da3318

Browse files
committed
auth: let AuthManager own external bearer auth
1 parent 4e7d464 commit 3da3318

File tree

2 files changed

+216
-45
lines changed

2 files changed

+216
-45
lines changed

codex-rs/login/src/auth/auth_tests.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::auth::storage::get_auth_file;
44
use crate::token_data::IdTokenInfo;
55
use crate::token_data::KnownPlan as InternalKnownPlan;
66
use crate::token_data::PlanType as InternalPlanType;
7+
use async_trait::async_trait;
78
use codex_protocol::account::PlanType as AccountPlanType;
89

910
use base64::Engine;
@@ -12,6 +13,7 @@ use pretty_assertions::assert_eq;
1213
use serde::Serialize;
1314
use serde_json::json;
1415
use std::sync::Arc;
16+
use std::sync::Mutex;
1517
use tempfile::tempdir;
1618

1719
#[tokio::test]
@@ -265,6 +267,87 @@ fn external_auth_tokens_without_chatgpt_metadata_cannot_seed_chatgpt_auth() {
265267
);
266268
}
267269

270+
#[tokio::test]
271+
async fn auth_manager_with_external_bearer_refresher_returns_provider_token_only_for_derived_manager()
272+
{
273+
let base_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("base-token"));
274+
let derived_manager =
275+
base_manager.with_external_bearer_refresher(Arc::new(StaticExternalAuthRefresher::new(
276+
Some(ExternalAuthTokens::access_token_only("provider-token")),
277+
ExternalAuthTokens::access_token_only("refreshed-provider-token"),
278+
)));
279+
280+
assert_eq!(
281+
base_manager
282+
.auth()
283+
.await
284+
.and_then(|auth| auth.api_key().map(str::to_string)),
285+
Some("base-token".to_string())
286+
);
287+
assert_eq!(
288+
derived_manager
289+
.auth()
290+
.await
291+
.and_then(|auth| auth.api_key().map(str::to_string)),
292+
Some("provider-token".to_string())
293+
);
294+
}
295+
296+
#[tokio::test]
297+
async fn unauthorized_recovery_uses_external_refresh_for_bearer_manager() {
298+
let base_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("base-token"));
299+
let refresher = Arc::new(StaticExternalAuthRefresher::new(
300+
Some(ExternalAuthTokens::access_token_only("provider-token")),
301+
ExternalAuthTokens::access_token_only("refreshed-provider-token"),
302+
));
303+
let derived_manager = base_manager.with_external_bearer_refresher(refresher.clone());
304+
let mut recovery = derived_manager.unauthorized_recovery();
305+
306+
assert!(recovery.has_next());
307+
assert_eq!(recovery.mode_name(), "external");
308+
assert_eq!(recovery.step_name(), "external_refresh");
309+
310+
let result = recovery
311+
.next()
312+
.await
313+
.expect("external refresh should succeed");
314+
315+
assert_eq!(result.auth_state_changed(), Some(true));
316+
assert_eq!(*refresher.refresh_calls.lock().unwrap(), 1);
317+
}
318+
319+
#[derive(Debug)]
320+
struct StaticExternalAuthRefresher {
321+
resolved: Option<ExternalAuthTokens>,
322+
refreshed: ExternalAuthTokens,
323+
refresh_calls: Mutex<usize>,
324+
}
325+
326+
impl StaticExternalAuthRefresher {
327+
fn new(resolved: Option<ExternalAuthTokens>, refreshed: ExternalAuthTokens) -> Self {
328+
Self {
329+
resolved,
330+
refreshed,
331+
refresh_calls: Mutex::new(0),
332+
}
333+
}
334+
}
335+
336+
#[async_trait]
337+
impl ExternalAuthRefresher for StaticExternalAuthRefresher {
338+
async fn resolve(&self) -> std::io::Result<Option<ExternalAuthTokens>> {
339+
Ok(self.resolved.clone())
340+
}
341+
342+
async fn refresh(
343+
&self,
344+
_context: ExternalAuthRefreshContext,
345+
) -> std::io::Result<ExternalAuthTokens> {
346+
*self.refresh_calls.lock().unwrap() += 1;
347+
Ok(self.refreshed.clone())
348+
}
349+
}
350+
268351
struct AuthFileParams {
269352
openai_api_key: Option<String>,
270353
chatgpt_plan_type: Option<String>,

0 commit comments

Comments
 (0)