2026-04-05 20:23:16 +08:00
|
|
|
use crate::constants::{
|
2026-04-04 01:40:24 +08:00
|
|
|
CONTROL_EXECUTE_FALLBACK_HEADER, TRUSTED_ADMIN_MANAGEMENT_TOKEN_ID_HEADER,
|
|
|
|
|
TRUSTED_ADMIN_SESSION_ID_HEADER, TRUSTED_ADMIN_USER_ID_HEADER, TRUSTED_ADMIN_USER_ROLE_HEADER,
|
|
|
|
|
};
|
2026-04-05 20:23:16 +08:00
|
|
|
use crate::control::GatewayControlDecision;
|
|
|
|
|
use crate::control::GatewayPublicRequestContext;
|
|
|
|
|
use crate::headers::header_value_str;
|
|
|
|
|
use crate::tunnel::TUNNEL_ROUTE_FAMILY;
|
2026-04-04 01:40:24 +08:00
|
|
|
use axum::http::{self, HeaderName};
|
|
|
|
|
use chrono::{SecondsFormat, Utc};
|
|
|
|
|
use url::form_urlencoded;
|
2026-04-03 14:59:58 +08:00
|
|
|
|
2026-04-07 02:50:19 +08:00
|
|
|
use super::OFFICIAL_EXTERNAL_MODEL_PROVIDERS;
|
|
|
|
|
|
2026-04-03 14:59:58 +08:00
|
|
|
pub(crate) fn rust_auth_terminates_provider_credentials(
|
|
|
|
|
decision: Option<&GatewayControlDecision>,
|
|
|
|
|
) -> bool {
|
2026-03-31 19:19:04 +08:00
|
|
|
decision.is_some_and(|decision| {
|
|
|
|
|
decision.route_class.as_deref() == Some("ai_public") && decision.auth_context.is_some()
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 14:59:58 +08:00
|
|
|
pub(crate) fn mark_external_models_official_providers(
|
|
|
|
|
value: &serde_json::Value,
|
|
|
|
|
) -> Option<serde_json::Value> {
|
2026-03-31 19:19:04 +08:00
|
|
|
let providers = value.as_object()?;
|
|
|
|
|
Some(serde_json::Value::Object(
|
|
|
|
|
providers
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|(provider_id, provider_value)| {
|
|
|
|
|
let updated = match provider_value.as_object() {
|
|
|
|
|
Some(object) => {
|
|
|
|
|
let mut cloned = object.clone();
|
|
|
|
|
cloned.insert(
|
|
|
|
|
"official".to_string(),
|
|
|
|
|
serde_json::Value::Bool(
|
|
|
|
|
OFFICIAL_EXTERNAL_MODEL_PROVIDERS
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|value| value == provider_id),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
serde_json::Value::Object(cloned)
|
|
|
|
|
}
|
|
|
|
|
None => provider_value.clone(),
|
|
|
|
|
};
|
|
|
|
|
(provider_id.clone(), updated)
|
|
|
|
|
})
|
|
|
|
|
.collect(),
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 14:59:58 +08:00
|
|
|
pub(crate) fn should_strip_forwarded_provider_credential_header(
|
2026-03-31 19:19:04 +08:00
|
|
|
decision: Option<&GatewayControlDecision>,
|
|
|
|
|
header_name: &HeaderName,
|
|
|
|
|
) -> bool {
|
|
|
|
|
if !rust_auth_terminates_provider_credentials(decision) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
matches!(
|
|
|
|
|
header_name.as_str(),
|
|
|
|
|
"authorization" | "x-api-key" | "api-key" | "x-goog-api-key"
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 14:59:58 +08:00
|
|
|
pub(crate) fn should_strip_forwarded_trusted_admin_header(
|
2026-03-31 19:19:04 +08:00
|
|
|
decision: Option<&GatewayControlDecision>,
|
|
|
|
|
header_name: &HeaderName,
|
|
|
|
|
) -> bool {
|
|
|
|
|
let Some(decision) = decision else {
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
if decision.route_class.as_deref() != Some("admin_proxy") {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
matches!(
|
|
|
|
|
header_name.as_str(),
|
|
|
|
|
TRUSTED_ADMIN_USER_ID_HEADER
|
|
|
|
|
| TRUSTED_ADMIN_USER_ROLE_HEADER
|
|
|
|
|
| TRUSTED_ADMIN_SESSION_ID_HEADER
|
|
|
|
|
| TRUSTED_ADMIN_MANAGEMENT_TOKEN_ID_HEADER
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 14:59:58 +08:00
|
|
|
pub(crate) fn sanitize_upstream_path_and_query(
|
2026-03-31 19:19:04 +08:00
|
|
|
decision: Option<&GatewayControlDecision>,
|
|
|
|
|
default_path_and_query: &str,
|
|
|
|
|
) -> String {
|
|
|
|
|
let base = decision
|
|
|
|
|
.map(GatewayControlDecision::proxy_path_and_query)
|
|
|
|
|
.unwrap_or_else(|| default_path_and_query.to_string());
|
|
|
|
|
let Some(decision) = decision else {
|
|
|
|
|
return base;
|
|
|
|
|
};
|
|
|
|
|
if !rust_auth_terminates_provider_credentials(Some(decision))
|
|
|
|
|
|| decision.route_family.as_deref() != Some("gemini")
|
|
|
|
|
{
|
|
|
|
|
return base;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
strip_query_param(&base, "key")
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 14:59:58 +08:00
|
|
|
pub(crate) fn strip_query_param(path_and_query: &str, key_to_strip: &str) -> String {
|
2026-03-31 19:19:04 +08:00
|
|
|
let Some((path, query)) = path_and_query.split_once('?') else {
|
|
|
|
|
return path_and_query.to_string();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mut serializer = form_urlencoded::Serializer::new(String::new());
|
|
|
|
|
let mut kept_any = false;
|
|
|
|
|
for (key, value) in form_urlencoded::parse(query.as_bytes()) {
|
|
|
|
|
if key == key_to_strip {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
serializer.append_pair(key.as_ref(), value.as_ref());
|
|
|
|
|
kept_any = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !kept_any {
|
|
|
|
|
return path.to_string();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
format!("{path}?{}", serializer.finish())
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 14:59:58 +08:00
|
|
|
pub(crate) fn query_param_bool(query: Option<&str>, key: &str, default: bool) -> bool {
|
2026-03-31 19:19:04 +08:00
|
|
|
let Some(query) = query else {
|
|
|
|
|
return default;
|
|
|
|
|
};
|
|
|
|
|
for (entry_key, value) in form_urlencoded::parse(query.as_bytes()) {
|
|
|
|
|
if entry_key == key {
|
|
|
|
|
let normalized = value.trim().to_ascii_lowercase();
|
|
|
|
|
return matches!(normalized.as_str(), "1" | "true" | "yes" | "on");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
default
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 14:59:58 +08:00
|
|
|
pub(crate) fn query_param_optional_bool(query: Option<&str>, key: &str) -> Option<bool> {
|
2026-03-31 19:19:04 +08:00
|
|
|
let query = query?;
|
|
|
|
|
for (entry_key, value) in form_urlencoded::parse(query.as_bytes()) {
|
|
|
|
|
if entry_key == key {
|
|
|
|
|
let normalized = value.trim().to_ascii_lowercase();
|
|
|
|
|
return match normalized.as_str() {
|
|
|
|
|
"1" | "true" | "yes" | "on" => Some(true),
|
|
|
|
|
"0" | "false" | "no" | "off" => Some(false),
|
|
|
|
|
_ => None,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 14:59:58 +08:00
|
|
|
pub(crate) fn query_param_value(query: Option<&str>, key: &str) -> Option<String> {
|
2026-03-31 19:19:04 +08:00
|
|
|
let query = query?;
|
|
|
|
|
for (entry_key, value) in form_urlencoded::parse(query.as_bytes()) {
|
|
|
|
|
if entry_key == key {
|
|
|
|
|
let value = value.trim();
|
|
|
|
|
if !value.is_empty() {
|
|
|
|
|
return Some(value.to_string());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 14:59:58 +08:00
|
|
|
pub(crate) fn request_enables_control_execute(headers: &http::HeaderMap) -> bool {
|
2026-04-04 01:40:24 +08:00
|
|
|
header_value_str(headers, CONTROL_EXECUTE_FALLBACK_HEADER).is_some_and(|value| {
|
|
|
|
|
matches!(
|
|
|
|
|
value.to_ascii_lowercase().as_str(),
|
|
|
|
|
"1" | "true" | "yes" | "on"
|
|
|
|
|
)
|
2026-03-31 19:19:04 +08:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 14:59:58 +08:00
|
|
|
pub(crate) fn unix_secs_to_rfc3339(unix_secs: u64) -> Option<String> {
|
2026-03-31 19:19:04 +08:00
|
|
|
let timestamp = i64::try_from(unix_secs).ok()?;
|
|
|
|
|
Some(
|
|
|
|
|
chrono::DateTime::<Utc>::from_timestamp(timestamp, 0)?
|
|
|
|
|
.to_rfc3339_opts(SecondsFormat::Secs, true),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 14:59:58 +08:00
|
|
|
pub(crate) fn json_string_list(value: Option<&serde_json::Value>) -> Vec<String> {
|
2026-03-31 19:19:04 +08:00
|
|
|
value
|
|
|
|
|
.and_then(serde_json::Value::as_array)
|
|
|
|
|
.into_iter()
|
|
|
|
|
.flatten()
|
|
|
|
|
.filter_map(serde_json::Value::as_str)
|
|
|
|
|
.map(ToOwned::to_owned)
|
|
|
|
|
.collect()
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 14:59:58 +08:00
|
|
|
pub(crate) fn admin_proxy_local_requires_buffered_body(
|
|
|
|
|
request_context: &GatewayPublicRequestContext,
|
|
|
|
|
) -> bool {
|
2026-03-31 19:19:04 +08:00
|
|
|
request_context
|
|
|
|
|
.control_decision
|
|
|
|
|
.as_ref()
|
|
|
|
|
.is_some_and(|decision| {
|
|
|
|
|
if decision.route_class.as_deref() != Some("admin_proxy") {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match (
|
|
|
|
|
decision.route_family.as_deref(),
|
|
|
|
|
request_context.request_method.clone(),
|
|
|
|
|
decision.route_kind.as_deref(),
|
|
|
|
|
) {
|
|
|
|
|
(Some("endpoints_manage"), http::Method::POST, Some("create_provider_key"))
|
|
|
|
|
| (Some("endpoints_manage"), http::Method::POST, Some("create_endpoint"))
|
|
|
|
|
| (Some("endpoints_manage"), http::Method::POST, Some("batch_delete_keys"))
|
|
|
|
|
| (Some("endpoints_manage"), http::Method::PUT, Some("update_key"))
|
|
|
|
|
| (Some("endpoints_manage"), http::Method::PUT, Some("update_endpoint"))
|
|
|
|
|
| (Some("modules_manage"), http::Method::PUT, Some("set_enabled"))
|
|
|
|
|
| (Some("oauth_manage"), http::Method::PUT, Some("upsert_provider"))
|
|
|
|
|
| (Some("oauth_manage"), http::Method::POST, Some("test_provider"))
|
|
|
|
|
| (Some("provider_oauth_manage"), http::Method::POST, Some("complete_key_oauth"))
|
|
|
|
|
| (
|
|
|
|
|
Some("provider_oauth_manage"),
|
|
|
|
|
http::Method::POST,
|
|
|
|
|
Some("complete_provider_oauth"),
|
|
|
|
|
)
|
|
|
|
|
| (
|
|
|
|
|
Some("provider_oauth_manage"),
|
|
|
|
|
http::Method::POST,
|
|
|
|
|
Some("import_refresh_token"),
|
|
|
|
|
)
|
2026-04-03 14:59:58 +08:00
|
|
|
| (Some("provider_oauth_manage"), http::Method::POST, Some("batch_import_oauth"))
|
2026-03-31 19:19:04 +08:00
|
|
|
| (
|
|
|
|
|
Some("provider_oauth_manage"),
|
|
|
|
|
http::Method::POST,
|
|
|
|
|
Some("start_batch_import_oauth_task"),
|
|
|
|
|
)
|
2026-04-03 14:59:58 +08:00
|
|
|
| (Some("provider_oauth_manage"), http::Method::POST, Some("device_authorize"))
|
|
|
|
|
| (Some("provider_oauth_manage"), http::Method::POST, Some("device_poll"))
|
2026-03-31 19:19:04 +08:00
|
|
|
| (Some("system_manage"), http::Method::PUT, Some("settings_set"))
|
|
|
|
|
| (Some("system_manage"), http::Method::PUT, Some("config_set"))
|
2026-04-03 14:59:58 +08:00
|
|
|
| (Some("system_manage"), http::Method::PUT, Some("email_template_set"))
|
|
|
|
|
| (Some("system_manage"), http::Method::POST, Some("email_template_preview"))
|
2026-03-31 19:19:04 +08:00
|
|
|
| (
|
|
|
|
|
Some("provider_models_manage"),
|
|
|
|
|
http::Method::POST,
|
|
|
|
|
Some("create_provider_model"),
|
|
|
|
|
)
|
|
|
|
|
| (
|
|
|
|
|
Some("provider_models_manage"),
|
|
|
|
|
http::Method::PATCH,
|
|
|
|
|
Some("update_provider_model"),
|
|
|
|
|
)
|
|
|
|
|
| (
|
|
|
|
|
Some("provider_models_manage"),
|
|
|
|
|
http::Method::POST,
|
|
|
|
|
Some("batch_create_provider_models"),
|
|
|
|
|
)
|
|
|
|
|
| (
|
|
|
|
|
Some("provider_models_manage"),
|
|
|
|
|
http::Method::POST,
|
|
|
|
|
Some("assign_global_models"),
|
|
|
|
|
)
|
|
|
|
|
| (
|
|
|
|
|
Some("provider_models_manage"),
|
|
|
|
|
http::Method::POST,
|
|
|
|
|
Some("import_from_upstream"),
|
|
|
|
|
)
|
|
|
|
|
| (
|
|
|
|
|
Some("provider_ops_manage"),
|
|
|
|
|
http::Method::POST,
|
|
|
|
|
Some("execute_provider_action"),
|
|
|
|
|
)
|
2026-04-03 14:59:58 +08:00
|
|
|
| (Some("provider_ops_manage"), http::Method::POST, Some("batch_balance"))
|
|
|
|
|
| (Some("provider_ops_manage"), http::Method::POST, Some("connect_provider"))
|
|
|
|
|
| (Some("provider_ops_manage"), http::Method::POST, Some("verify_provider"))
|
|
|
|
|
| (Some("provider_ops_manage"), http::Method::PUT, Some("save_provider_config"))
|
|
|
|
|
| (Some("announcements_manage"), http::Method::POST, Some("create_announcement"))
|
|
|
|
|
| (Some("announcements_manage"), http::Method::PUT, Some("update_announcement"))
|
2026-03-31 19:19:04 +08:00
|
|
|
| (
|
|
|
|
|
Some("provider_strategy_manage"),
|
|
|
|
|
http::Method::PUT,
|
|
|
|
|
Some("update_provider_billing"),
|
|
|
|
|
)
|
|
|
|
|
| (
|
|
|
|
|
Some("provider_query_manage"),
|
|
|
|
|
http::Method::POST,
|
|
|
|
|
Some("query_models" | "test_model" | "test_model_failover"),
|
|
|
|
|
)
|
2026-04-03 14:59:58 +08:00
|
|
|
| (Some("billing_manage"), http::Method::POST, Some("apply_preset"))
|
|
|
|
|
| (Some("billing_manage"), http::Method::POST, Some("create_rule"))
|
|
|
|
|
| (Some("billing_manage"), http::Method::PUT, Some("update_rule"))
|
|
|
|
|
| (Some("billing_manage"), http::Method::POST, Some("create_collector"))
|
|
|
|
|
| (Some("billing_manage"), http::Method::PUT, Some("update_collector"))
|
|
|
|
|
| (Some("payments_manage"), http::Method::POST, Some("credit_order"))
|
|
|
|
|
| (Some("api_keys_manage"), http::Method::POST, Some("create_api_key"))
|
|
|
|
|
| (Some("api_keys_manage"), http::Method::PUT, Some("update_api_key"))
|
|
|
|
|
| (Some("api_keys_manage"), http::Method::PATCH, Some("toggle_api_key"))
|
|
|
|
|
| (Some("adaptive_manage"), http::Method::PATCH, Some("toggle_mode"))
|
|
|
|
|
| (Some("security_manage"), http::Method::POST, Some("blacklist_add"))
|
|
|
|
|
| (Some("security_manage"), http::Method::POST, Some("whitelist_add"))
|
|
|
|
|
| (Some("users_manage"), http::Method::POST, Some("create_user"))
|
|
|
|
|
| (Some("users_manage"), http::Method::PUT, Some("update_user"))
|
|
|
|
|
| (Some("users_manage"), http::Method::POST, Some("create_user_api_key"))
|
|
|
|
|
| (Some("users_manage"), http::Method::PUT, Some("update_user_api_key"))
|
|
|
|
|
| (Some("users_manage"), http::Method::PATCH, Some("lock_user_api_key"))
|
|
|
|
|
| (Some("pool_manage"), http::Method::POST, Some("batch_import_keys"))
|
|
|
|
|
| (Some("pool_manage"), http::Method::POST, Some("batch_action_keys"))
|
|
|
|
|
| (Some("pool_manage"), http::Method::POST, Some("resolve_selection"))
|
|
|
|
|
| (Some("usage_manage"), http::Method::POST, Some("replay"))
|
|
|
|
|
| (Some("wallets_manage"), http::Method::POST, Some("adjust_balance"))
|
|
|
|
|
| (Some("wallets_manage"), http::Method::POST, Some("recharge_balance"))
|
|
|
|
|
| (Some("wallets_manage"), http::Method::POST, Some("process_refund"))
|
|
|
|
|
| (Some("wallets_manage"), http::Method::POST, Some("complete_refund"))
|
|
|
|
|
| (Some("wallets_manage"), http::Method::POST, Some("fail_refund"))
|
2026-03-31 19:19:04 +08:00
|
|
|
| (Some("gemini_files_manage"), http::Method::POST, Some("upload"))
|
|
|
|
|
| (Some("ldap_manage"), http::Method::PUT, Some("set_config"))
|
2026-04-03 14:59:58 +08:00
|
|
|
| (Some("ldap_manage"), http::Method::POST, Some("test_connection"))
|
2026-03-31 19:19:04 +08:00
|
|
|
| (Some("global_models_manage"), http::Method::POST, Some("create_global_model"))
|
|
|
|
|
| (
|
|
|
|
|
Some("global_models_manage"),
|
|
|
|
|
http::Method::PATCH,
|
|
|
|
|
Some("update_global_model"),
|
|
|
|
|
)
|
|
|
|
|
| (
|
|
|
|
|
Some("global_models_manage"),
|
|
|
|
|
http::Method::POST,
|
|
|
|
|
Some("batch_delete_global_models"),
|
|
|
|
|
)
|
|
|
|
|
| (Some("global_models_manage"), http::Method::POST, Some("assign_to_providers"))
|
|
|
|
|
| (Some("providers_manage"), http::Method::POST, Some("create_provider"))
|
|
|
|
|
| (Some("providers_manage"), http::Method::PATCH, Some("update_provider")) => true,
|
|
|
|
|
_ => false,
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 14:59:58 +08:00
|
|
|
pub(crate) fn internal_proxy_local_requires_buffered_body(
|
2026-03-31 19:19:04 +08:00
|
|
|
request_context: &GatewayPublicRequestContext,
|
|
|
|
|
) -> bool {
|
|
|
|
|
request_context
|
|
|
|
|
.control_decision
|
|
|
|
|
.as_ref()
|
|
|
|
|
.is_some_and(|decision| {
|
|
|
|
|
if decision.route_class.as_deref() != Some("internal_proxy")
|
|
|
|
|
|| request_context.request_method != http::Method::POST
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
matches!(
|
|
|
|
|
(
|
|
|
|
|
decision.route_family.as_deref(),
|
|
|
|
|
decision.route_kind.as_deref()
|
|
|
|
|
),
|
2026-04-05 20:23:16 +08:00
|
|
|
(Some(TUNNEL_ROUTE_FAMILY), Some("heartbeat" | "node_status"))
|
|
|
|
|
| (
|
|
|
|
|
Some("internal_gateway"),
|
|
|
|
|
Some(
|
|
|
|
|
"resolve"
|
|
|
|
|
| "auth_context"
|
|
|
|
|
| "decision_sync"
|
|
|
|
|
| "decision_stream"
|
|
|
|
|
| "execute_sync"
|
|
|
|
|
| "execute_stream"
|
|
|
|
|
| "plan_sync"
|
|
|
|
|
| "plan_stream"
|
|
|
|
|
| "report_sync"
|
|
|
|
|
| "report_stream"
|
|
|
|
|
| "finalize_sync"
|
|
|
|
|
)
|
2026-03-31 19:19:04 +08:00
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 14:59:58 +08:00
|
|
|
pub(crate) fn public_support_local_requires_buffered_body(
|
2026-03-31 19:19:04 +08:00
|
|
|
request_context: &GatewayPublicRequestContext,
|
|
|
|
|
) -> bool {
|
|
|
|
|
request_context
|
|
|
|
|
.control_decision
|
|
|
|
|
.as_ref()
|
|
|
|
|
.is_some_and(|decision| {
|
|
|
|
|
if decision.route_class.as_deref() != Some("public_support") {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
matches!(
|
|
|
|
|
(
|
|
|
|
|
decision.route_family.as_deref(),
|
|
|
|
|
request_context.request_method.clone(),
|
|
|
|
|
decision.route_kind.as_deref(),
|
|
|
|
|
),
|
|
|
|
|
(
|
2026-04-04 01:40:24 +08:00
|
|
|
Some("auth"),
|
2026-03-31 19:19:04 +08:00
|
|
|
http::Method::POST,
|
|
|
|
|
Some(
|
|
|
|
|
"login"
|
|
|
|
|
| "register"
|
|
|
|
|
| "send_verification_code"
|
|
|
|
|
| "verify_email"
|
|
|
|
|
| "verification_status"
|
|
|
|
|
),
|
2026-04-03 14:59:58 +08:00
|
|
|
) | (
|
2026-04-04 01:40:24 +08:00
|
|
|
Some("users_me"),
|
2026-04-03 14:59:58 +08:00
|
|
|
http::Method::PUT,
|
|
|
|
|
Some(
|
|
|
|
|
"update_detail"
|
|
|
|
|
| "model_capabilities_update"
|
|
|
|
|
| "preferences_update"
|
|
|
|
|
| "api_key_update"
|
|
|
|
|
| "management_token_update"
|
|
|
|
|
| "api_key_providers_update"
|
|
|
|
|
| "api_key_capabilities_update",
|
|
|
|
|
),
|
|
|
|
|
) | (
|
2026-04-04 01:40:24 +08:00
|
|
|
Some("users_me"),
|
2026-04-03 14:59:58 +08:00
|
|
|
http::Method::PATCH,
|
|
|
|
|
Some("password" | "session_update" | "api_key_patch"),
|
|
|
|
|
) | (
|
2026-04-04 01:40:24 +08:00
|
|
|
Some("users_me"),
|
2026-04-03 14:59:58 +08:00
|
|
|
http::Method::POST,
|
|
|
|
|
Some("api_keys_create" | "management_tokens_create"),
|
|
|
|
|
) | (
|
2026-04-04 01:40:24 +08:00
|
|
|
Some("wallet"),
|
2026-04-03 14:59:58 +08:00
|
|
|
http::Method::POST,
|
|
|
|
|
Some("create_refund" | "create_recharge_order"),
|
|
|
|
|
) | (
|
2026-04-04 01:40:24 +08:00
|
|
|
Some("payment_callback"),
|
2026-04-03 14:59:58 +08:00
|
|
|
http::Method::POST,
|
|
|
|
|
Some("callback"),
|
2026-03-31 19:19:04 +08:00
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 14:59:58 +08:00
|
|
|
pub(crate) fn local_proxy_route_requires_buffered_body(
|
|
|
|
|
request_context: &GatewayPublicRequestContext,
|
|
|
|
|
) -> bool {
|
2026-03-31 19:19:04 +08:00
|
|
|
admin_proxy_local_requires_buffered_body(request_context)
|
|
|
|
|
|| internal_proxy_local_requires_buffered_body(request_context)
|
|
|
|
|
|| public_support_local_requires_buffered_body(request_context)
|
|
|
|
|
}
|