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 anyhow::Result;
|
||||||
use axum::http::HeaderMap;
|
use axum::{
|
||||||
use log::info;
|
extract::{Request, ConnectInfo},
|
||||||
|
http::{HeaderMap, StatusCode},
|
||||||
|
middleware::Next,
|
||||||
|
response::Response,
|
||||||
|
};
|
||||||
|
use log::{info, warn};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::net::SocketAddr;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -64,11 +70,31 @@ pub struct ListItemsQuery {
|
|||||||
pub count: Option<u32>,
|
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 {
|
pub fn check_auth(headers: &HeaderMap, password: &Option<String>) -> bool {
|
||||||
if let Some(expected_password) = password {
|
if let Some(expected_password) = password {
|
||||||
if let Some(auth_header) = headers.get("authorization") {
|
if let Some(auth_header) = headers.get("authorization") {
|
||||||
if let Ok(auth_str) = auth_header.to_str() {
|
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
|
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(
|
pub async fn logging_middleware(
|
||||||
req: axum::http::Request<axum::body::Body>,
|
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
||||||
next: axum::middleware::Next,
|
request: Request,
|
||||||
) -> Result<axum::http::Response<axum::body::Body>, axum::response::Response> {
|
next: Next,
|
||||||
let method = req.method().clone();
|
) -> Response {
|
||||||
let uri = req.uri().clone();
|
let method = request.method().clone();
|
||||||
let headers = req.headers().clone();
|
let uri = request.uri().clone();
|
||||||
|
|
||||||
// Log incoming request
|
|
||||||
info!("SERVER: {} {} - Headers: {:?}", method, uri, headers);
|
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let response = next.run(req).await;
|
let response = next.run(request).await;
|
||||||
let duration = start.elapsed();
|
let duration = start.elapsed();
|
||||||
|
|
||||||
// Log response
|
info!("{} {} {} {} - {:?}", addr, method, uri, response.status(), duration);
|
||||||
info!("SERVER: {} {} - Status: {} - Duration: {:?}", 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