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