use crate::error::AppError;
use axum::{
extract::Request,
http::{header, HeaderMap},
middleware::Next,
response::{IntoResponse, Response},
};
pub(crate) const TOKEN_HEADER: &str = "x-moadim-token";
pub(crate) const API_TOKEN_ENV: &str = "MOADIM_API_TOKEN";
pub fn api_token() -> Option<String> {
std::env::var(API_TOKEN_ENV)
.ok()
.map(|token| token.trim().to_string())
.filter(|token| !token.is_empty())
}
fn authorized(headers: &HeaderMap, token: &str) -> bool {
bearer_token(headers).is_some_and(|candidate| candidate == token)
|| header_token(headers).is_some_and(|candidate| candidate == token)
}
fn bearer_token(headers: &HeaderMap) -> Option<&str> {
let value = headers.get(header::AUTHORIZATION)?.to_str().ok()?.trim();
value.strip_prefix("Bearer ").map(str::trim)
}
fn header_token(headers: &HeaderMap) -> Option<&str> {
headers.get(TOKEN_HEADER)?.to_str().ok().map(str::trim)
}
pub async fn api_token_auth(req: Request, next: Next) -> Response {
let Some(token) = api_token() else {
return next.run(req).await;
};
if authorized(req.headers(), &token) {
next.run(req).await
} else {
AppError::Unauthorized("missing or invalid API token".to_string()).into_response()
}
}
#[cfg(test)]
#[path = "api_token_tests.rs"]
mod api_token_tests;