mockforge_http/auth/
admin_auth.rs1use axum::body::Body;
6use axum::http::{Request, StatusCode};
7use axum::response::Response;
8use base64::{engine::general_purpose, Engine as _};
9use tracing::{debug, warn};
10
11pub fn check_admin_auth(
13 req: &Request<Body>,
14 admin_auth_required: bool,
15 admin_username: &Option<String>,
16 admin_password: &Option<String>,
17) -> Result<(), Response> {
18 if !admin_auth_required {
20 debug!("Admin auth not required, allowing access");
21 return Ok(());
22 }
23
24 let auth_header = req.headers().get("authorization").and_then(|h| h.to_str().ok());
26
27 if let Some(auth_value) = auth_header {
28 if let Some(basic_creds) = auth_value.strip_prefix("Basic ") {
30 match general_purpose::STANDARD.decode(basic_creds) {
32 Ok(decoded) => {
33 if let Ok(creds_str) = String::from_utf8(decoded) {
34 if let Some((username, password)) = creds_str.split_once(':') {
36 if let (Some(expected_user), Some(expected_pass)) =
38 (admin_username, admin_password)
39 {
40 if username == expected_user && password == expected_pass {
41 debug!("Admin authentication successful");
42 return Ok(());
43 }
44 }
45 }
46 }
47 }
48 Err(e) => {
49 warn!("Failed to decode admin credentials: {}", e);
50 }
51 }
52 }
53 }
54
55 warn!("Admin authentication failed or missing");
57 let mut res = Response::new(Body::from(
58 serde_json::json!({
59 "error": "Authentication required",
60 "message": "Admin UI requires authentication"
61 })
62 .to_string(),
63 ));
64 *res.status_mut() = StatusCode::UNAUTHORIZED;
65 res.headers_mut()
66 .insert("www-authenticate", "Basic realm=\"MockForge Admin\"".parse().unwrap());
67
68 Err(res)
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74 use axum::http::Request;
75
76 #[test]
77 fn test_admin_auth_not_required() {
78 let req = Request::builder().body(Body::empty()).unwrap();
79 assert!(check_admin_auth(&req, false, &None, &None).is_ok());
80 }
81
82 #[test]
83 fn test_admin_auth_missing() {
84 let req = Request::builder().body(Body::empty()).unwrap();
85 let username = Some("admin".to_string());
86 let password = Some("secret".to_string());
87 assert!(check_admin_auth(&req, true, &username, &password).is_err());
88 }
89
90 #[test]
91 fn test_admin_auth_valid() {
92 let username = Some("admin".to_string());
93 let password = Some("secret".to_string());
94
95 let credentials = general_purpose::STANDARD.encode("admin:secret");
97 let auth_value = format!("Basic {}", credentials);
98
99 let req = Request::builder()
100 .header("authorization", auth_value)
101 .body(Body::empty())
102 .unwrap();
103
104 assert!(check_admin_auth(&req, true, &username, &password).is_ok());
105 }
106
107 #[test]
108 fn test_admin_auth_invalid_password() {
109 let username = Some("admin".to_string());
110 let password = Some("secret".to_string());
111
112 let credentials = general_purpose::STANDARD.encode("admin:wrong");
114 let auth_value = format!("Basic {}", credentials);
115
116 let req = Request::builder()
117 .header("authorization", auth_value)
118 .body(Body::empty())
119 .unwrap();
120
121 assert!(check_admin_auth(&req, true, &username, &password).is_err());
122 }
123}