Refresh tokens more often and log a better message when both auth and token refresh fails (#5655)
<img width="784" height="153" alt="image" src="https://github.com/user-attachments/assets/c44b0eb2-d65c-4fc2-8b54-b34f7e1c4d95" />
This commit is contained in:
@@ -25,6 +25,7 @@ use crate::default_client::CodexHttpClient;
|
|||||||
use crate::token_data::PlanType;
|
use crate::token_data::PlanType;
|
||||||
use crate::token_data::TokenData;
|
use crate::token_data::TokenData;
|
||||||
use crate::token_data::parse_id_token;
|
use crate::token_data::parse_id_token;
|
||||||
|
use crate::util::try_parse_error_message;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CodexAuth {
|
pub struct CodexAuth {
|
||||||
@@ -42,6 +43,9 @@ impl PartialEq for CodexAuth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(pakrym): use token exp field to check for expiration instead
|
||||||
|
const TOKEN_REFRESH_INTERVAL: i64 = 8;
|
||||||
|
|
||||||
impl CodexAuth {
|
impl CodexAuth {
|
||||||
pub async fn refresh_token(&self) -> Result<String, std::io::Error> {
|
pub async fn refresh_token(&self) -> Result<String, std::io::Error> {
|
||||||
tracing::info!("Refreshing token");
|
tracing::info!("Refreshing token");
|
||||||
@@ -94,7 +98,7 @@ impl CodexAuth {
|
|||||||
last_refresh: Some(last_refresh),
|
last_refresh: Some(last_refresh),
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
if last_refresh < Utc::now() - chrono::Duration::days(28) {
|
if last_refresh < Utc::now() - chrono::Duration::days(TOKEN_REFRESH_INTERVAL) {
|
||||||
let refresh_response = tokio::time::timeout(
|
let refresh_response = tokio::time::timeout(
|
||||||
Duration::from_secs(60),
|
Duration::from_secs(60),
|
||||||
try_refresh_token(tokens.refresh_token.clone(), &self.client),
|
try_refresh_token(tokens.refresh_token.clone(), &self.client),
|
||||||
@@ -446,8 +450,9 @@ async fn try_refresh_token(
|
|||||||
Ok(refresh_response)
|
Ok(refresh_response)
|
||||||
} else {
|
} else {
|
||||||
Err(std::io::Error::other(format!(
|
Err(std::io::Error::other(format!(
|
||||||
"Failed to refresh token: {}",
|
"Failed to refresh token: {}: {}",
|
||||||
response.status()
|
response.status(),
|
||||||
|
try_parse_error_message(&response.text().await.unwrap_or_default()),
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
const INITIAL_DELAY_MS: u64 = 200;
|
const INITIAL_DELAY_MS: u64 = 200;
|
||||||
const BACKOFF_FACTOR: f64 = 2.0;
|
const BACKOFF_FACTOR: f64 = 2.0;
|
||||||
@@ -11,3 +12,47 @@ pub(crate) fn backoff(attempt: u64) -> Duration {
|
|||||||
let jitter = rand::rng().random_range(0.9..1.1);
|
let jitter = rand::rng().random_range(0.9..1.1);
|
||||||
Duration::from_millis((base as f64 * jitter) as u64)
|
Duration::from_millis((base as f64 * jitter) as u64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn try_parse_error_message(text: &str) -> String {
|
||||||
|
debug!("Parsing server error response: {}", text);
|
||||||
|
let json = serde_json::from_str::<serde_json::Value>(text).unwrap_or_default();
|
||||||
|
if let Some(error) = json.get("error")
|
||||||
|
&& let Some(message) = error.get("message")
|
||||||
|
&& let Some(message_str) = message.as_str()
|
||||||
|
{
|
||||||
|
return message_str.to_string();
|
||||||
|
}
|
||||||
|
if text.is_empty() {
|
||||||
|
return "Unknown error".to_string();
|
||||||
|
}
|
||||||
|
text.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_try_parse_error_message() {
|
||||||
|
let text = r#"{
|
||||||
|
"error": {
|
||||||
|
"message": "Your refresh token has already been used to generate a new access token. Please try signing in again.",
|
||||||
|
"type": "invalid_request_error",
|
||||||
|
"param": null,
|
||||||
|
"code": "refresh_token_reused"
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
let message = try_parse_error_message(text);
|
||||||
|
assert_eq!(
|
||||||
|
message,
|
||||||
|
"Your refresh token has already been used to generate a new access token. Please try signing in again."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_try_parse_error_message_no_error() {
|
||||||
|
let text = r#"{"message": "test"}"#;
|
||||||
|
let message = try_parse_error_message(text);
|
||||||
|
assert_eq!(message, r#"{"message": "test"}"#);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user