use super::types::AppState;
use crate::config::Config;
use axum::extract::{Request, State};
use axum::http::{HeaderValue, Method, StatusCode};
use axum::middleware::Next;
use axum::response::Response;
use subtle::ConstantTimeEq;
use tower_http::cors::{Any, CorsLayer};
fn tokens_match(allowed: &[String], presented: &str) -> bool {
let presented_bytes = presented.as_bytes();
let mut found = subtle::Choice::from(0u8);
for t in allowed {
let t_bytes = t.as_bytes();
if t_bytes.len() != presented_bytes.len() {
continue;
}
found |= t_bytes.ct_eq(presented_bytes);
}
bool::from(found)
}
pub(super) async fn require_token(
State(state): State<AppState>,
req: Request,
next: Next,
) -> Result<Response, StatusCode> {
if state.tokens.is_empty() {
return Ok(next.run(req).await);
}
let presented = extract_token(&req).ok_or(StatusCode::UNAUTHORIZED)?;
if tokens_match(&state.tokens, &presented) {
Ok(next.run(req).await)
} else {
Err(StatusCode::UNAUTHORIZED)
}
}
pub(super) async fn require_admin_token(
State(state): State<AppState>,
req: Request,
next: Next,
) -> Result<Response, StatusCode> {
if state.admin_tokens.is_empty() {
return Err(StatusCode::SERVICE_UNAVAILABLE);
}
let presented = extract_token(&req).ok_or(StatusCode::UNAUTHORIZED)?;
if tokens_match(&state.admin_tokens, &presented) {
Ok(next.run(req).await)
} else {
Err(StatusCode::UNAUTHORIZED)
}
}
fn extract_token(req: &Request) -> Option<String> {
if let Some(v) = req.headers().get("x-api-key").and_then(|v| v.to_str().ok()) {
let s = v.trim();
if !s.is_empty() {
return Some(s.to_string());
}
}
let auth = req
.headers()
.get("authorization")
.and_then(|v| v.to_str().ok())?;
let stripped = auth.strip_prefix("Bearer ")?;
Some(stripped.to_string())
}
pub(super) fn build_cors(cfg: &Config) -> CorsLayer {
let methods = [Method::GET, Method::POST, Method::OPTIONS];
if cfg.server.cors_origins.is_empty() {
CorsLayer::new()
.allow_origin(Any)
.allow_methods(methods)
.allow_headers(Any)
} else {
let origins: Vec<HeaderValue> = cfg
.server
.cors_origins
.iter()
.filter_map(|s| s.parse().ok())
.collect();
CorsLayer::new()
.allow_origin(origins)
.allow_methods(methods)
.allow_headers(Any)
}
}