use crate::error::AppError; use axum::http::HeaderMap; use subtle::ConstantTimeEq; use tracing::warn; const COOKIE_NAME: &str = "admin"; fn extract_token(headers: &HeaderMap) -> Option { if let Some(auth) = headers.get("Authorization").and_then(|h| h.to_str().ok()) { if let Some(token) = auth.strip_prefix("Bearer ") { return Some(token.to_string()); } } if let Some(cookie_header) = headers.get("Cookie").and_then(|h| h.to_str().ok()) { for part in cookie_header.split(';') { let part = part.trim(); if let Some(value) = part.strip_prefix(&format!("{}=", COOKIE_NAME)) { return Some(value.to_string()); } } } None } fn token_matches(provided: &str, expected: &str) -> bool { let a = provided.as_bytes(); let b = expected.as_bytes(); if a.len() != b.len() { // Still do a constant-time compare to make timing uniform on the same-length path. let _ = a.ct_eq(a); return false; } a.ct_eq(b).into() } pub fn is_authed(headers: &HeaderMap, admin_token: &str) -> bool { match extract_token(headers) { Some(t) => token_matches(&t, admin_token), None => false, } } pub fn check_auth(headers: &HeaderMap, admin_token: &str) -> Result<(), AppError> { if !is_authed(headers, admin_token) { warn!("Unauthorized access attempt"); return Err(AppError::Unauthorized); } Ok(()) }