feat: support api key ip restriction rules

This commit is contained in:
fawney19
2026-05-20 16:11:49 +08:00
parent b6bdc08267
commit f76bbaab52
55 changed files with 698 additions and 551 deletions
@@ -10,7 +10,7 @@ use crate::handlers::admin::system::shared::paths::{
admin_management_token_status_id_from_path, is_admin_management_tokens_root,
};
use crate::handlers::internal::build_management_token_payload;
use crate::handlers::shared::generate_gateway_secret_plaintext;
use crate::handlers::shared::{generate_gateway_secret_plaintext, parse_json_ip_rules};
use crate::{GatewayError, LocalMutationOutcome};
use aether_data::repository::management_tokens::{
CreateManagementTokenRecord, ManagementTokenListQuery, RegenerateManagementTokenSecret,
@@ -97,59 +97,10 @@ fn admin_management_token_prefix(value: &str) -> Option<String> {
.then(|| value[..value.len().min(ADMIN_MANAGEMENT_TOKEN_DISPLAY_PREFIX_LEN)].to_string())
}
fn admin_validate_ip_or_cidr(value: &str) -> bool {
let value = value.trim();
if value.is_empty() {
return false;
}
if value.parse::<std::net::IpAddr>().is_ok() {
return true;
}
let Some((host, prefix)) = value.split_once('/') else {
return false;
};
let Ok(ip) = host.trim().parse::<std::net::IpAddr>() else {
return false;
};
let Ok(prefix) = prefix.trim().parse::<u8>() else {
return false;
};
match ip {
std::net::IpAddr::V4(_) => prefix <= 32,
std::net::IpAddr::V6(_) => prefix <= 128,
}
}
fn admin_parse_management_token_allowed_ips(
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::Array(items) => {
if items.is_empty() {
return Err("IP 白名单不能为空列表,如需取消限制请不提供此字段".to_string());
}
let mut normalized = Vec::with_capacity(items.len());
for (index, item) in items.iter().enumerate() {
let Some(raw) = item.as_str() else {
return Err("IP 白名单必须是字符串数组".to_string());
};
let trimmed = raw.trim();
if trimmed.is_empty() {
return Err(format!("IP 白名单第 {} 项为空", index + 1));
}
if !admin_validate_ip_or_cidr(trimmed) {
return Err(format!("无效的 IP 地址或 CIDR: {raw}"));
}
normalized.push(trimmed.to_string());
}
Ok(Some(json!(normalized)))
}
_ => Err("IP 白名单必须是字符串数组".to_string()),
}
parse_json_ip_rules(value)
}
fn admin_parse_management_token_expires_at(
@@ -46,7 +46,7 @@ pub(crate) const ADMIN_MODULE_DEFINITIONS: &[AdminModuleDefinition] = &[
AdminModuleDefinition {
name: "management_tokens",
display_name: "访问令牌",
description: "管理 API 访问令牌,支持细粒度权限控制和 IP 白名单",
description: "管理 API 访问令牌,支持细粒度权限控制和 IP 限制",
category: "security",
env_key: "MANAGEMENT_TOKENS_AVAILABLE",
default_available: true,