From 1170c5ee47535fd225d1b0a3ce1d154d0a47cea3 Mon Sep 17 00:00:00 2001 From: Andrew Phillips Date: Wed, 13 Aug 2025 13:00:15 -0300 Subject: [PATCH] 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) --- src/modes/server/common.rs | 75 +++++++++++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 17 deletions(-) diff --git a/src/modes/server/common.rs b/src/modes/server/common.rs index 03cf5cf..7fa5024 100644 --- a/src/modes/server/common.rs +++ b/src/modes/server/common.rs @@ -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, } +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) -> 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) -> bool { } } -// Custom middleware for logging requests and responses pub async fn logging_middleware( - req: axum::http::Request, - next: axum::middleware::Next, -) -> Result, 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, + 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, +) -> impl Fn(ConnectInfo, Request, Next) -> std::pin::Pin> + Send>> + Clone { + move |ConnectInfo(addr): ConnectInfo, 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) + }) + } }