mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 01:40:21 +08:00
Merge branch 'pr-503'
# Conflicts: # apps/aether-gateway/src/handlers/admin/provider/summary/value.rs # apps/aether-gateway/src/lib.rs # apps/aether-gateway/src/maintenance/mod.rs # apps/aether-gateway/src/maintenance/runtime/workers.rs # frontend/src/api/endpoints/types/provider.ts
This commit is contained in:
@@ -797,22 +797,13 @@ pub fn admin_provider_ops_sub2api_verify_payload(
|
||||
}
|
||||
}
|
||||
|
||||
let username_or_email = admin_provider_ops_sub2api_non_empty_string(user_data, "username")
|
||||
.or_else(|| admin_provider_ops_sub2api_non_empty_string(user_data, "email"));
|
||||
admin_provider_ops_verify_success(
|
||||
admin_provider_ops_verify_user_payload(
|
||||
user_data
|
||||
.get("username")
|
||||
.or_else(|| user_data.get("email"))
|
||||
.and_then(Value::as_str)
|
||||
.map(ToOwned::to_owned),
|
||||
user_data
|
||||
.get("username")
|
||||
.or_else(|| user_data.get("email"))
|
||||
.and_then(Value::as_str)
|
||||
.map(ToOwned::to_owned),
|
||||
user_data
|
||||
.get("email")
|
||||
.and_then(Value::as_str)
|
||||
.map(ToOwned::to_owned),
|
||||
username_or_email.clone(),
|
||||
username_or_email,
|
||||
admin_provider_ops_sub2api_non_empty_string(user_data, "email"),
|
||||
Some(balance + points),
|
||||
Some(extra),
|
||||
),
|
||||
@@ -820,6 +811,17 @@ pub fn admin_provider_ops_sub2api_verify_payload(
|
||||
)
|
||||
}
|
||||
|
||||
fn admin_provider_ops_sub2api_non_empty_string(
|
||||
map: &Map<String, Value>,
|
||||
key: &str,
|
||||
) -> Option<String> {
|
||||
map.get(key)
|
||||
.and_then(Value::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(ToOwned::to_owned)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
@@ -913,6 +915,28 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub2api_verify_payload_falls_back_to_email_when_username_is_null() {
|
||||
let payload = admin_provider_ops_sub2api_verify_payload(
|
||||
StatusCode::OK,
|
||||
&json!({
|
||||
"code": 0,
|
||||
"data": {
|
||||
"username": null,
|
||||
"email": "user@example.com",
|
||||
"balance": 2.0,
|
||||
"points": 0.0
|
||||
}
|
||||
}),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(payload["success"], json!(true));
|
||||
assert_eq!(payload["data"]["username"], json!("user@example.com"));
|
||||
assert_eq!(payload["data"]["display_name"], json!("user@example.com"));
|
||||
assert_eq!(payload["data"]["email"], json!("user@example.com"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn anyrouter_verify_payload_uses_cookie_auth_messages_and_usage_fields() {
|
||||
let payload = admin_provider_ops_anyrouter_verify_payload(
|
||||
|
||||
@@ -667,7 +667,11 @@ 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", "turnstile_secret_key"];
|
||||
const SENSITIVE_SYSTEM_CONFIG_KEYS: &[&str] = &[
|
||||
"smtp_password",
|
||||
"turnstile_secret_key",
|
||||
"module.important_notification.server_chan_send_key",
|
||||
];
|
||||
const ADMIN_API_FORMAT_DEFINITIONS: &[AdminApiFormatDefinition] = &[
|
||||
AdminApiFormatDefinition {
|
||||
value: "openai:chat",
|
||||
@@ -1162,7 +1166,7 @@ pub fn build_admin_module_validation_result(
|
||||
oauth_providers: &[StoredOAuthProviderModuleConfig],
|
||||
ldap_config: Option<&StoredLdapModuleConfig>,
|
||||
gemini_files_has_capable_key: bool,
|
||||
smtp_configured: bool,
|
||||
important_notification_configured: bool,
|
||||
) -> (bool, Option<String>) {
|
||||
match module_name {
|
||||
"oauth" => {
|
||||
@@ -1233,11 +1237,11 @@ pub fn build_admin_module_validation_result(
|
||||
}
|
||||
(true, None)
|
||||
}
|
||||
"notification_email" => {
|
||||
if smtp_configured {
|
||||
"important_notification" | "notification_email" => {
|
||||
if important_notification_configured {
|
||||
(true, None)
|
||||
} else {
|
||||
(false, Some("请先完成邮件配置(SMTP)".to_string()))
|
||||
(false, Some("请先完成重要通知通道配置".to_string()))
|
||||
}
|
||||
}
|
||||
"gemini_files" => {
|
||||
@@ -1260,7 +1264,9 @@ pub fn build_admin_module_health(
|
||||
gemini_files_has_capable_key: bool,
|
||||
) -> &'static str {
|
||||
match module_name {
|
||||
"management_tokens" | "model_directives" | "proxy_nodes" => "healthy",
|
||||
"management_tokens" | "model_directives" | "proxy_nodes" | "important_notification" => {
|
||||
"healthy"
|
||||
}
|
||||
"gemini_files" => {
|
||||
if gemini_files_has_capable_key {
|
||||
"healthy"
|
||||
@@ -1437,6 +1443,8 @@ pub fn normalize_admin_system_config_key(requested_key: &str) -> String {
|
||||
let trimmed = requested_key.trim();
|
||||
if trimmed.eq_ignore_ascii_case(LEGACY_REQUEST_LOG_LEVEL_KEY) {
|
||||
REQUEST_RECORD_LEVEL_KEY.to_string()
|
||||
} else if trimmed.eq_ignore_ascii_case("module.notification_email.enabled") {
|
||||
"module.important_notification.enabled".to_string()
|
||||
} else {
|
||||
trimmed.to_string()
|
||||
}
|
||||
@@ -1449,6 +1457,11 @@ pub fn admin_system_config_delete_keys(requested_key: &str) -> Vec<String> {
|
||||
REQUEST_RECORD_LEVEL_KEY.to_string(),
|
||||
LEGACY_REQUEST_LOG_LEVEL_KEY.to_string(),
|
||||
]
|
||||
} else if normalized == "module.important_notification.enabled" {
|
||||
vec![
|
||||
"module.important_notification.enabled".to_string(),
|
||||
"module.notification_email.enabled".to_string(),
|
||||
]
|
||||
} else {
|
||||
vec![normalized]
|
||||
}
|
||||
@@ -1570,6 +1583,12 @@ pub fn admin_system_config_default_value(key: &str) -> Option<serde_json::Value>
|
||||
"smtp_from_email" => Some(serde_json::Value::Null),
|
||||
"smtp_from_name" => Some(json!("Aether")),
|
||||
"enable_oauth_token_refresh" => Some(json!(true)),
|
||||
"module.important_notification.enabled" => Some(json!(false)),
|
||||
"module.important_notification.email_enabled" => Some(json!(false)),
|
||||
"module.important_notification.email_recipients" => Some(json!("")),
|
||||
"module.important_notification.server_chan_enabled" => Some(json!(false)),
|
||||
"module.important_notification.server_chan_send_key" => Some(serde_json::Value::Null),
|
||||
"module.important_notification.server_chan_template" => Some(json!("")),
|
||||
"module.chat_pii_redaction.enabled" => Some(json!(false)),
|
||||
"module.chat_pii_redaction.rules" => Some(chat_pii_redaction_default_rules()),
|
||||
"module.chat_pii_redaction.cache_ttl_seconds" => Some(json!(300)),
|
||||
@@ -1720,6 +1739,44 @@ fn normalize_chat_pii_redaction_rule_features(
|
||||
Ok(Value::Object(features))
|
||||
}
|
||||
|
||||
fn normalize_string_list_config_value(value: serde_json::Value) -> Result<serde_json::Value, ()> {
|
||||
match value {
|
||||
Value::Null => Ok(json!("")),
|
||||
Value::String(raw) => Ok(json!(raw.trim())),
|
||||
Value::Array(items) => {
|
||||
let mut normalized = Vec::with_capacity(items.len());
|
||||
for item in items {
|
||||
let Some(raw) = item.as_str() else {
|
||||
return Err(());
|
||||
};
|
||||
let raw = raw.trim();
|
||||
if !raw.is_empty() {
|
||||
normalized.push(raw.to_string());
|
||||
}
|
||||
}
|
||||
Ok(json!(normalized))
|
||||
}
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_nullable_string_config_value(
|
||||
value: serde_json::Value,
|
||||
) -> Result<serde_json::Value, ()> {
|
||||
match value {
|
||||
Value::Null => Ok(Value::Null),
|
||||
Value::String(raw) => {
|
||||
let raw = raw.trim();
|
||||
if raw.is_empty() {
|
||||
Ok(Value::Null)
|
||||
} else {
|
||||
Ok(json!(raw))
|
||||
}
|
||||
}
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_admin_system_config_update(
|
||||
requested_key: &str,
|
||||
request_body: &[u8],
|
||||
@@ -1773,6 +1830,48 @@ pub fn parse_admin_system_config_update(
|
||||
}
|
||||
|
||||
match normalized_key.as_str() {
|
||||
"module.important_notification.enabled"
|
||||
| "module.important_notification.email_enabled"
|
||||
| "module.important_notification.server_chan_enabled" => match value.as_bool() {
|
||||
Some(enabled) => value = json!(enabled),
|
||||
None if value.is_null() => {
|
||||
value = admin_system_config_default_value(&normalized_key).unwrap_or(json!(false));
|
||||
}
|
||||
None => {
|
||||
return Err((
|
||||
http::StatusCode::BAD_REQUEST,
|
||||
json!({ "detail": "请求数据验证失败" }),
|
||||
));
|
||||
}
|
||||
},
|
||||
"module.important_notification.email_recipients" => {
|
||||
value = normalize_string_list_config_value(value).map_err(|_| {
|
||||
(
|
||||
http::StatusCode::BAD_REQUEST,
|
||||
json!({ "detail": "请求数据验证失败" }),
|
||||
)
|
||||
})?;
|
||||
}
|
||||
"module.important_notification.server_chan_send_key" => {
|
||||
value = normalize_nullable_string_config_value(value).map_err(|_| {
|
||||
(
|
||||
http::StatusCode::BAD_REQUEST,
|
||||
json!({ "detail": "请求数据验证失败" }),
|
||||
)
|
||||
})?;
|
||||
}
|
||||
"module.important_notification.server_chan_template" => {
|
||||
value = match value {
|
||||
Value::Null => json!(""),
|
||||
Value::String(raw) => json!(raw),
|
||||
_ => {
|
||||
return Err((
|
||||
http::StatusCode::BAD_REQUEST,
|
||||
json!({ "detail": "请求数据验证失败" }),
|
||||
));
|
||||
}
|
||||
};
|
||||
}
|
||||
"module.chat_pii_redaction.enabled" => match value.as_bool() {
|
||||
Some(enabled) => value = json!(enabled),
|
||||
None if value.is_null() => {
|
||||
@@ -2829,9 +2928,27 @@ mod tests {
|
||||
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("TURNSTILE_SECRET_KEY"));
|
||||
assert!(is_sensitive_admin_system_config_key(
|
||||
"module.important_notification.server_chan_send_key"
|
||||
));
|
||||
assert!(!is_sensitive_admin_system_config_key("site_name"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_notification_email_config_key_normalizes_to_important_notification() {
|
||||
assert_eq!(
|
||||
normalize_admin_system_config_key("module.notification_email.enabled"),
|
||||
"module.important_notification.enabled"
|
||||
);
|
||||
assert_eq!(
|
||||
admin_system_config_delete_keys("module.important_notification.enabled"),
|
||||
vec![
|
||||
"module.important_notification.enabled".to_string(),
|
||||
"module.notification_email.enabled".to_string(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_admin_system_config_detail_masks_turnstile_secret_key() {
|
||||
let payload = build_admin_system_config_detail_payload(
|
||||
|
||||
Reference in New Issue
Block a user