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:
Andrew Phillips
2025-08-13 13:00:15 -03:00
parent b380930493
commit 1170c5ee47

View File

@@ -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)
})
}
}