use axum::{
body::{to_bytes, Body},
extract::State,
http::{HeaderMap, Request, StatusCode, Uri},
middleware::Next,
response::IntoResponse,
};
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
use serde_json::Value;
use crate::{
config::PROXY_AUTH_HEADER,
error::{AuthenticationError, ServerError, ValidationError},
management::API_KEY_PREFIX,
state::AppState,
Claims,
};
pub async fn proxy_middleware(
State(_state): State<AppState>,
req: Request<Body>,
next: Next,
) -> impl IntoResponse {
next.run(req).await
}
pub fn proxy_uri(original_uri: Uri, namespace: &str, sandbox_name: &str) -> Uri {
let target_host = format!("sandbox-{}.{}.internal", sandbox_name, namespace);
let uri_string = if let Some(path_and_query) = original_uri.path_and_query() {
format!("http://{}:{}{}", target_host, 8080, path_and_query)
} else {
format!("http://{}:{}/", target_host, 8080)
};
uri_string
.parse()
.unwrap_or_else(|_| "http://localhost:8080/".parse().unwrap())
}
pub async fn logging_middleware(
req: Request<Body>,
next: Next,
) -> Result<impl IntoResponse, (StatusCode, String)> {
let method = req.method().clone();
let uri = req.uri().clone();
tracing::info!("Request: {} {}", method, uri);
let response = next.run(req).await;
tracing::info!("Response: {} {}: {}", method, uri, response.status());
Ok(response)
}
pub async fn auth_middleware(
State(state): State<AppState>,
req: Request<Body>,
next: Next,
) -> Result<impl IntoResponse, ServerError> {
if *state.get_config().get_dev_mode() {
return Ok(next.run(req).await);
}
let api_key = extract_api_key_from_headers(req.headers())?;
let claims = validate_token(&api_key, &state)?;
if claims.namespace == "*" {
return Ok(next.run(req).await);
}
let (parts, body) = req.into_parts();
let bytes = to_bytes(body, usize::MAX)
.await
.map_err(|e| ServerError::InternalError(format!("Failed to read request body: {}", e)))?;
let namespace_from_request = extract_namespace_from_json_rpc(&bytes)?;
if claims.namespace != namespace_from_request {
return Err(ServerError::AuthorizationError(
crate::error::AuthorizationError::AccessDenied(format!(
"Token does not have access to namespace '{}'",
namespace_from_request
)),
));
}
let body = Body::from(bytes);
let req = Request::from_parts(parts, body);
Ok(next.run(req).await)
}
pub async fn mcp_smart_auth_middleware(
State(state): State<AppState>,
req: Request<Body>,
next: Next,
) -> Result<impl IntoResponse, ServerError> {
if *state.get_config().get_dev_mode() {
return Ok(next.run(req).await);
}
let api_key = extract_api_key_from_headers(req.headers())?;
let claims = validate_token(&api_key, &state)?;
if claims.namespace == "*" {
return Ok(next.run(req).await);
}
let (parts, body) = req.into_parts();
let bytes = to_bytes(body, usize::MAX)
.await
.map_err(|e| ServerError::InternalError(format!("Failed to read request body: {}", e)))?;
let json_value: serde_json::Value = serde_json::from_slice(&bytes).map_err(|e| {
ServerError::ValidationError(crate::error::ValidationError::InvalidInput(format!(
"Invalid JSON-RPC request: {}",
e
)))
})?;
let method = json_value
.get("method")
.and_then(serde_json::Value::as_str)
.unwrap_or("unknown");
let requires_namespace_validation = matches!(method, "tools/call");
if requires_namespace_validation {
let namespace_from_request = extract_namespace_from_json_rpc(&bytes)?;
if claims.namespace != namespace_from_request {
return Err(ServerError::AuthorizationError(
crate::error::AuthorizationError::AccessDenied(format!(
"Token does not have access to namespace '{}'",
namespace_from_request
)),
));
}
}
let body = Body::from(bytes);
let req = Request::from_parts(parts, body);
Ok(next.run(req).await)
}
fn extract_namespace_from_json_rpc(bytes: &[u8]) -> Result<String, ServerError> {
let json_value: Value = serde_json::from_slice(bytes).map_err(|e| {
ServerError::ValidationError(ValidationError::InvalidInput(format!(
"Invalid JSON-RPC request: {}",
e
)))
})?;
let method = json_value
.get("method")
.and_then(Value::as_str)
.unwrap_or("unknown");
let params = json_value.get("params").ok_or_else(|| {
ServerError::ValidationError(ValidationError::InvalidInput(
"Missing 'params' field in JSON-RPC request".to_string(),
))
})?;
params
.get("namespace")
.and_then(Value::as_str)
.map(String::from)
.ok_or_else(|| {
ServerError::ValidationError(ValidationError::InvalidInput(format!(
"Missing or invalid 'namespace' in params for method '{}'",
method
)))
})
}
fn extract_api_key_from_headers(headers: &HeaderMap) -> Result<String, ServerError> {
if let Some(auth_header) = headers.get(PROXY_AUTH_HEADER) {
let auth_value = auth_header.to_str().map_err(|_| {
ServerError::Authentication(AuthenticationError::InvalidCredentials(
"Invalid authorization header format".to_string(),
))
})?;
if let Some(token) = auth_value.strip_prefix("Bearer ") {
return Ok(token.to_string());
}
return Ok(auth_value.to_string());
}
if let Some(auth_header) = headers.get("Authorization") {
let auth_value = auth_header.to_str().map_err(|_| {
ServerError::Authentication(AuthenticationError::InvalidCredentials(
"Invalid authorization header format".to_string(),
))
})?;
if let Some(token) = auth_value.strip_prefix("Bearer ") {
return Ok(token.to_string());
}
return Ok(auth_value.to_string());
}
Err(ServerError::Authentication(
AuthenticationError::InvalidCredentials("Missing authorization header".to_string()),
))
}
fn convert_api_key_to_jwt(api_key: &str) -> Result<String, ServerError> {
if !api_key.starts_with(API_KEY_PREFIX) {
return Err(ServerError::Authentication(
AuthenticationError::InvalidCredentials(
"Invalid API key format: missing prefix".to_string(),
),
));
}
Ok(api_key[API_KEY_PREFIX.len()..].to_string())
}
fn get_server_key(state: &AppState) -> Result<String, ServerError> {
match state.get_config().get_key() {
Some(key) => Ok(key.clone()),
None => Err(ServerError::Authentication(
AuthenticationError::InvalidCredentials(
"Server key not found in configuration".to_string(),
),
)),
}
}
fn validate_token(api_key: &str, state: &AppState) -> Result<Claims, ServerError> {
let jwt = convert_api_key_to_jwt(api_key)?;
let server_key = get_server_key(state)?;
let token_data = decode::<Claims>(
&jwt,
&DecodingKey::from_secret(server_key.as_bytes()),
&Validation::new(Algorithm::HS256),
)
.map_err(|e| {
let error_message = match e.kind() {
jsonwebtoken::errors::ErrorKind::ExpiredSignature => "Token expired".to_string(),
jsonwebtoken::errors::ErrorKind::InvalidSignature => {
"Invalid token signature".to_string()
}
_ => format!("Token validation error: {}", e),
};
ServerError::Authentication(AuthenticationError::InvalidToken(error_message))
})?;
Ok(token_data.claims)
}
pub fn validate_token_and_namespace(
api_key: &str,
requested_namespace: &str,
state: &AppState,
) -> Result<Claims, ServerError> {
let claims = validate_token(api_key, state)?;
if claims.namespace != requested_namespace && claims.namespace != "*" {
return Err(ServerError::Authentication(
AuthenticationError::InvalidCredentials(format!(
"Token does not have access to namespace '{}'",
requested_namespace
)),
));
}
Ok(claims)
}