mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-05 00:47:48 +08:00
feat(gateway): add reversible chat pii redaction
This commit is contained in:
@@ -191,6 +191,7 @@ pub(super) async fn execute_provider_quota_plan(
|
||||
let error = match err {
|
||||
GatewayError::UpstreamUnavailable { message, .. }
|
||||
| GatewayError::ControlUnavailable { message, .. }
|
||||
| GatewayError::Client { message, .. }
|
||||
| GatewayError::Internal(message) => message,
|
||||
};
|
||||
let proxy_node_id = plan
|
||||
|
||||
@@ -304,6 +304,7 @@ fn admin_provider_ops_gateway_error_message(error: GatewayError) -> String {
|
||||
match error {
|
||||
GatewayError::UpstreamUnavailable { message, .. }
|
||||
| GatewayError::ControlUnavailable { message, .. }
|
||||
| GatewayError::Client { message, .. }
|
||||
| GatewayError::Internal(message) => message,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,6 +176,7 @@ pub(crate) fn build_admin_provider_summary_value(
|
||||
"claude_code_advanced": config.and_then(|cfg| cfg.get("claude_code_advanced")).cloned(),
|
||||
"pool_advanced": config.and_then(|cfg| cfg.get("pool_advanced")).cloned(),
|
||||
"failover_rules": config.and_then(|cfg| cfg.get("failover_rules")).cloned(),
|
||||
"chat_pii_redaction": config.and_then(|cfg| cfg.get("chat_pii_redaction")).cloned(),
|
||||
"total_endpoints": total_endpoints,
|
||||
"active_endpoints": active_endpoints,
|
||||
"total_keys": total_keys,
|
||||
|
||||
@@ -129,6 +129,28 @@ pub(crate) fn normalize_pool_advanced_config(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn normalize_chat_pii_redaction_config(
|
||||
value: Option<serde_json::Value>,
|
||||
) -> Result<Option<serde_json::Value>, String> {
|
||||
let Some(value) = value else {
|
||||
return Ok(None);
|
||||
};
|
||||
match value {
|
||||
serde_json::Value::Null => Ok(None),
|
||||
serde_json::Value::Object(mut map) => {
|
||||
if map.len() != 1 || !map.contains_key("enabled") {
|
||||
return Err("chat_pii_redaction 仅支持 enabled 布尔配置".to_string());
|
||||
}
|
||||
let enabled = map
|
||||
.remove("enabled")
|
||||
.and_then(|value| value.as_bool())
|
||||
.ok_or_else(|| "chat_pii_redaction.enabled 必须是布尔值".to_string())?;
|
||||
Ok(Some(serde_json::json!({ "enabled": enabled })))
|
||||
}
|
||||
_ => Err("chat_pii_redaction 必须是 JSON 对象".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn validate_vertex_api_formats(
|
||||
provider_type: &str,
|
||||
auth_type: &str,
|
||||
@@ -177,7 +199,8 @@ mod tests {
|
||||
use super::{
|
||||
normalize_allow_auth_channel_mismatch_formats, normalize_api_format_json_object_keys,
|
||||
normalize_api_format_list, normalize_auth_type, normalize_auth_type_by_format,
|
||||
normalize_pool_advanced_config, normalize_provider_type_input, validate_vertex_api_formats,
|
||||
normalize_chat_pii_redaction_config, normalize_pool_advanced_config,
|
||||
normalize_provider_type_input, validate_vertex_api_formats,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
@@ -201,6 +224,26 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_chat_pii_redaction_requires_enabled_boolean_only() {
|
||||
assert_eq!(
|
||||
normalize_chat_pii_redaction_config(Some(json!({ "enabled": true })))
|
||||
.expect("chat pii redaction should normalize"),
|
||||
Some(json!({ "enabled": true }))
|
||||
);
|
||||
assert_eq!(
|
||||
normalize_chat_pii_redaction_config(Some(
|
||||
json!({ "enabled": true, "entities": ["email"] })
|
||||
))
|
||||
.unwrap_err(),
|
||||
"chat_pii_redaction 仅支持 enabled 布尔配置"
|
||||
);
|
||||
assert_eq!(
|
||||
normalize_chat_pii_redaction_config(Some(json!({ "enabled": "yes" }))).unwrap_err(),
|
||||
"chat_pii_redaction.enabled 必须是布尔值"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_auth_type_supports_bearer() {
|
||||
assert_eq!(
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::handlers::admin::provider::shared::payloads::AdminProviderCreateReque
|
||||
use crate::handlers::admin::provider::shared::support::{
|
||||
normalize_provider_billing_type, parse_optional_rfc3339_unix_secs,
|
||||
};
|
||||
use crate::handlers::admin::provider::write::normalize::normalize_chat_pii_redaction_config;
|
||||
use crate::handlers::admin::provider::write::normalize::normalize_pool_advanced_config;
|
||||
use crate::handlers::admin::provider::write::normalize::normalize_provider_type_input;
|
||||
use crate::handlers::admin::request::AdminAppState;
|
||||
@@ -134,6 +135,12 @@ pub(crate) async fn build_admin_create_provider_record(
|
||||
}
|
||||
config_map.insert("claude_code_advanced".to_string(), value);
|
||||
}
|
||||
if config_map.contains_key("chat_pii_redaction") {
|
||||
let value = normalize_chat_pii_redaction_config(config_map.remove("chat_pii_redaction"))?;
|
||||
if let Some(value) = value {
|
||||
config_map.insert("chat_pii_redaction".to_string(), value);
|
||||
}
|
||||
}
|
||||
let config = (!config_map.is_empty()).then_some(serde_json::Value::Object(config_map));
|
||||
|
||||
let now_unix_secs = SystemTime::now()
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::handlers::admin::provider::shared::payloads::AdminProviderUpdatePatch
|
||||
use crate::handlers::admin::provider::shared::support::{
|
||||
normalize_provider_billing_type, parse_optional_rfc3339_unix_secs,
|
||||
};
|
||||
use crate::handlers::admin::provider::write::normalize::normalize_chat_pii_redaction_config;
|
||||
use crate::handlers::admin::provider::write::normalize::normalize_pool_advanced_config;
|
||||
use crate::handlers::admin::provider::write::normalize::normalize_provider_type_input;
|
||||
use crate::handlers::admin::request::AdminAppState;
|
||||
@@ -225,14 +226,29 @@ pub(crate) async fn build_admin_update_provider_record(
|
||||
updated.enable_format_conversion = enable_format_conversion;
|
||||
}
|
||||
|
||||
let config_seed = if fields.contains("config") {
|
||||
normalize_json_object(payload.config, "config")?
|
||||
} else {
|
||||
updated.config.clone()
|
||||
};
|
||||
let mut config_map = config_seed
|
||||
let mut config_map = updated
|
||||
.config
|
||||
.clone()
|
||||
.and_then(|value| value.as_object().cloned())
|
||||
.unwrap_or_default();
|
||||
if fields.contains("config") {
|
||||
if fields.is_null("config") {
|
||||
config_map.clear();
|
||||
} else {
|
||||
let value = normalize_json_object(payload.config, "config")?
|
||||
.ok_or_else(|| "config 必须是 JSON 对象".to_string())?;
|
||||
let serde_json::Value::Object(patch_map) = value else {
|
||||
return Err("config 必须是 JSON 对象".to_string());
|
||||
};
|
||||
for (key, value) in patch_map {
|
||||
if value.is_null() {
|
||||
config_map.remove(&key);
|
||||
} else {
|
||||
config_map.insert(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if fields.contains("claude_code_advanced") {
|
||||
if fields.is_null("claude_code_advanced") {
|
||||
@@ -270,6 +286,13 @@ pub(crate) async fn build_admin_update_provider_record(
|
||||
}
|
||||
}
|
||||
|
||||
if config_map.contains_key("chat_pii_redaction") {
|
||||
let value = normalize_chat_pii_redaction_config(config_map.remove("chat_pii_redaction"))?;
|
||||
if let Some(value) = value {
|
||||
config_map.insert("chat_pii_redaction".to_string(), value);
|
||||
}
|
||||
}
|
||||
|
||||
updated.config = (!config_map.is_empty()).then_some(serde_json::Value::Object(config_map));
|
||||
updated.updated_at_unix_secs = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
|
||||
@@ -666,6 +666,7 @@ fn admin_provider_oauth_gateway_error_message(error: GatewayError) -> String {
|
||||
match error {
|
||||
GatewayError::UpstreamUnavailable { message, .. }
|
||||
| GatewayError::ControlUnavailable { message, .. }
|
||||
| GatewayError::Client { message, .. }
|
||||
| GatewayError::Internal(message) => message,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,18 @@ pub(crate) const ADMIN_MODULE_DEFINITIONS: &[AdminModuleDefinition] = &[
|
||||
admin_menu_group: None,
|
||||
admin_menu_order: 0,
|
||||
},
|
||||
AdminModuleDefinition {
|
||||
name: "chat_pii_redaction",
|
||||
display_name: "敏感信息替换保护",
|
||||
description: "发送给供应商前将聊天消息中的敏感信息替换为占位符,返回客户端前自动还原。",
|
||||
category: "security",
|
||||
env_key: "CHAT_PII_REDACTION_AVAILABLE",
|
||||
default_available: true,
|
||||
admin_route: Some("/admin/modules/chat-pii-redaction"),
|
||||
admin_menu_icon: Some("ShieldCheck"),
|
||||
admin_menu_group: Some("system"),
|
||||
admin_menu_order: 59,
|
||||
},
|
||||
AdminModuleDefinition {
|
||||
name: "notification_email",
|
||||
display_name: "异常通知",
|
||||
|
||||
Reference in New Issue
Block a user