use axum::http::{HeaderMap, StatusCode}; use tracing::warn; use crate::state::AppState; pub fn require_bootstrap_auth( state: &AppState, headers: &HeaderMap, ) -> Result<(), (StatusCode, String)> { if state.config.allow_unauthenticated { return Ok(()); } let expected = match state.config.bootstrap_token.as_deref() { Some(token) if !token.is_empty() => token, _ => { return Err(( StatusCode::SERVICE_UNAVAILABLE, "bootstrap token not configured".to_string(), )) } }; let provided = extract_token(headers); if provided.as_deref() == Some(expected) { return Ok(()); } Err(( StatusCode::UNAUTHORIZED, "invalid bootstrap token".to_string(), )) } pub fn require_admin_auth( state: &AppState, headers: &HeaderMap, ) -> Result<(), (StatusCode, String)> { if state.config.allow_unauthenticated { return Ok(()); } let expected = match state.config.admin_token.as_deref() { Some(token) if !token.is_empty() => token, _ => { if !state.config.allow_admin_fallback { return Err(( StatusCode::SERVICE_UNAVAILABLE, "admin token not configured".to_string(), )); } match state.config.bootstrap_token.as_deref() { Some(token) if !token.is_empty() => { warn!("admin token not configured; falling back to bootstrap token"); token } _ => { return Err(( StatusCode::SERVICE_UNAVAILABLE, "admin token not configured".to_string(), )) } } } }; let provided = extract_token(headers); if provided.as_deref() == Some(expected) { return Ok(()); } Err((StatusCode::UNAUTHORIZED, "invalid admin token".to_string())) } fn extract_token(headers: &HeaderMap) -> Option { if let Some(value) = headers.get("x-deployer-token") { if let Ok(token) = value.to_str() { let trimmed = token.trim(); if !trimmed.is_empty() { return Some(trimmed.to_string()); } } } if let Some(value) = headers.get("authorization") { if let Ok(auth) = value.to_str() { let trimmed = auth.trim(); if let Some((scheme, rest)) = trimmed.split_once(' ') { if scheme.eq_ignore_ascii_case("bearer") { let token = rest.trim(); if !token.is_empty() { return Some(token.to_string()); } } } } } None }