Normalize management token prefixes

This commit is contained in:
fawney19
2026-04-28 19:46:38 +08:00
parent 3d20c05ef7
commit 230e7d6259
7 changed files with 56 additions and 25 deletions

View File

@@ -84,6 +84,8 @@ const LOCAL_EXECUTION_LOOP_DETECTED_DETAIL: &str =
const AUTH_API_KEY_CONCURRENCY_LIMIT_REACHED_DETAIL: &str =
"当前 API Key 并发请求数已达上限,请稍后重试";
const EXECUTION_PATH_TUNNEL_AFFINITY_FORWARD: &str = "tunnel_affinity_forward";
const MANAGEMENT_TOKEN_PREFIX: &str = "ae-";
const LEGACY_MANAGEMENT_TOKEN_PREFIX: &str = "ae_";
fn local_execution_outcome_label(outcome: &LocalExecutionRequestOutcome) -> &'static str {
match outcome {
@@ -113,7 +115,10 @@ fn extract_management_token_bearer(headers: &http::HeaderMap) -> Option<String>
.or_else(|| header.strip_prefix("bearer "))?
.trim()
.to_string();
(!token.is_empty() && token.starts_with("ae_")).then_some(token)
(!token.is_empty()
&& (token.starts_with(MANAGEMENT_TOKEN_PREFIX)
|| token.starts_with(LEGACY_MANAGEMENT_TOKEN_PREFIX)))
.then_some(token)
}
fn hash_management_token(value: &str) -> String {

View File

@@ -19,11 +19,12 @@ use super::{
query_param_value, resolve_authenticated_local_user, AppState, AuthenticatedLocalUserContext,
GatewayPublicRequestContext,
};
use crate::handlers::shared::generate_gateway_secret_plaintext;
use crate::LocalMutationOutcome;
const USERS_ME_MANAGEMENT_TOKEN_PREFIX: &str = "ae_";
const USERS_ME_MANAGEMENT_TOKEN_RANDOM_LENGTH: usize = 40;
const USERS_ME_MANAGEMENT_TOKEN_DISPLAY_PREFIX_LEN: usize = 7;
const USERS_ME_MANAGEMENT_TOKEN_PREFIX: &str = "ae";
const USERS_ME_MANAGEMENT_TOKEN_SEPARATOR: &str = "-";
const USERS_ME_MANAGEMENT_TOKEN_DISPLAY_PREFIX_LEN: usize = 10;
const USERS_ME_MANAGEMENT_TOKEN_FETCH_LIMIT: usize = 10_000;
const USERS_ME_MANAGEMENT_TOKEN_DEFAULT_MAX_PER_USER: usize = 20;
const USERS_ME_MANAGEMENT_TOKEN_MAX_PER_USER_ENV: &str = "MANAGEMENT_TOKEN_MAX_PER_USER";
@@ -122,13 +123,10 @@ fn users_me_management_token_max_per_user() -> usize {
}
fn generate_users_me_management_token_plaintext() -> String {
let first = Uuid::new_v4().simple().to_string();
let second = Uuid::new_v4().simple().to_string();
let mut random_part = String::with_capacity(USERS_ME_MANAGEMENT_TOKEN_RANDOM_LENGTH);
random_part.push_str(&first);
random_part.push_str(&second);
random_part.truncate(USERS_ME_MANAGEMENT_TOKEN_RANDOM_LENGTH);
format!("{USERS_ME_MANAGEMENT_TOKEN_PREFIX}{random_part}")
generate_gateway_secret_plaintext(
USERS_ME_MANAGEMENT_TOKEN_PREFIX,
USERS_ME_MANAGEMENT_TOKEN_SEPARATOR,
)
}
fn hash_users_me_management_token(value: &str) -> String {

View File

@@ -26,7 +26,7 @@ fn api_key_placeholder_display_with_prefix(prefix: &str) -> String {
format!("{prefix}-****")
}
fn generate_gateway_api_key_plaintext_with_prefix(prefix: &str) -> String {
fn generate_gateway_secret_random_part() -> String {
let mut random = String::with_capacity(API_KEY_RANDOM_LEN);
while random.len() < API_KEY_RANDOM_LEN {
for byte in uuid::Uuid::new_v4().as_bytes() {
@@ -37,7 +37,18 @@ fn generate_gateway_api_key_plaintext_with_prefix(prefix: &str) -> String {
}
}
}
format!("{prefix}-{random}")
random
}
pub(crate) fn generate_gateway_secret_plaintext(prefix: &str, separator: &str) -> String {
format!(
"{prefix}{separator}{}",
generate_gateway_secret_random_part()
)
}
fn generate_gateway_api_key_plaintext_with_prefix(prefix: &str) -> String {
generate_gateway_secret_plaintext(prefix, "-")
}
pub(crate) fn configured_api_key_prefix() -> String {
@@ -84,7 +95,8 @@ pub(crate) fn normalize_optional_api_key_concurrent_limit(
mod tests {
use super::{
api_key_placeholder_display_with_prefix, configured_api_key_prefix_from_lookup,
generate_gateway_api_key_plaintext_with_prefix, masked_gateway_api_key_display,
generate_gateway_api_key_plaintext_with_prefix, generate_gateway_secret_plaintext,
masked_gateway_api_key_display,
};
#[test]
@@ -114,6 +126,17 @@ mod tests {
.all(|ch| ch.is_ascii_alphanumeric()));
}
#[test]
fn generates_plaintext_secret_with_custom_separator() {
let value = generate_gateway_secret_plaintext("ae", "-");
assert!(value.starts_with("ae-"));
assert_eq!(value.len(), 3 + 32);
assert!(value
.trim_start_matches("ae-")
.chars()
.all(|ch| ch.is_ascii_alphanumeric()));
}
#[test]
fn uses_configured_prefix_in_placeholder_display() {
assert_eq!(

View File

@@ -16,7 +16,8 @@ pub(crate) use self::admin_proxy::{
};
pub(crate) use self::api_keys::{
api_key_placeholder_display, configured_api_key_prefix, generate_gateway_api_key_plaintext,
masked_gateway_api_key_display, normalize_optional_api_key_concurrent_limit,
generate_gateway_secret_plaintext, masked_gateway_api_key_display,
normalize_optional_api_key_concurrent_limit,
};
pub(crate) use self::catalog::{
build_admin_provider_key_response, decrypt_catalog_secret_with_fallbacks,

View File

@@ -663,7 +663,7 @@ async fn gateway_registers_and_unregisters_proxy_nodes_locally_with_management_t
}),
);
let raw_token = "ae_proxy_register_test";
let raw_token = "ae-proxy-register-test";
let proxy_node_repository = Arc::new(InMemoryProxyNodeRepository::default());
let state = AppState::new().expect("gateway should build");
let admin_user = state

View File

@@ -6825,10 +6825,12 @@ async fn gateway_handles_users_me_management_token_writes_locally_without_proxyi
.to_string();
assert_eq!(create_payload["message"], "Management Token 创建成功");
assert_eq!(create_payload["data"]["name"], "writer-token");
assert!(create_payload["token"]
.as_str()
.unwrap_or_default()
.starts_with("ae_"));
let created_token = create_payload["token"].as_str().unwrap_or_default();
let created_token_random = created_token.strip_prefix("ae-").unwrap_or_default();
assert_eq!(created_token_random.len(), 32);
assert!(created_token_random
.chars()
.all(|ch| ch.is_ascii_alphanumeric()));
let update_response = client
.put(format!(
@@ -6901,10 +6903,12 @@ async fn gateway_handles_users_me_management_token_writes_locally_without_proxyi
.await
.expect("json body should parse");
assert_eq!(regenerate_payload["message"], "Token 已重新生成");
assert!(regenerate_payload["token"]
.as_str()
.unwrap_or_default()
.starts_with("ae_"));
let regenerated_token = regenerate_payload["token"].as_str().unwrap_or_default();
let regenerated_token_random = regenerated_token.strip_prefix("ae-").unwrap_or_default();
assert_eq!(regenerated_token_random.len(), 32);
assert!(regenerated_token_random
.chars()
.all(|ch| ch.is_ascii_alphanumeric()));
let delete_response = client
.delete(format!(

View File

@@ -132,7 +132,7 @@ impl StoredManagementToken {
self.token_prefix
.as_deref()
.map(|prefix| format!("{prefix}...****"))
.unwrap_or_else(|| "ae_****".to_string())
.unwrap_or_else(|| "ae-****".to_string())
}
}