1use std::sync::Arc;
7
8use axum::body::Body;
9use axum::extract::State;
10use axum::http::{Request, StatusCode};
11use axum::middleware::Next;
12use axum::response::{IntoResponse, Response};
13use base64::engine::general_purpose::STANDARD;
14use base64::Engine;
15
16#[derive(Clone)]
19pub struct AuthState {
20 pub username: String,
22 pub password: String,
24}
25
26pub async fn basic_auth_middleware(
35 State(auth): State<Arc<AuthState>>,
36 req: Request<Body>,
37 next: Next,
38) -> Response {
39 let path = req.uri().path();
41 if path == "/healthz" || path == "/readyz" {
42 return next.run(req).await;
43 }
44
45 let auth_header = req
46 .headers()
47 .get("authorization")
48 .and_then(|v| v.to_str().ok());
49
50 let authorized = match auth_header {
51 Some(header) if header.starts_with("Basic ") => {
52 let decoded = STANDARD.decode(&header[6..]).unwrap_or_default();
53 let credentials = String::from_utf8(decoded).unwrap_or_default();
54 let expected = format!("{}:{}", auth.username, auth.password);
55 credentials == expected
56 }
57 _ => false,
58 };
59
60 if authorized {
61 next.run(req).await
62 } else {
63 (
64 StatusCode::UNAUTHORIZED,
65 [("www-authenticate", "Basic realm=\"featherbit admin\"")],
66 "Unauthorized",
67 )
68 .into_response()
69 }
70}