release(secrets-mcp): 0.5.3 — 审计日志分页与 Web;CONTRIBUTING;文档与模板修正
This commit is contained in:
@@ -165,30 +165,7 @@ async fn main() -> Result<()> {
|
||||
Some("prod" | "production")
|
||||
);
|
||||
|
||||
let cors = if is_production {
|
||||
// Only use the origin part (scheme://host:port) of BASE_URL for CORS.
|
||||
// Browsers send Origin without path, so including a path would cause mismatches.
|
||||
let allowed_origin = if let Ok(parsed) = base_url.parse::<url::Url>() {
|
||||
let origin = parsed.origin().ascii_serialization();
|
||||
origin
|
||||
.parse::<axum::http::HeaderValue>()
|
||||
.unwrap_or_else(|_| panic!("invalid BASE_URL origin: {}", origin))
|
||||
} else {
|
||||
base_url
|
||||
.parse::<axum::http::HeaderValue>()
|
||||
.unwrap_or_else(|_| panic!("invalid BASE_URL: {}", base_url))
|
||||
};
|
||||
CorsLayer::new()
|
||||
.allow_origin(allowed_origin)
|
||||
.allow_methods(Any)
|
||||
.allow_headers(Any)
|
||||
.allow_credentials(true)
|
||||
} else {
|
||||
CorsLayer::new()
|
||||
.allow_origin(Any)
|
||||
.allow_methods(Any)
|
||||
.allow_headers(Any)
|
||||
};
|
||||
let cors = build_cors_layer(&base_url, is_production);
|
||||
|
||||
// Rate limiting
|
||||
let rate_limit_state = rate_limit::RateLimitState::new();
|
||||
@@ -257,3 +234,86 @@ async fn shutdown_signal() {
|
||||
|
||||
tracing::info!("Shutting down gracefully...");
|
||||
}
|
||||
|
||||
/// Production CORS allowed headers.
|
||||
///
|
||||
/// When adding a new custom header to the MCP or Web API, this list must be
|
||||
/// updated accordingly — otherwise browsers will block the request during
|
||||
/// the CORS preflight check.
|
||||
fn production_allowed_headers() -> [axum::http::HeaderName; 5] {
|
||||
[
|
||||
axum::http::header::AUTHORIZATION,
|
||||
axum::http::header::CONTENT_TYPE,
|
||||
axum::http::HeaderName::from_static("x-encryption-key"),
|
||||
axum::http::HeaderName::from_static("mcp-session-id"),
|
||||
axum::http::HeaderName::from_static("x-mcp-session"),
|
||||
]
|
||||
}
|
||||
|
||||
/// Build the CORS layer for the application.
|
||||
///
|
||||
/// In production mode the origin is restricted to the BASE_URL origin
|
||||
/// (scheme://host:port, path stripped) and credentials are allowed.
|
||||
/// `allow_headers` uses an explicit whitelist to avoid the tower-http
|
||||
/// restriction on `allow_credentials(true)` + `allow_headers(Any)`.
|
||||
///
|
||||
/// In development mode all origins, methods and headers are allowed.
|
||||
fn build_cors_layer(base_url: &str, is_production: bool) -> CorsLayer {
|
||||
if is_production {
|
||||
let allowed_origin = if let Ok(parsed) = base_url.parse::<url::Url>() {
|
||||
let origin = parsed.origin().ascii_serialization();
|
||||
origin
|
||||
.parse::<axum::http::HeaderValue>()
|
||||
.unwrap_or_else(|_| panic!("invalid BASE_URL origin: {}", origin))
|
||||
} else {
|
||||
base_url
|
||||
.parse::<axum::http::HeaderValue>()
|
||||
.unwrap_or_else(|_| panic!("invalid BASE_URL: {}", base_url))
|
||||
};
|
||||
CorsLayer::new()
|
||||
.allow_origin(allowed_origin)
|
||||
.allow_methods(Any)
|
||||
.allow_headers(production_allowed_headers())
|
||||
.allow_credentials(true)
|
||||
} else {
|
||||
CorsLayer::new()
|
||||
.allow_origin(Any)
|
||||
.allow_methods(Any)
|
||||
.allow_headers(Any)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn production_cors_does_not_panic() {
|
||||
let layer = build_cors_layer("https://secrets.example.com/app", true);
|
||||
let _ = layer;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn production_cors_headers_include_all_required() {
|
||||
let headers = production_allowed_headers();
|
||||
let names: Vec<&str> = headers.iter().map(|h| h.as_str()).collect();
|
||||
assert!(names.contains(&"authorization"));
|
||||
assert!(names.contains(&"content-type"));
|
||||
assert!(names.contains(&"x-encryption-key"));
|
||||
assert!(names.contains(&"mcp-session-id"));
|
||||
assert!(names.contains(&"x-mcp-session"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn production_cors_normalizes_base_url_with_path() {
|
||||
let url = url::Url::parse("https://secrets.example.com/secrets/app").unwrap();
|
||||
let origin = url.origin().ascii_serialization();
|
||||
assert_eq!(origin, "https://secrets.example.com");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn development_cors_allows_everything() {
|
||||
let layer = build_cors_layer("http://localhost:9315", false);
|
||||
let _ = layer;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user