mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
fix(public): 修复用户可见性、额度、验证与 Codex 探测
This commit is contained in:
@@ -314,6 +314,133 @@ pub fn parse_codex_wham_usage_response(
|
||||
Some(serde_json::Value::Object(result))
|
||||
}
|
||||
|
||||
fn codex_json_object<'a>(
|
||||
root: &'a serde_json::Map<String, serde_json::Value>,
|
||||
keys: &[&str],
|
||||
) -> Option<&'a serde_json::Map<String, serde_json::Value>> {
|
||||
keys.iter()
|
||||
.find_map(|key| root.get(*key).and_then(serde_json::Value::as_object))
|
||||
}
|
||||
|
||||
fn codex_json_string_from_object(
|
||||
object: Option<&serde_json::Map<String, serde_json::Value>>,
|
||||
keys: &[&str],
|
||||
) -> Option<String> {
|
||||
let object = object?;
|
||||
keys.iter()
|
||||
.find_map(|key| coerce_json_string(object.get(*key)))
|
||||
}
|
||||
|
||||
fn codex_json_string_from_root(
|
||||
root: &serde_json::Map<String, serde_json::Value>,
|
||||
keys: &[&str],
|
||||
) -> Option<String> {
|
||||
keys.iter()
|
||||
.find_map(|key| coerce_json_string(root.get(*key)))
|
||||
}
|
||||
|
||||
fn codex_backend_me_account_object(
|
||||
root: &serde_json::Map<String, serde_json::Value>,
|
||||
) -> Option<&serde_json::Map<String, serde_json::Value>> {
|
||||
codex_json_object(root, &["account", "current_account", "selected_account"])
|
||||
.or_else(|| {
|
||||
root.get("accounts")
|
||||
.and_then(serde_json::Value::as_array)?
|
||||
.iter()
|
||||
.filter_map(serde_json::Value::as_object)
|
||||
.find(|account| {
|
||||
account
|
||||
.get("is_default")
|
||||
.or_else(|| account.get("selected"))
|
||||
.or_else(|| account.get("current"))
|
||||
.and_then(coerce_json_bool)
|
||||
.unwrap_or(false)
|
||||
})
|
||||
})
|
||||
.or_else(|| {
|
||||
root.get("accounts")
|
||||
.and_then(serde_json::Value::as_array)?
|
||||
.iter()
|
||||
.find_map(serde_json::Value::as_object)
|
||||
})
|
||||
}
|
||||
|
||||
fn codex_backend_me_plan_object<'a>(
|
||||
root: &'a serde_json::Map<String, serde_json::Value>,
|
||||
account: Option<&'a serde_json::Map<String, serde_json::Value>>,
|
||||
) -> Option<&'a serde_json::Map<String, serde_json::Value>> {
|
||||
codex_json_object(root, &["plan", "subscription", "workspace_plan"]).or_else(|| {
|
||||
account
|
||||
.and_then(|account| account.get("plan"))
|
||||
.and_then(serde_json::Value::as_object)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_codex_backend_me_response(
|
||||
value: &serde_json::Value,
|
||||
updated_at_unix_secs: u64,
|
||||
) -> Option<serde_json::Value> {
|
||||
let root = value.as_object()?;
|
||||
if root.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let user = codex_json_object(root, &["user", "auth_user", "profile"]);
|
||||
let account = codex_backend_me_account_object(root);
|
||||
let plan = codex_backend_me_plan_object(root, account);
|
||||
let mut result = serde_json::Map::new();
|
||||
|
||||
if let Some(user_id) = codex_json_string_from_object(user, &["id", "user_id"])
|
||||
.or_else(|| codex_json_string_from_root(root, &["user_id"]))
|
||||
{
|
||||
result.insert("user_id".to_string(), json!(user_id));
|
||||
}
|
||||
if let Some(email) = codex_json_string_from_object(user, &["email"])
|
||||
.or_else(|| codex_json_string_from_root(root, &["email"]))
|
||||
{
|
||||
result.insert("email".to_string(), json!(email));
|
||||
}
|
||||
if let Some(name) = codex_json_string_from_object(user, &["name", "display_name", "full_name"])
|
||||
.or_else(|| codex_json_string_from_root(root, &["name", "display_name", "full_name"]))
|
||||
{
|
||||
result.insert("user_name".to_string(), json!(name));
|
||||
}
|
||||
if let Some(account_id) =
|
||||
codex_json_string_from_object(account, &["id", "account_id", "accountId", "workspace_id"])
|
||||
.or_else(|| {
|
||||
codex_json_string_from_root(root, &["account_id", "accountId", "workspace_id"])
|
||||
})
|
||||
{
|
||||
result.insert("account_id".to_string(), json!(account_id));
|
||||
}
|
||||
if let Some(account_name) =
|
||||
codex_json_string_from_object(account, &["name", "title", "display_name"])
|
||||
{
|
||||
result.insert("account_name".to_string(), json!(account_name));
|
||||
}
|
||||
|
||||
let plan_type = codex_json_string_from_object(
|
||||
account,
|
||||
&["plan_type", "planType", "subscription_plan", "tier"],
|
||||
)
|
||||
.or_else(|| codex_json_string_from_object(plan, &["type", "plan_type", "name", "tier"]))
|
||||
.or_else(|| codex_json_string_from_root(root, &["plan_type", "planType"]));
|
||||
if let Some(plan_type) = normalize_codex_plan_type(plan_type.as_deref()) {
|
||||
result.insert("plan_type".to_string(), json!(plan_type));
|
||||
}
|
||||
if let Some(plan_title) =
|
||||
codex_json_string_from_object(plan, &["title", "display_name", "label"])
|
||||
{
|
||||
result.insert("plan_title".to_string(), json!(plan_title));
|
||||
}
|
||||
|
||||
if result.is_empty() {
|
||||
return None;
|
||||
}
|
||||
result.insert("updated_at".to_string(), json!(updated_at_unix_secs));
|
||||
Some(serde_json::Value::Object(result))
|
||||
}
|
||||
|
||||
pub fn parse_codex_usage_headers(
|
||||
headers: &BTreeMap<String, String>,
|
||||
updated_at_unix_secs: u64,
|
||||
@@ -512,6 +639,14 @@ pub fn codex_structured_invalid_reason(status_code: u16, upstream_message: Optio
|
||||
};
|
||||
return format!("{OAUTH_ACCOUNT_BLOCK_PREFIX}{detail}");
|
||||
}
|
||||
if status_code == 402 {
|
||||
let detail = if message.is_empty() {
|
||||
"Codex 账户需要付款 (402)"
|
||||
} else {
|
||||
message
|
||||
};
|
||||
return format!("{OAUTH_ACCOUNT_BLOCK_PREFIX}{detail}");
|
||||
}
|
||||
message.to_string()
|
||||
}
|
||||
|
||||
@@ -521,9 +656,7 @@ pub fn codex_runtime_invalid_reason(
|
||||
) -> Option<String> {
|
||||
match status_code {
|
||||
401 => Some(codex_structured_invalid_reason(401, upstream_message)),
|
||||
402 if codex_looks_like_workspace_deactivated(upstream_message) => {
|
||||
Some(codex_structured_invalid_reason(402, upstream_message))
|
||||
}
|
||||
402 => Some(codex_structured_invalid_reason(402, upstream_message)),
|
||||
403 if codex_looks_like_token_invalidated(upstream_message)
|
||||
|| codex_looks_like_account_deactivated(upstream_message) =>
|
||||
{
|
||||
@@ -913,9 +1046,9 @@ pub fn parse_chatgpt_web_conversation_init_response(
|
||||
mod tests {
|
||||
use super::{
|
||||
codex_build_invalid_state, codex_runtime_invalid_reason,
|
||||
parse_chatgpt_web_conversation_init_response, parse_codex_wham_usage_response,
|
||||
OAUTH_ACCOUNT_BLOCK_PREFIX, OAUTH_EXPIRED_PREFIX, OAUTH_REFRESH_FAILED_PREFIX,
|
||||
OAUTH_REQUEST_FAILED_PREFIX,
|
||||
parse_chatgpt_web_conversation_init_response, parse_codex_backend_me_response,
|
||||
parse_codex_wham_usage_response, OAUTH_ACCOUNT_BLOCK_PREFIX, OAUTH_EXPIRED_PREFIX,
|
||||
OAUTH_REFRESH_FAILED_PREFIX, OAUTH_REQUEST_FAILED_PREFIX,
|
||||
};
|
||||
use aether_data_contracts::repository::provider_catalog::StoredProviderCatalogKey;
|
||||
use serde_json::json;
|
||||
@@ -938,6 +1071,14 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_runtime_invalid_reason_marks_402_as_account_blocked() {
|
||||
assert_eq!(
|
||||
codex_runtime_invalid_reason(402, Some("payment required")),
|
||||
Some(format!("{OAUTH_ACCOUNT_BLOCK_PREFIX}payment required"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_runtime_invalid_reason_ignores_generic_403() {
|
||||
assert_eq!(codex_runtime_invalid_reason(403, Some("forbidden")), None);
|
||||
@@ -1067,6 +1208,40 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_codex_backend_me_identity_metadata_without_quota_windows() {
|
||||
let parsed = parse_codex_backend_me_response(
|
||||
&json!({
|
||||
"user": {
|
||||
"id": "user-codex-123",
|
||||
"email": "codex@example.com",
|
||||
"name": "Codex User"
|
||||
},
|
||||
"account": {
|
||||
"id": "acct-codex-123",
|
||||
"name": "Personal",
|
||||
"plan_type": "plus"
|
||||
},
|
||||
"plan": {
|
||||
"type": "Plus",
|
||||
"title": "ChatGPT Plus"
|
||||
}
|
||||
}),
|
||||
1_777_000_000,
|
||||
)
|
||||
.expect("codex backend me should parse");
|
||||
|
||||
assert_eq!(parsed.get("user_id"), Some(&json!("user-codex-123")));
|
||||
assert_eq!(parsed.get("email"), Some(&json!("codex@example.com")));
|
||||
assert_eq!(parsed.get("account_id"), Some(&json!("acct-codex-123")));
|
||||
assert_eq!(parsed.get("account_name"), Some(&json!("Personal")));
|
||||
assert_eq!(parsed.get("plan_type"), Some(&json!("plus")));
|
||||
assert_eq!(parsed.get("plan_title"), Some(&json!("ChatGPT Plus")));
|
||||
assert_eq!(parsed.get("updated_at"), Some(&json!(1_777_000_000u64)));
|
||||
assert!(parsed.get("primary_used_percent").is_none());
|
||||
assert!(parsed.get("secondary_used_percent").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_chatgpt_web_image_quota_from_conversation_init() {
|
||||
let parsed = parse_chatgpt_web_conversation_init_response(
|
||||
|
||||
@@ -667,7 +667,7 @@ struct AdminApiFormatDefinition {
|
||||
|
||||
const REQUEST_RECORD_LEVEL_KEY: &str = "request_record_level";
|
||||
const LEGACY_REQUEST_LOG_LEVEL_KEY: &str = "request_log_level";
|
||||
const SENSITIVE_SYSTEM_CONFIG_KEYS: &[&str] = &["smtp_password"];
|
||||
const SENSITIVE_SYSTEM_CONFIG_KEYS: &[&str] = &["smtp_password", "turnstile_secret_key"];
|
||||
const ADMIN_API_FORMAT_DEFINITIONS: &[AdminApiFormatDefinition] = &[
|
||||
AdminApiFormatDefinition {
|
||||
value: "openai:chat",
|
||||
@@ -1492,6 +1492,9 @@ pub fn admin_system_config_default_value(key: &str) -> Option<serde_json::Value>
|
||||
"auto_delete_expired_keys" => Some(json!(false)),
|
||||
"email_suffix_mode" => Some(json!("none")),
|
||||
"email_suffix_list" => Some(json!([])),
|
||||
"turnstile_enabled" => Some(json!(false)),
|
||||
"turnstile_site_key" => Some(json!("")),
|
||||
"turnstile_allowed_hostnames" => Some(json!([])),
|
||||
"enable_format_conversion" => Some(json!(false)),
|
||||
"enable_model_directives" => Some(json!(false)),
|
||||
"model_directives" => Some(json!({
|
||||
@@ -2821,6 +2824,7 @@ mod tests {
|
||||
fn sensitive_admin_system_config_keys_are_case_insensitive() {
|
||||
assert!(is_sensitive_admin_system_config_key("smtp_password"));
|
||||
assert!(is_sensitive_admin_system_config_key("SMTP_PASSWORD"));
|
||||
assert!(is_sensitive_admin_system_config_key("turnstile_secret_key"));
|
||||
assert!(!is_sensitive_admin_system_config_key("site_name"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user