2026-09-05 00:31:28 -07:00
|
|
|
/// Handler middleware utilities
|
|
|
|
|
///
|
|
|
|
|
/// Centralized JWT validation + rate limiting for all HTTP handlers.
|
|
|
|
|
/// Eliminates boilerplate across endpoints, improves testability.
|
|
|
|
|
|
|
|
|
|
use actix_web::{HttpRequest, HttpResponse};
|
|
|
|
|
use serde_json::json;
|
|
|
|
|
use crate::http_server::AppState;
|
|
|
|
|
|
|
|
|
|
/// Result type for middleware operations
|
|
|
|
|
pub type MiddlewareResult<T> = Result<T, HttpResponse>;
|
|
|
|
|
|
|
|
|
|
/// Validate JWT token + check rate limit
|
|
|
|
|
///
|
|
|
|
|
/// Handles:
|
|
|
|
|
/// 1. Extract Authorization header
|
|
|
|
|
/// 2. Validate JWT (if auth enabled)
|
|
|
|
|
/// 3. Check rate limit (if limiter enabled)
|
|
|
|
|
/// 4. Return error response on failure
|
|
|
|
|
///
|
|
|
|
|
/// # Usage
|
|
|
|
|
/// ```ignore
|
|
|
|
|
/// validate_and_rate_limit(&req, &state, "compact", 10)?;
|
|
|
|
|
/// // If we get here, both JWT and rate limit checks passed
|
|
|
|
|
/// ```
|
|
|
|
|
pub fn validate_and_rate_limit(
|
|
|
|
|
req: &HttpRequest,
|
|
|
|
|
state: &AppState,
|
|
|
|
|
endpoint: &str,
|
|
|
|
|
rate_limit: u32,
|
|
|
|
|
) -> MiddlewareResult<()> {
|
|
|
|
|
// 1. JWT validation (if enabled)
|
|
|
|
|
if let Some(jwt_validator) = &state.jwt_validator {
|
|
|
|
|
let auth_header = req
|
|
|
|
|
.headers()
|
|
|
|
|
.get("Authorization")
|
|
|
|
|
.and_then(|h| h.to_str().ok())
|
|
|
|
|
.ok_or_else(|| {
|
|
|
|
|
HttpResponse::Unauthorized().json(json!({
|
|
|
|
|
"error": "Missing Authorization header"
|
|
|
|
|
}))
|
|
|
|
|
})?;
|
|
|
|
|
|
2026-09-08 01:11:14 +00:00
|
|
|
crate::jwt_validator::JwtValidator::extract_bearer_token(auth_header).map_err(|e| {
|
2026-09-05 00:31:28 -07:00
|
|
|
HttpResponse::Unauthorized().json(json!({
|
|
|
|
|
"error": format!("JWT validation failed: {}", e)
|
|
|
|
|
}))
|
|
|
|
|
})?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. Rate limiting (if enabled)
|
|
|
|
|
state
|
|
|
|
|
.rate_limiter
|
2026-09-08 01:11:14 +00:00
|
|
|
.check("default", endpoint)
|
2026-09-05 00:31:28 -07:00
|
|
|
.map_err(|e| {
|
|
|
|
|
HttpResponse::TooManyRequests().json(json!({
|
2026-09-08 01:11:14 +00:00
|
|
|
"error": format!("Rate limit exceeded: {}", e.reason())
|
2026-09-05 00:31:28 -07:00
|
|
|
}))
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_middleware_result_type_is_result() {
|
|
|
|
|
// Verify type alias works
|
|
|
|
|
let _result: MiddlewareResult<()> = Ok(());
|
|
|
|
|
let _result: MiddlewareResult<()> = Err(HttpResponse::Unauthorized().finish());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_validate_and_rate_limit_signature() {
|
|
|
|
|
// Just verify the function signature is correct (compile-time test)
|
|
|
|
|
// Runtime tests require full AppState with mocks
|
|
|
|
|
let _ = validate_and_rate_limit;
|
|
|
|
|
}
|
|
|
|
|
}
|