@@ -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