feat: implement centralized logging and authentication middleware
Co-authored-by: aider (openai/andrew/openrouter/anthropic/claude-sonnet-4) <aider@aider.chat>
This commit is contained in:
@@ -16,7 +16,7 @@ mod common;
|
|||||||
mod api;
|
mod api;
|
||||||
mod pages;
|
mod pages;
|
||||||
|
|
||||||
pub use common::{ServerConfig, AppState, logging_middleware};
|
pub use common::{ServerConfig, AppState, logging_middleware, create_auth_middleware};
|
||||||
|
|
||||||
pub fn mode_server(
|
pub fn mode_server(
|
||||||
_cmd: &mut Command,
|
_cmd: &mut Command,
|
||||||
@@ -55,21 +55,21 @@ async fn run_server(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
|
// Add API, documentation, and pages routes first
|
||||||
|
.merge(api::add_routes(Router::new()))
|
||||||
|
.merge(api::add_docs_routes(Router::new()))
|
||||||
|
.merge(pages::add_routes(Router::new()))
|
||||||
|
// Apply state
|
||||||
|
.with_state(state)
|
||||||
|
// Add middleware layers (applied in reverse order)
|
||||||
.layer(axum::middleware::from_fn(logging_middleware))
|
.layer(axum::middleware::from_fn(logging_middleware))
|
||||||
|
.layer(axum::middleware::from_fn(create_auth_middleware(config.password.clone())))
|
||||||
.layer(
|
.layer(
|
||||||
ServiceBuilder::new()
|
ServiceBuilder::new()
|
||||||
.layer(TraceLayer::new_for_http())
|
.layer(TraceLayer::new_for_http())
|
||||||
.layer(CorsLayer::permissive())
|
.layer(CorsLayer::permissive())
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add API, documentation, and pages routes
|
|
||||||
let app = api::add_routes(app);
|
|
||||||
let app = api::add_docs_routes(app);
|
|
||||||
let app = pages::add_routes(app);
|
|
||||||
|
|
||||||
// Apply state to the router after all routes are added
|
|
||||||
let app = app.with_state(state);
|
|
||||||
|
|
||||||
let addr: SocketAddr = if config.address.starts_with('/') || config.address.starts_with("./") {
|
let addr: SocketAddr = if config.address.starts_with('/') || config.address.starts_with("./") {
|
||||||
// Unix socket - not supported by axum directly, fall back to TCP
|
// Unix socket - not supported by axum directly, fall back to TCP
|
||||||
warn!("Unix sockets not yet implemented, falling back to TCP on 127.0.0.1:8080");
|
warn!("Unix sockets not yet implemented, falling back to TCP on 127.0.0.1:8080");
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
use axum::{
|
use axum::{
|
||||||
extract::{ConnectInfo, Path, Query, State},
|
extract::{Path, Query, State},
|
||||||
http::{HeaderMap, StatusCode},
|
http::{StatusCode},
|
||||||
response::{Json, Response, IntoResponse},
|
response::{Json, Response, IntoResponse},
|
||||||
http::header,
|
http::header,
|
||||||
};
|
};
|
||||||
use log::warn;
|
use log::warn;
|
||||||
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::io::Read;
|
use std::io::Read;
|
||||||
@@ -14,7 +13,7 @@ use anyhow::{Result, anyhow};
|
|||||||
|
|
||||||
use crate::compression_engine::{CompressionType, get_compression_engine};
|
use crate::compression_engine::{CompressionType, get_compression_engine};
|
||||||
use crate::db;
|
use crate::db;
|
||||||
use crate::modes::server::common::{AppState, ApiResponse, ItemInfo, TagsQuery, check_auth, ListItemsQuery};
|
use crate::modes::server::common::{AppState, ApiResponse, ItemInfo, TagsQuery, ListItemsQuery};
|
||||||
|
|
||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
get,
|
get,
|
||||||
@@ -37,13 +36,7 @@ use crate::modes::server::common::{AppState, ApiResponse, ItemInfo, TagsQuery, c
|
|||||||
pub async fn handle_list_items(
|
pub async fn handle_list_items(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Query(params): Query<ListItemsQuery>,
|
Query(params): Query<ListItemsQuery>,
|
||||||
headers: HeaderMap,
|
|
||||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
|
||||||
) -> Result<Json<ApiResponse<Vec<ItemInfo>>>, StatusCode> {
|
) -> Result<Json<ApiResponse<Vec<ItemInfo>>>, StatusCode> {
|
||||||
if !check_auth(&headers, &state.password) {
|
|
||||||
warn!("Unauthorized request to /api/item/ from {}", addr);
|
|
||||||
return Err(StatusCode::UNAUTHORIZED);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut conn = state.db.lock().await;
|
let mut conn = state.db.lock().await;
|
||||||
|
|
||||||
@@ -138,13 +131,7 @@ pub async fn handle_list_items(
|
|||||||
)]
|
)]
|
||||||
pub async fn handle_post_item(
|
pub async fn handle_post_item(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
headers: HeaderMap,
|
|
||||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
|
||||||
) -> Result<Json<ApiResponse<ItemInfo>>, StatusCode> {
|
) -> Result<Json<ApiResponse<ItemInfo>>, StatusCode> {
|
||||||
if !check_auth(&headers, &state.password) {
|
|
||||||
warn!("Unauthorized request to POST /api/item/ from {}", addr);
|
|
||||||
return Err(StatusCode::UNAUTHORIZED);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is a simplified implementation
|
// This is a simplified implementation
|
||||||
// In a real implementation, you'd need to properly parse multipart/form-data
|
// In a real implementation, you'd need to properly parse multipart/form-data
|
||||||
@@ -178,17 +165,9 @@ pub async fn handle_post_item(
|
|||||||
pub async fn handle_delete_item(
|
pub async fn handle_delete_item(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(item_id): Path<i64>,
|
Path(item_id): Path<i64>,
|
||||||
headers: HeaderMap,
|
|
||||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
|
||||||
) -> Result<Json<ApiResponse<()>>, StatusCode> {
|
) -> Result<Json<ApiResponse<()>>, StatusCode> {
|
||||||
if !check_auth(&headers, &state.password) {
|
|
||||||
warn!("Unauthorized request to DELETE /api/item/{} from {}", item_id, addr);
|
|
||||||
return Err(StatusCode::UNAUTHORIZED);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate that item ID is positive to prevent path traversal issues
|
// Validate that item ID is positive to prevent path traversal issues
|
||||||
if item_id <= 0 {
|
if item_id <= 0 {
|
||||||
warn!("Invalid item ID {} from {}", item_id, addr);
|
|
||||||
return Err(StatusCode::BAD_REQUEST);
|
return Err(StatusCode::BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,10 +177,7 @@ pub async fn handle_delete_item(
|
|||||||
warn!("Failed to get item {} for deletion: {}", item_id, e);
|
warn!("Failed to get item {} for deletion: {}", item_id, e);
|
||||||
StatusCode::INTERNAL_SERVER_ERROR
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
})? {
|
})? {
|
||||||
db::delete_item(&mut *conn, item).map_err(|e| {
|
db::delete_item(&mut *conn, item).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
warn!("Failed to delete item {}: {}", item_id, e);
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let response = ApiResponse::<()> {
|
let response = ApiResponse::<()> {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -233,13 +209,7 @@ pub async fn handle_delete_item(
|
|||||||
pub async fn handle_get_item_latest(
|
pub async fn handle_get_item_latest(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Query(params): Query<TagsQuery>,
|
Query(params): Query<TagsQuery>,
|
||||||
headers: HeaderMap,
|
|
||||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
|
||||||
) -> Result<Json<ApiResponse<String>>, StatusCode> {
|
) -> Result<Json<ApiResponse<String>>, StatusCode> {
|
||||||
if !check_auth(&headers, &state.password) {
|
|
||||||
warn!("Unauthorized request to /api/item/latest/content from {}", addr);
|
|
||||||
return Err(StatusCode::UNAUTHORIZED);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut conn = state.db.lock().await;
|
let mut conn = state.db.lock().await;
|
||||||
|
|
||||||
@@ -301,17 +271,9 @@ pub async fn handle_get_item_latest(
|
|||||||
pub async fn handle_get_item(
|
pub async fn handle_get_item(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(item_id): Path<i64>,
|
Path(item_id): Path<i64>,
|
||||||
headers: HeaderMap,
|
|
||||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
|
||||||
) -> Result<Json<ApiResponse<String>>, StatusCode> {
|
) -> Result<Json<ApiResponse<String>>, StatusCode> {
|
||||||
if !check_auth(&headers, &state.password) {
|
|
||||||
warn!("Unauthorized request to /api/item/{}/content from {}", item_id, addr);
|
|
||||||
return Err(StatusCode::UNAUTHORIZED);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate that item ID is positive to prevent path traversal issues
|
// Validate that item ID is positive to prevent path traversal issues
|
||||||
if item_id <= 0 {
|
if item_id <= 0 {
|
||||||
warn!("Invalid item ID {} from {}", item_id, addr);
|
|
||||||
return Err(StatusCode::BAD_REQUEST);
|
return Err(StatusCode::BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,13 +326,7 @@ pub async fn handle_get_item(
|
|||||||
pub async fn handle_get_item_latest_content(
|
pub async fn handle_get_item_latest_content(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Query(params): Query<TagsQuery>,
|
Query(params): Query<TagsQuery>,
|
||||||
headers: HeaderMap,
|
|
||||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
|
||||||
) -> Result<Response, StatusCode> {
|
) -> Result<Response, StatusCode> {
|
||||||
if !check_auth(&headers, &state.password) {
|
|
||||||
warn!("Unauthorized request to /api/item/latest/content from {}", addr);
|
|
||||||
return Err(StatusCode::UNAUTHORIZED);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut conn = state.db.lock().await;
|
let mut conn = state.db.lock().await;
|
||||||
|
|
||||||
@@ -427,17 +383,9 @@ pub async fn handle_get_item_latest_content(
|
|||||||
pub async fn handle_get_item_content(
|
pub async fn handle_get_item_content(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(item_id): Path<i64>,
|
Path(item_id): Path<i64>,
|
||||||
headers: HeaderMap,
|
|
||||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
|
||||||
) -> Result<Response, StatusCode> {
|
) -> Result<Response, StatusCode> {
|
||||||
if !check_auth(&headers, &state.password) {
|
|
||||||
warn!("Unauthorized request to /api/item/{}/content from {}", item_id, addr);
|
|
||||||
return Err(StatusCode::UNAUTHORIZED);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate that item ID is positive to prevent path traversal issues
|
// Validate that item ID is positive to prevent path traversal issues
|
||||||
if item_id <= 0 {
|
if item_id <= 0 {
|
||||||
warn!("Invalid item ID {} from {}", item_id, addr);
|
|
||||||
return Err(StatusCode::BAD_REQUEST);
|
return Err(StatusCode::BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -539,13 +487,7 @@ async fn get_item_raw_content(item: &db::Item, data_dir: &PathBuf, conn: &mut ru
|
|||||||
pub async fn handle_get_item_latest_meta(
|
pub async fn handle_get_item_latest_meta(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Query(params): Query<TagsQuery>,
|
Query(params): Query<TagsQuery>,
|
||||||
headers: HeaderMap,
|
|
||||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
|
||||||
) -> Result<Json<ApiResponse<HashMap<String, String>>>, StatusCode> {
|
) -> Result<Json<ApiResponse<HashMap<String, String>>>, StatusCode> {
|
||||||
if !check_auth(&headers, &state.password) {
|
|
||||||
warn!("Unauthorized request to /api/item/latest/meta from {}", addr);
|
|
||||||
return Err(StatusCode::UNAUTHORIZED);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut conn = state.db.lock().await;
|
let mut conn = state.db.lock().await;
|
||||||
|
|
||||||
@@ -604,13 +546,7 @@ pub async fn handle_get_item_latest_meta(
|
|||||||
pub async fn handle_get_item_meta(
|
pub async fn handle_get_item_meta(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(item_id): Path<i64>,
|
Path(item_id): Path<i64>,
|
||||||
headers: HeaderMap,
|
|
||||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
|
||||||
) -> Result<Json<ApiResponse<HashMap<String, String>>>, StatusCode> {
|
) -> Result<Json<ApiResponse<HashMap<String, String>>>, StatusCode> {
|
||||||
if !check_auth(&headers, &state.password) {
|
|
||||||
warn!("Unauthorized request to /api/item/{}/meta from {}", item_id, addr);
|
|
||||||
return Err(StatusCode::UNAUTHORIZED);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut conn = state.db.lock().await;
|
let mut conn = state.db.lock().await;
|
||||||
|
|
||||||
|
|||||||
@@ -53,8 +53,9 @@ pub fn add_routes(router: Router<AppState>) -> Router<AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_docs_routes(router: Router<AppState>) -> Router<AppState> {
|
pub fn add_docs_routes(router: Router<AppState>) -> Router<AppState> {
|
||||||
router.merge(
|
router
|
||||||
SwaggerUi::new("/swagger-ui")
|
.merge(SwaggerUi::new("/swagger").url("/openapi.json", ApiDoc::openapi()))
|
||||||
.url("/api-docs/openapi.json", ApiDoc::openapi())
|
.route("/openapi.json", axum::routing::get(|| async {
|
||||||
)
|
axum::Json(ApiDoc::openapi())
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
use axum::{
|
use axum::{
|
||||||
extract::{ConnectInfo, State},
|
extract::State,
|
||||||
http::{HeaderMap, StatusCode},
|
http::StatusCode,
|
||||||
response::Json,
|
response::Json,
|
||||||
};
|
};
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use std::net::SocketAddr;
|
|
||||||
|
|
||||||
use crate::modes::server::common::{AppState, ApiResponse, check_auth};
|
use crate::modes::server::common::{AppState, ApiResponse};
|
||||||
use crate::common::status::{generate_status_info, StatusInfo};
|
use crate::common::status::{generate_status_info, StatusInfo};
|
||||||
use crate::meta_plugin::MetaPluginType;
|
use crate::meta_plugin::MetaPluginType;
|
||||||
|
|
||||||
@@ -24,13 +23,7 @@ use crate::meta_plugin::MetaPluginType;
|
|||||||
)]
|
)]
|
||||||
pub async fn handle_status(
|
pub async fn handle_status(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
headers: HeaderMap,
|
|
||||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
|
||||||
) -> Result<Json<ApiResponse<StatusInfo>>, StatusCode> {
|
) -> Result<Json<ApiResponse<StatusInfo>>, StatusCode> {
|
||||||
if !check_auth(&headers, &state.password) {
|
|
||||||
warn!("Unauthorized request to /api/status from {}", addr);
|
|
||||||
return Err(StatusCode::UNAUTHORIZED);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get database path
|
// Get database path
|
||||||
let db_path = state.db.lock().await.path().unwrap_or("unknown").to_string();
|
let db_path = state.db.lock().await.path().unwrap_or("unknown").to_string();
|
||||||
|
|||||||
Reference in New Issue
Block a user