Skip to content

Commit 242614b

Browse files
committed
codex: fix follow-up PR blockers (#15106)
1 parent a8e0608 commit 242614b

File tree

3 files changed

+100
-17
lines changed

3 files changed

+100
-17
lines changed

codex-rs/app-server-client/src/lib.rs

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -335,14 +335,6 @@ impl ChatgptAuthRefreshContext {
335335
"local ChatGPT auth must use workspace {expected_workspace}, but found {chatgpt_account_id:?}"
336336
));
337337
}
338-
if let Some(previous_account_id) = params.previous_account_id.as_deref()
339-
&& previous_account_id != chatgpt_account_id
340-
{
341-
return Err(format!(
342-
"local ChatGPT auth refresh account mismatch: expected `{previous_account_id}`, got `{chatgpt_account_id}`"
343-
));
344-
}
345-
346338
Ok(ChatgptAuthTokensRefreshResponse {
347339
access_token,
348340
chatgpt_account_id,
@@ -1706,6 +1698,28 @@ mod tests {
17061698
assert!(!response.access_token.is_empty());
17071699
}
17081700

1701+
#[test]
1702+
fn chatgpt_auth_refresh_context_ignores_previous_workspace_mismatch() {
1703+
let codex_home = TestCodexHome::new();
1704+
write_local_chatgpt_auth(&codex_home.path);
1705+
let context = ChatgptAuthRefreshContext {
1706+
codex_home: codex_home.path.clone(),
1707+
auth_credentials_store_mode: AuthCredentialsStoreMode::File,
1708+
forced_chatgpt_workspace_id: Some("workspace-1".to_string()),
1709+
};
1710+
1711+
let response = context
1712+
.resolve_refresh_response(&ChatgptAuthTokensRefreshParams {
1713+
reason: ChatgptAuthTokensRefreshReason::Unauthorized,
1714+
previous_account_id: Some("workspace-2".to_string()),
1715+
})
1716+
.expect("stale previous workspace should not fail local auth refresh");
1717+
1718+
assert_eq!(response.chatgpt_account_id, "workspace-1");
1719+
assert_eq!(response.chatgpt_plan_type.as_deref(), Some("business"));
1720+
assert!(!response.access_token.is_empty());
1721+
}
1722+
17091723
#[test]
17101724
fn chatgpt_auth_refresh_context_rejects_workspace_mismatch() {
17111725
let codex_home = TestCodexHome::new();

codex-rs/exec/src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1006,7 +1006,6 @@ fn all_thread_source_kinds() -> Vec<ThreadSourceKind> {
10061006
ThreadSourceKind::VsCode,
10071007
ThreadSourceKind::Exec,
10081008
ThreadSourceKind::AppServer,
1009-
ThreadSourceKind::Custom,
10101009
ThreadSourceKind::SubAgent,
10111010
ThreadSourceKind::SubAgentReview,
10121011
ThreadSourceKind::SubAgentCompact,

codex-rs/tui_app_server/src/app/app_server_adapter.rs

Lines changed: 78 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -294,14 +294,7 @@ fn resolve_chatgpt_auth_tokens_refresh_response(
294294
auth_credentials_store_mode,
295295
forced_chatgpt_workspace_id,
296296
)?;
297-
if let Some(previous_account_id) = params.previous_account_id.as_deref()
298-
&& previous_account_id != auth.chatgpt_account_id
299-
{
300-
return Err(format!(
301-
"local ChatGPT auth refresh account mismatch: expected `{previous_account_id}`, got `{}`",
302-
auth.chatgpt_account_id
303-
));
304-
}
297+
let _ = params;
305298
Ok(auth.to_refresh_response())
306299
}
307300

@@ -489,15 +482,71 @@ mod tests {
489482
use super::LegacyThreadNotification;
490483
use super::ServerNotificationThreadTarget;
491484
use super::legacy_thread_notification;
485+
use super::resolve_chatgpt_auth_tokens_refresh_response;
492486
use super::server_notification_thread_target;
487+
use base64::Engine;
488+
use chrono::Utc;
489+
use codex_app_server_protocol::AuthMode;
490+
use codex_app_server_protocol::ChatgptAuthTokensRefreshParams;
491+
use codex_app_server_protocol::ChatgptAuthTokensRefreshReason;
493492
use codex_app_server_protocol::JSONRPCNotification;
494493
use codex_app_server_protocol::ServerNotification;
495494
use codex_app_server_protocol::Turn;
496495
use codex_app_server_protocol::TurnStartedNotification;
497496
use codex_app_server_protocol::TurnStatus;
497+
use codex_core::auth::AuthCredentialsStoreMode;
498+
use codex_core::auth::AuthDotJson;
499+
use codex_core::auth::save_auth;
500+
use codex_core::token_data::TokenData;
498501
use codex_protocol::ThreadId;
499502
use pretty_assertions::assert_eq;
503+
use serde::Serialize;
500504
use serde_json::json;
505+
use tempfile::TempDir;
506+
507+
fn fake_jwt(email: &str, account_id: &str, plan_type: &str) -> String {
508+
#[derive(Serialize)]
509+
struct Header {
510+
alg: &'static str,
511+
typ: &'static str,
512+
}
513+
514+
let header = Header {
515+
alg: "none",
516+
typ: "JWT",
517+
};
518+
let payload = json!({
519+
"email": email,
520+
"https://api.openai.com/auth": {
521+
"chatgpt_account_id": account_id,
522+
"chatgpt_plan_type": plan_type,
523+
},
524+
});
525+
let encode = |bytes: &[u8]| base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(bytes);
526+
let header_b64 = encode(&serde_json::to_vec(&header).expect("serialize header"));
527+
let payload_b64 = encode(&serde_json::to_vec(&payload).expect("serialize payload"));
528+
let signature_b64 = encode(b"sig");
529+
format!("{header_b64}.{payload_b64}.{signature_b64}")
530+
}
531+
532+
fn write_chatgpt_auth(codex_home: &std::path::Path) {
533+
let id_token = fake_jwt("user@example.com", "workspace-1", "business");
534+
let access_token = fake_jwt("user@example.com", "workspace-1", "business");
535+
let auth = AuthDotJson {
536+
auth_mode: Some(AuthMode::Chatgpt),
537+
openai_api_key: None,
538+
tokens: Some(TokenData {
539+
id_token: codex_core::token_data::parse_chatgpt_jwt_claims(&id_token)
540+
.expect("id token should parse"),
541+
access_token,
542+
refresh_token: "refresh-token".to_string(),
543+
account_id: Some("workspace-1".to_string()),
544+
}),
545+
last_refresh: Some(Utc::now()),
546+
};
547+
save_auth(codex_home, &auth, AuthCredentialsStoreMode::File)
548+
.expect("chatgpt auth should save");
549+
}
501550

502551
#[test]
503552
fn legacy_warning_notification_extracts_thread_id_and_message() {
@@ -580,4 +629,25 @@ mod tests {
580629
ServerNotificationThreadTarget::InvalidThreadId("not-a-thread-id".to_string())
581630
);
582631
}
632+
633+
#[test]
634+
fn chatgpt_auth_refresh_ignores_previous_workspace_mismatch() {
635+
let codex_home = TempDir::new().expect("tempdir");
636+
write_chatgpt_auth(codex_home.path());
637+
638+
let response = resolve_chatgpt_auth_tokens_refresh_response(
639+
codex_home.path(),
640+
AuthCredentialsStoreMode::File,
641+
Some("workspace-1"),
642+
&ChatgptAuthTokensRefreshParams {
643+
reason: ChatgptAuthTokensRefreshReason::Unauthorized,
644+
previous_account_id: Some("workspace-2".to_string()),
645+
},
646+
)
647+
.expect("stale previous workspace should not fail local auth refresh");
648+
649+
assert_eq!(response.chatgpt_account_id, "workspace-1");
650+
assert_eq!(response.chatgpt_plan_type.as_deref(), Some("business"));
651+
assert!(!response.access_token.is_empty());
652+
}
583653
}

0 commit comments

Comments
 (0)