feat: implement centralized HTTP request logging and authentication middleware with bearer and basic auth support
Co-authored-by: aider (openai/andrew/openrouter/anthropic/claude-sonnet-4) <aider@aider.chat>
This commit is contained in:
@@ -1,8 +1,14 @@
|
||||
use anyhow::Result;
|
||||
use axum::http::HeaderMap;
|
||||
use log::info;
|
||||
use axum::{
|
||||
extract::{Request, ConnectInfo},
|
||||
http::{HeaderMap, StatusCode},
|
||||
middleware::Next,
|
||||
response::Response,
|
||||
};
|
||||
use log::{info, warn};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
@@ -64,11 +70,31 @@ pub struct ListItemsQuery {
|
||||
pub count: Option<u32>,
|
||||
}
|
||||
|
||||
fn check_bearer_auth(auth_str: &str, expected_password: &str) -> bool {
|
||||
auth_str.starts_with("Bearer ") && &auth_str[7..] == expected_password
|
||||
}
|
||||
|
||||
fn check_basic_auth(auth_str: &str, expected_password: &str) -> bool {
|
||||
if !auth_str.starts_with("Basic ") {
|
||||
return false;
|
||||
}
|
||||
|
||||
let encoded = &auth_str[6..];
|
||||
if let Ok(decoded_bytes) = base64::decode(encoded) {
|
||||
if let Ok(decoded_str) = String::from_utf8(decoded_bytes) {
|
||||
let expected_credentials = format!("keep:{}", expected_password);
|
||||
return decoded_str == expected_credentials;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn check_auth(headers: &HeaderMap, password: &Option<String>) -> bool {
|
||||
if let Some(expected_password) = password {
|
||||
if let Some(auth_header) = headers.get("authorization") {
|
||||
if let Ok(auth_str) = auth_header.to_str() {
|
||||
return auth_str.starts_with("Bearer ") && &auth_str[7..] == expected_password;
|
||||
return check_bearer_auth(auth_str, expected_password) ||
|
||||
check_basic_auth(auth_str, expected_password);
|
||||
}
|
||||
}
|
||||
false
|
||||
@@ -77,24 +103,39 @@ pub fn check_auth(headers: &HeaderMap, password: &Option<String>) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
// Custom middleware for logging requests and responses
|
||||
pub async fn logging_middleware(
|
||||
req: axum::http::Request<axum::body::Body>,
|
||||
next: axum::middleware::Next,
|
||||
) -> Result<axum::http::Response<axum::body::Body>, axum::response::Response> {
|
||||
let method = req.method().clone();
|
||||
let uri = req.uri().clone();
|
||||
let headers = req.headers().clone();
|
||||
|
||||
// Log incoming request
|
||||
info!("SERVER: {} {} - Headers: {:?}", method, uri, headers);
|
||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
||||
request: Request,
|
||||
next: Next,
|
||||
) -> Response {
|
||||
let method = request.method().clone();
|
||||
let uri = request.uri().clone();
|
||||
|
||||
let start = Instant::now();
|
||||
let response = next.run(req).await;
|
||||
let response = next.run(request).await;
|
||||
let duration = start.elapsed();
|
||||
|
||||
// Log response
|
||||
info!("SERVER: {} {} - Status: {} - Duration: {:?}", method, uri, response.status(), duration);
|
||||
info!("{} {} {} {} - {:?}", addr, method, uri, response.status(), duration);
|
||||
|
||||
Ok(response)
|
||||
response
|
||||
}
|
||||
|
||||
pub fn create_auth_middleware(
|
||||
password: Option<String>,
|
||||
) -> impl Fn(ConnectInfo<SocketAddr>, Request, Next) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Response, StatusCode>> + Send>> + Clone {
|
||||
move |ConnectInfo(addr): ConnectInfo<SocketAddr>, request: Request, next: Next| {
|
||||
let password = password.clone();
|
||||
Box::pin(async move {
|
||||
let headers = request.headers().clone();
|
||||
let uri = request.uri().clone();
|
||||
|
||||
if !check_auth(&headers, &password) {
|
||||
warn!("Unauthorized request to {} from {}", uri, addr);
|
||||
return Err(StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
let response = next.run(request).await;
|
||||
Ok(response)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user