fix(login): fallback to /oauth2/token and send Accept header for token exchange\n\nSome environments report failures posting to https://auth.openai.com/oauth/token. Try both /oauth/token and /oauth2/token, include Accept: application/json, and surface clearer error messages.\n\nThis improves compatibility with recent issuer changes and should resolve token exchange failures.\n\nCloses #21

This commit is contained in:
James Peter
2025-09-06 10:25:27 +10:00
parent 77c9930809
commit c8b4584b1f
+56 -37
View File
@@ -326,26 +326,36 @@ async fn exchange_code_for_tokens(
} }
let client = codex_core::http_client::build_http_client(); let client = codex_core::http_client::build_http_client();
let resp = client let body = format!(
.post(format!("{issuer}/oauth/token")) "grant_type=authorization_code&code={}&redirect_uri={}&client_id={}&code_verifier={}",
.header("Content-Type", "application/x-www-form-urlencoded") urlencoding::encode(code),
.body(format!( urlencoding::encode(redirect_uri),
"grant_type=authorization_code&code={}&redirect_uri={}&client_id={}&code_verifier={}", urlencoding::encode(client_id),
urlencoding::encode(code), urlencoding::encode(&pkce.code_verifier)
urlencoding::encode(redirect_uri), );
urlencoding::encode(client_id),
urlencoding::encode(&pkce.code_verifier)
))
.send()
.await
.map_err(io::Error::other)?;
if !resp.status().is_success() { // Try legacy endpoint first, then fallback to oauth2 path used by some issuers
return Err(io::Error::other(format!( let mut last_status: Option<reqwest::StatusCode> = None;
"token endpoint returned status {}", let mut resp_opt = None;
resp.status() for path in ["/oauth/token", "/oauth2/token"] {
))); let url = format!("{issuer}{path}");
let res = client
.post(&url)
.header("Content-Type", "application/x-www-form-urlencoded")
.header("Accept", "application/json")
.body(body.clone())
.send()
.await;
match res {
Ok(resp) if resp.status().is_success() => { resp_opt = Some(resp); break; }
Ok(resp) => { last_status = Some(resp.status()); }
Err(e) => { return Err(io::Error::other(format!("failed to POST {}: {}", url, e))); }
}
} }
let resp = resp_opt.ok_or_else(|| io::Error::other(format!(
"token endpoint failed (last status {:?})",
last_status
)))?;
let tokens: TokenResponse = resp.json().await.map_err(io::Error::other)?; let tokens: TokenResponse = resp.json().await.map_err(io::Error::other)?;
Ok(ExchangedTokens { Ok(ExchangedTokens {
@@ -495,26 +505,35 @@ async fn obtain_api_key(issuer: &str, client_id: &str, id_token: &str) -> io::Re
access_token: String, access_token: String,
} }
let client = codex_core::http_client::build_http_client(); let client = codex_core::http_client::build_http_client();
let resp = client let form = format!(
.post(format!("{issuer}/oauth/token")) "grant_type={}&client_id={}&requested_token={}&subject_token={}&subject_token_type={}",
.header("Content-Type", "application/x-www-form-urlencoded") urlencoding::encode("urn:ietf:params:oauth:grant-type:token-exchange"),
.body(format!( urlencoding::encode(client_id),
"grant_type={}&client_id={}&requested_token={}&subject_token={}&subject_token_type={}", urlencoding::encode("openai-api-key"),
urlencoding::encode("urn:ietf:params:oauth:grant-type:token-exchange"), urlencoding::encode(id_token),
urlencoding::encode(client_id), urlencoding::encode("urn:ietf:params:oauth:token-type:id_token")
urlencoding::encode("openai-api-key"), );
urlencoding::encode(id_token), let mut last_status: Option<reqwest::StatusCode> = None;
urlencoding::encode("urn:ietf:params:oauth:token-type:id_token") let mut resp_opt = None;
)) for path in ["/oauth/token", "/oauth2/token"] {
.send() let url = format!("{issuer}{path}");
.await let res = client
.map_err(io::Error::other)?; .post(&url)
if !resp.status().is_success() { .header("Content-Type", "application/x-www-form-urlencoded")
return Err(io::Error::other(format!( .header("Accept", "application/json")
"api key exchange failed with status {}", .body(form.clone())
resp.status() .send()
))); .await;
match res {
Ok(resp) if resp.status().is_success() => { resp_opt = Some(resp); break; }
Ok(resp) => { last_status = Some(resp.status()); }
Err(e) => { return Err(io::Error::other(format!("failed to POST {}: {}", url, e))); }
}
} }
let resp = resp_opt.ok_or_else(|| io::Error::other(format!(
"api key exchange failed (last status {:?})",
last_status
)))?;
let body: ExchangeResp = resp.json().await.map_err(io::Error::other)?; let body: ExchangeResp = resp.json().await.map_err(io::Error::other)?;
Ok(body.access_token) Ok(body.access_token)
} }