feat: Initial v0.9 release with API Key authentication
## v0.9.20260325_144654 ### Features - API Key Authentication System - Job Worker System - V2 Backup Versioning ### Bug Fixes - get_processor_results_by_job column mapping Co-authored-by: OpenCode
This commit is contained in:
120
src/api/middleware.rs
Normal file
120
src/api/middleware.rs
Normal file
@@ -0,0 +1,120 @@
|
||||
use axum::{
|
||||
extract::{Request, State},
|
||||
http::{header::HeaderMap, StatusCode},
|
||||
middleware::Next,
|
||||
response::Response,
|
||||
};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::core::db::postgres_db::ApiKeyRecord;
|
||||
use crate::core::db::PostgresDb;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ApiKeyAuth {
|
||||
pub key_id: String,
|
||||
pub record: ApiKeyRecord,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ApiState {
|
||||
pub db: Arc<PostgresDb>,
|
||||
}
|
||||
|
||||
pub async fn api_key_validation(
|
||||
State(state): State<ApiState>,
|
||||
request: Request,
|
||||
next: Next,
|
||||
) -> Response {
|
||||
tracing::info!("[MIDDLEWARE] Starting API key validation");
|
||||
tracing::info!("[MIDDLEWARE] Path: {:?}", request.uri().path());
|
||||
|
||||
let headers = request.headers();
|
||||
tracing::info!(
|
||||
"[MIDDLEWARE] Headers: {:?}",
|
||||
headers.keys().collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
let api_key = match extract_api_key(headers) {
|
||||
Ok(key) => {
|
||||
tracing::info!("[MIDDLEWARE] API key extracted, length: {}", key.len());
|
||||
key
|
||||
}
|
||||
Err(status) => {
|
||||
tracing::warn!("[MIDDLEWARE] API key extraction failed: {:?}", status);
|
||||
return Response::builder()
|
||||
.status(status)
|
||||
.body(axum::body::Body::empty())
|
||||
.unwrap();
|
||||
}
|
||||
};
|
||||
|
||||
let key_hash = hash_key(&api_key);
|
||||
tracing::info!("[MIDDLEWARE] Key hash: {}", &key_hash[..16]);
|
||||
|
||||
tracing::info!("[MIDDLEWARE] Querying database for key...");
|
||||
let record = match state.db.get_api_key_by_hash(&key_hash).await {
|
||||
Ok(Some(r)) => {
|
||||
tracing::info!("[MIDDLEWARE] API key found: {}", r.key_id);
|
||||
r
|
||||
}
|
||||
Ok(None) => {
|
||||
tracing::warn!("[MIDDLEWARE] API key not found in database");
|
||||
return Response::builder()
|
||||
.status(StatusCode::UNAUTHORIZED)
|
||||
.body(axum::body::Body::empty())
|
||||
.unwrap();
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("[MIDDLEWARE] DB error: {}", e);
|
||||
return Response::builder()
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body(axum::body::Body::empty())
|
||||
.unwrap();
|
||||
}
|
||||
};
|
||||
|
||||
if record.status != "active" {
|
||||
tracing::warn!("[MIDDLEWARE] API key not active: {}", record.status);
|
||||
return Response::builder()
|
||||
.status(StatusCode::UNAUTHORIZED)
|
||||
.body(axum::body::Body::empty())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
"[MIDDLEWARE] API key validated successfully: {}",
|
||||
record.key_id
|
||||
);
|
||||
|
||||
let auth = ApiKeyAuth {
|
||||
key_id: record.key_id.clone(),
|
||||
record,
|
||||
};
|
||||
|
||||
if let Err(e) = state.db.update_api_key_usage(&auth.key_id, None).await {
|
||||
tracing::warn!("[MIDDLEWARE] Failed to update API key usage: {}", e);
|
||||
}
|
||||
|
||||
let mut request = request;
|
||||
request.extensions_mut().insert(auth);
|
||||
|
||||
tracing::info!("[MIDDLEWARE] Passing request to handler");
|
||||
let response = next.run(request).await;
|
||||
tracing::info!("[MIDDLEWARE] Handler returned response");
|
||||
response
|
||||
}
|
||||
|
||||
fn extract_api_key(headers: &HeaderMap) -> Result<String, StatusCode> {
|
||||
headers
|
||||
.get("X-API-Key")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.map(|s| s.to_string())
|
||||
.ok_or(StatusCode::UNAUTHORIZED)
|
||||
}
|
||||
|
||||
fn hash_key(key: &str) -> String {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(key.as_bytes());
|
||||
format!("{:x}", hasher.finalize())
|
||||
}
|
||||
Reference in New Issue
Block a user