mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-07 01:47:47 +08:00
Distinguish expired OAuth token status
This commit is contained in:
@@ -948,10 +948,29 @@ pub fn codex_build_invalid_state(
|
||||
|
||||
pub fn codex_looks_like_token_invalidated(message: Option<&str>) -> bool {
|
||||
let lowered = message.unwrap_or_default().trim().to_ascii_lowercase();
|
||||
lowered.contains("token invalid")
|
||||
lowered.contains("token_invalidated")
|
||||
|| lowered.contains("authentication token has been invalidated")
|
||||
|| lowered.contains("token has been invalidated")
|
||||
|| lowered.contains("token invalidated")
|
||||
|| lowered.contains("session has expired")
|
||||
|| lowered.contains("invalidated")
|
||||
|| lowered.contains("revoked")
|
||||
|| lowered.contains("已撤销")
|
||||
|| lowered.contains("被撤销")
|
||||
|| lowered.contains("撤销")
|
||||
|| lowered.contains("作废")
|
||||
}
|
||||
|
||||
pub fn codex_looks_like_token_expired(message: Option<&str>) -> bool {
|
||||
let lowered = message.unwrap_or_default().trim().to_ascii_lowercase();
|
||||
lowered.contains("session has expired")
|
||||
|| lowered.contains("session expired")
|
||||
|| lowered.contains("access token expired")
|
||||
|| lowered.contains("expired access token")
|
||||
|| lowered.contains("token has expired")
|
||||
|| lowered.contains("token expired")
|
||||
|| lowered.contains("security token included in the request is expired")
|
||||
|| lowered.contains("已过期")
|
||||
|| lowered.contains("过期")
|
||||
}
|
||||
|
||||
fn codex_looks_like_account_deactivated(message: Option<&str>) -> bool {
|
||||
@@ -980,7 +999,15 @@ pub fn codex_structured_invalid_reason(status_code: u16, upstream_message: Optio
|
||||
}
|
||||
if codex_looks_like_token_invalidated(Some(message)) {
|
||||
let detail = if message.is_empty() {
|
||||
"Codex Token 无效或已过期"
|
||||
"Codex Token 已失效"
|
||||
} else {
|
||||
message
|
||||
};
|
||||
return format!("{OAUTH_EXPIRED_PREFIX}{detail}");
|
||||
}
|
||||
if codex_looks_like_token_expired(Some(message)) {
|
||||
let detail = if message.is_empty() {
|
||||
"Codex Token 已过期"
|
||||
} else {
|
||||
message
|
||||
};
|
||||
@@ -988,7 +1015,7 @@ pub fn codex_structured_invalid_reason(status_code: u16, upstream_message: Optio
|
||||
}
|
||||
if status_code == 401 {
|
||||
let detail = if message.is_empty() {
|
||||
"Codex Token 无效或已过期 (401)"
|
||||
"Codex Token 已过期 (401)"
|
||||
} else {
|
||||
message
|
||||
};
|
||||
@@ -1021,6 +1048,7 @@ pub fn codex_runtime_invalid_reason(
|
||||
401 => Some(codex_structured_invalid_reason(401, upstream_message)),
|
||||
402 => Some(codex_structured_invalid_reason(402, upstream_message)),
|
||||
403 if codex_looks_like_token_invalidated(upstream_message)
|
||||
|| codex_looks_like_token_expired(upstream_message)
|
||||
|| codex_looks_like_account_deactivated(upstream_message) =>
|
||||
{
|
||||
Some(codex_structured_invalid_reason(403, upstream_message))
|
||||
@@ -1848,12 +1876,19 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_remove_structured_reason_keeps_oauth_expired_token_invalid() {
|
||||
assert!(!should_auto_remove_structured_reason(Some(
|
||||
fn auto_remove_structured_reason_removes_oauth_token_invalidated() {
|
||||
assert!(should_auto_remove_structured_reason(Some(
|
||||
"[OAUTH_EXPIRED] token invalidated"
|
||||
)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_remove_structured_reason_keeps_oauth_token_expired() {
|
||||
assert!(!should_auto_remove_structured_reason(Some(
|
||||
"[OAUTH_EXPIRED] session expired"
|
||||
)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_remove_refresh_failed_after_access_token_expiry() {
|
||||
let mut key = StoredProviderCatalogKey::new(
|
||||
@@ -1945,7 +1980,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oauth_token_invalid_is_not_auto_remove_proof_by_itself() {
|
||||
fn oauth_token_invalid_is_auto_remove_proof_by_itself() {
|
||||
let mut key = StoredProviderCatalogKey::new(
|
||||
"key-1".to_string(),
|
||||
"provider-1".to_string(),
|
||||
@@ -1958,7 +1993,7 @@ mod tests {
|
||||
key.expires_at_unix_secs = Some(1_000);
|
||||
key.oauth_invalid_reason = Some("oauth_token_invalid".to_string());
|
||||
|
||||
assert!(!super::should_auto_remove_oauth_invalid_key(
|
||||
assert!(super::should_auto_remove_oauth_invalid_key(
|
||||
&key,
|
||||
Some("oauth_token_invalid"),
|
||||
false,
|
||||
|
||||
@@ -26,6 +26,10 @@ const ACCOUNT_BLOCK_REASON_KEYWORDS: &[&str] = &[
|
||||
"账户访问被禁止",
|
||||
"访问受限",
|
||||
"账户访问受限",
|
||||
"oauth_token_invalid",
|
||||
"oauth_token_expired",
|
||||
"token_invalidated",
|
||||
"session expired",
|
||||
"authentication token has been invalidated",
|
||||
"token has been invalidated",
|
||||
"codex token 无效或已过期",
|
||||
@@ -43,6 +47,7 @@ const AUTO_REMOVABLE_ACCOUNT_STATE_CODES: &[&str] = &[
|
||||
"account_quarantined",
|
||||
"workspace_deactivated",
|
||||
"account_forbidden",
|
||||
"oauth_token_invalid",
|
||||
];
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
@@ -122,8 +127,79 @@ fn looks_like_account_verification(reason: &str) -> bool {
|
||||
.any(|keyword| lowered.contains(keyword))
|
||||
}
|
||||
|
||||
pub fn oauth_token_reason_is_expired(reason: &str) -> bool {
|
||||
let lowered = reason.trim().to_ascii_lowercase();
|
||||
!lowered.is_empty()
|
||||
&& [
|
||||
"oauth_token_expired",
|
||||
"session has expired",
|
||||
"session expired",
|
||||
"access token expired",
|
||||
"expired access token",
|
||||
"token expired",
|
||||
"token has expired",
|
||||
"security token included in the request is expired",
|
||||
"已过期",
|
||||
"过期",
|
||||
]
|
||||
.iter()
|
||||
.any(|keyword| lowered.contains(keyword))
|
||||
}
|
||||
|
||||
pub fn oauth_token_reason_is_hard_invalid(reason: &str) -> bool {
|
||||
let lowered = reason.trim().to_ascii_lowercase();
|
||||
if lowered.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if [
|
||||
"oauth_token_invalid",
|
||||
"token_invalidated",
|
||||
"authentication token has been invalidated",
|
||||
"token has been invalidated",
|
||||
"token invalidated",
|
||||
"invalidated",
|
||||
"revoked",
|
||||
"已撤销",
|
||||
"被撤销",
|
||||
"撤销",
|
||||
"已作废",
|
||||
"作废",
|
||||
"已失效",
|
||||
"token 失效",
|
||||
"令牌失效",
|
||||
]
|
||||
.iter()
|
||||
.any(|keyword| lowered.contains(keyword))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
(lowered.contains("token 无效") || lowered.contains("令牌无效"))
|
||||
&& !oauth_token_reason_is_expired(reason)
|
||||
}
|
||||
|
||||
pub fn oauth_token_account_status_parts(reason: &str) -> (&'static str, &'static str) {
|
||||
if oauth_token_reason_is_hard_invalid(reason) {
|
||||
("oauth_token_invalid", "Token 失效")
|
||||
} else {
|
||||
("oauth_token_expired", "Token 过期")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn oauth_token_snapshot_status_parts(reason: &str) -> (&'static str, &'static str) {
|
||||
if oauth_token_reason_is_hard_invalid(reason) {
|
||||
("invalid", "已失效")
|
||||
} else {
|
||||
("expired", "已过期")
|
||||
}
|
||||
}
|
||||
|
||||
fn classify_block_reason(reason: &str) -> (&'static str, &'static str) {
|
||||
let lowered = reason.to_ascii_lowercase();
|
||||
if oauth_token_reason_is_hard_invalid(reason) || oauth_token_reason_is_expired(reason) {
|
||||
return oauth_token_account_status_parts(reason);
|
||||
}
|
||||
if [
|
||||
"authentication token has been invalidated",
|
||||
"token has been invalidated",
|
||||
@@ -352,17 +428,18 @@ fn resolve_from_oauth_invalid_reason(reason: Option<&str>) -> Option<PoolAccount
|
||||
}
|
||||
if let Some(cleaned) = tagged_reason(&text, "OAUTH_EXPIRED") {
|
||||
let reason = if cleaned.is_empty() {
|
||||
"OAuth Token 已过期且无法续期".to_string()
|
||||
"OAuth Token 已过期".to_string()
|
||||
} else {
|
||||
cleaned
|
||||
};
|
||||
let (code, label) = oauth_token_account_status_parts(&reason);
|
||||
return Some(PoolAccountState {
|
||||
blocked: true,
|
||||
code: Some("oauth_token_invalid".to_string()),
|
||||
label: Some("Token 失效".to_string()),
|
||||
code: Some(code.to_string()),
|
||||
label: Some(label.to_string()),
|
||||
reason: Some(reason),
|
||||
source: Some("oauth_invalid".to_string()),
|
||||
recoverable: false,
|
||||
recoverable: code == "oauth_token_expired",
|
||||
});
|
||||
}
|
||||
if let Some(cleaned) = tagged_reason(&text, "REQUEST_FAILED") {
|
||||
@@ -456,17 +533,18 @@ pub fn resolve_account_status_snapshot(
|
||||
|
||||
if let Some(cleaned) = tagged_reason(&text, "OAUTH_EXPIRED") {
|
||||
let reason = if cleaned.is_empty() {
|
||||
"OAuth Token 已过期且无法续期".to_string()
|
||||
"OAuth Token 已过期".to_string()
|
||||
} else {
|
||||
cleaned
|
||||
};
|
||||
let (code, label) = oauth_token_account_status_parts(&reason);
|
||||
return AccountStatusSnapshot {
|
||||
code: "oauth_token_invalid".to_string(),
|
||||
label: Some("Token 失效".to_string()),
|
||||
code: code.to_string(),
|
||||
label: Some(label.to_string()),
|
||||
reason: Some(reason),
|
||||
blocked: true,
|
||||
source: Some("oauth_invalid".to_string()),
|
||||
recoverable: false,
|
||||
recoverable: code == "oauth_token_expired",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -631,11 +709,11 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn account_snapshot_marks_oauth_expired_as_token_invalid() {
|
||||
fn account_snapshot_marks_oauth_invalidated_as_token_invalid() {
|
||||
let snapshot = resolve_account_status_snapshot(
|
||||
Some("codex"),
|
||||
None,
|
||||
Some("[OAUTH_EXPIRED] Codex Token 无效或已过期 (401)"),
|
||||
Some("[OAUTH_EXPIRED] Your authentication token has been invalidated. Please try signing in again."),
|
||||
);
|
||||
|
||||
assert_eq!(snapshot.code, "oauth_token_invalid");
|
||||
@@ -645,7 +723,21 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oauth_expired_state_is_not_auto_removed() {
|
||||
fn account_snapshot_marks_oauth_expired_as_token_expired() {
|
||||
let snapshot = resolve_account_status_snapshot(
|
||||
Some("codex"),
|
||||
None,
|
||||
Some("[OAUTH_EXPIRED] session expired"),
|
||||
);
|
||||
|
||||
assert_eq!(snapshot.code, "oauth_token_expired");
|
||||
assert_eq!(snapshot.label.as_deref(), Some("Token 过期"));
|
||||
assert!(snapshot.blocked);
|
||||
assert!(snapshot.recoverable);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oauth_invalidated_state_is_auto_removed() {
|
||||
let state = resolve_pool_account_state(
|
||||
Some("codex"),
|
||||
None,
|
||||
@@ -654,6 +746,19 @@ mod tests {
|
||||
|
||||
assert!(state.blocked);
|
||||
assert_eq!(state.code.as_deref(), Some("oauth_token_invalid"));
|
||||
assert!(should_auto_remove_account_state(&state));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oauth_expired_state_is_not_auto_removed() {
|
||||
let state = resolve_pool_account_state(
|
||||
Some("codex"),
|
||||
None,
|
||||
Some("[OAUTH_EXPIRED] session expired"),
|
||||
);
|
||||
|
||||
assert!(state.blocked);
|
||||
assert_eq!(state.code.as_deref(), Some("oauth_token_expired"));
|
||||
assert!(!should_auto_remove_account_state(&state));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user