use axum::{
extract::{Request, State},
http::header,
middleware::Next,
response::Response,
};
use sha2::{Digest, Sha256};
use std::sync::Arc;
use subtle::ConstantTimeEq;
use crate::AppState;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AuthStatus {
Public,
Admin,
}
pub async fn auth_middleware(
State(state): State<Arc<AppState>>,
mut request: Request,
next: Next,
) -> Response {
let mut auth_status = AuthStatus::Public;
if let Some(ref auth_config) = state.config.auth
&& let Some(ref token_config) = auth_config.api_token
{
match token_config.resolve() {
Ok(expected_token) => {
if expected_token.is_empty() {
tracing::warn!("API token resolves to empty string. Admin auth disabled.");
} else {
if let Some(auth_header) = request.headers().get(header::AUTHORIZATION)
&& let Ok(auth_str) = auth_header.to_str()
&& let Some(provided_token) = auth_str.strip_prefix("Bearer ")
{
let provided_hash = Sha256::digest(provided_token.trim().as_bytes());
let expected_hash = Sha256::digest(expected_token.as_bytes());
if provided_hash.ct_eq(&expected_hash).into() {
auth_status = AuthStatus::Admin;
}
}
}
}
Err(e) => {
tracing::warn!("Failed to resolve API token: {}. Admin auth disabled.", e);
}
}
}
request.extensions_mut().insert(auth_status);
next.run(request).await
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_auth_status_equality() {
assert_eq!(AuthStatus::Public, AuthStatus::Public);
assert_eq!(AuthStatus::Admin, AuthStatus::Admin);
assert_ne!(AuthStatus::Public, AuthStatus::Admin);
}
}