1use crate::core::hasher::PasswordHasher;
7use crate::rest_authentication::RestAuthentication;
8use crate::{AuthenticationBackend, AuthenticationError, SimpleUser, User};
9use base64::{Engine, engine::general_purpose::STANDARD};
10use reinhardt_http::Request;
11use std::collections::HashMap;
12use uuid::Uuid;
13
14struct InternalArgon2Hasher;
19
20impl PasswordHasher for InternalArgon2Hasher {
21 fn hash(&self, password: &str) -> Result<String, reinhardt_core::exception::Error> {
22 use argon2::Argon2;
23 use password_hash::{PasswordHasher as _, SaltString, rand_core::OsRng};
24
25 let salt = SaltString::generate(&mut OsRng);
26 let argon2 = Argon2::default();
27
28 argon2
29 .hash_password(password.as_bytes(), &salt)
30 .map(|hash| hash.to_string())
31 .map_err(|e| reinhardt_core::exception::Error::Authentication(e.to_string()))
32 }
33
34 fn verify(&self, password: &str, hash: &str) -> Result<bool, reinhardt_core::exception::Error> {
35 use argon2::Argon2;
36 use password_hash::{PasswordHash, PasswordVerifier};
37
38 let parsed_hash = PasswordHash::new(hash)
39 .map_err(|e| reinhardt_core::exception::Error::Authentication(e.to_string()))?;
40
41 Ok(Argon2::default()
43 .verify_password(password.as_bytes(), &parsed_hash)
44 .is_ok())
45 }
46}
47
48pub struct BasicAuthentication {
53 users: HashMap<String, String>,
55 hasher: InternalArgon2Hasher,
56}
57
58impl BasicAuthentication {
59 pub fn new() -> Self {
87 Self {
88 users: HashMap::new(),
89 hasher: InternalArgon2Hasher,
90 }
91 }
92
93 pub fn add_user(&mut self, username: impl Into<String>, password: impl Into<String>) {
134 let hash = self
135 .hasher
136 .hash(&password.into())
137 .expect("Argon2 hashing should not fail");
138 self.users.insert(username.into(), hash);
139 }
140
141 fn parse_auth_header(&self, header: &str) -> Option<(String, String)> {
143 if !header.starts_with("Basic ") {
144 return None;
145 }
146
147 let encoded = header.strip_prefix("Basic ")?;
148 let decoded = STANDARD.decode(encoded).ok()?;
149 let decoded_str = String::from_utf8(decoded).ok()?;
150
151 let parts: Vec<&str> = decoded_str.splitn(2, ':').collect();
152 if parts.len() != 2 {
153 return None;
154 }
155
156 Some((parts[0].to_string(), parts[1].to_string()))
157 }
158}
159
160impl Default for BasicAuthentication {
161 fn default() -> Self {
162 Self::new()
163 }
164}
165
166#[async_trait::async_trait]
167impl AuthenticationBackend for BasicAuthentication {
168 async fn authenticate(
169 &self,
170 request: &Request,
171 ) -> Result<Option<Box<dyn User>>, AuthenticationError> {
172 let auth_header = request
173 .headers
174 .get("Authorization")
175 .and_then(|h| h.to_str().ok());
176
177 if let Some(header) = auth_header
178 && let Some((username, password)) = self.parse_auth_header(header)
179 {
180 if let Some(stored_hash) = self.users.get(&username) {
181 let is_valid = self.hasher.verify(&password, stored_hash).unwrap_or(false);
183 if is_valid {
184 return Ok(Some(Box::new(SimpleUser {
185 id: Uuid::new_v4(),
186 username: username.clone(),
187 email: format!("{}@example.com", username),
188 is_active: true,
189 is_admin: false,
190 is_staff: false,
191 is_superuser: false,
192 })));
193 }
194 }
195 return Err(AuthenticationError::InvalidCredentials);
196 }
197
198 Ok(None)
199 }
200
201 async fn get_user(&self, user_id: &str) -> Result<Option<Box<dyn User>>, AuthenticationError> {
202 if self.users.contains_key(user_id) {
203 Ok(Some(Box::new(SimpleUser {
204 id: Uuid::new_v4(),
205 username: user_id.to_string(),
206 email: format!("{}@example.com", user_id),
207 is_active: true,
208 is_admin: false,
209 is_staff: false,
210 is_superuser: false,
211 })))
212 } else {
213 Ok(None)
214 }
215 }
216}
217
218#[async_trait::async_trait]
220impl RestAuthentication for BasicAuthentication {
221 async fn authenticate(
222 &self,
223 request: &Request,
224 ) -> Result<Option<Box<dyn User>>, AuthenticationError> {
225 AuthenticationBackend::authenticate(self, request).await
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233 use bytes::Bytes;
234 use hyper::{HeaderMap, Method};
235 use rstest::rstest;
236
237 fn create_request_with_auth(auth: &str) -> Request {
238 let mut headers = HeaderMap::new();
239 headers.insert("Authorization", auth.parse().unwrap());
240 Request::builder()
241 .method(Method::GET)
242 .uri("/")
243 .headers(headers)
244 .body(Bytes::new())
245 .build()
246 .unwrap()
247 }
248
249 #[rstest]
250 #[tokio::test]
251 async fn test_basic_auth_success() {
252 let mut backend = BasicAuthentication::new();
254 backend.add_user("testuser", "testpass");
255
256 let auth = "Basic dGVzdHVzZXI6dGVzdHBhc3M=";
258 let request = create_request_with_auth(auth);
259
260 let result = AuthenticationBackend::authenticate(&backend, &request)
262 .await
263 .unwrap();
264
265 assert!(result.is_some());
267 assert_eq!(result.unwrap().get_username(), "testuser");
268 }
269
270 #[rstest]
271 #[tokio::test]
272 async fn test_basic_auth_invalid_password() {
273 let mut backend = BasicAuthentication::new();
275 backend.add_user("testuser", "correctpass");
276
277 let auth = "Basic dGVzdHVzZXI6d3JvbmdwYXNz";
279 let request = create_request_with_auth(auth);
280
281 let result = AuthenticationBackend::authenticate(&backend, &request).await;
283
284 assert!(result.is_err());
286 }
287
288 #[rstest]
289 #[tokio::test]
290 async fn test_basic_auth_no_header() {
291 let backend = BasicAuthentication::new();
293 let request = Request::builder()
294 .method(Method::GET)
295 .uri("/")
296 .body(Bytes::new())
297 .build()
298 .unwrap();
299
300 let result = AuthenticationBackend::authenticate(&backend, &request)
302 .await
303 .unwrap();
304
305 assert!(result.is_none());
307 }
308
309 #[rstest]
310 fn test_parse_auth_header() {
311 let backend = BasicAuthentication::new();
313
314 let (user, pass) = backend.parse_auth_header("Basic dGVzdDpwYXNz").unwrap();
316
317 assert_eq!(user, "test");
319 assert_eq!(pass, "pass");
320 }
321
322 #[rstest]
323 #[tokio::test]
324 async fn test_get_user() {
325 let mut backend = BasicAuthentication::new();
327 backend.add_user("testuser", "testpass");
328
329 let user = backend.get_user("testuser").await.unwrap();
331 let no_user = backend.get_user("nonexistent").await.unwrap();
332
333 assert!(user.is_some());
335 assert_eq!(user.unwrap().get_username(), "testuser");
336 assert!(no_user.is_none());
337 }
338
339 #[rstest]
340 fn test_password_is_hashed_on_storage() {
341 let mut backend = BasicAuthentication::new();
343
344 backend.add_user("testuser", "plaintext_password");
346
347 let stored = backend.users.get("testuser").unwrap();
349 assert!(
351 stored.starts_with("$argon2"),
352 "Password should be stored as Argon2 hash, got: {}",
353 stored
354 );
355 assert_ne!(stored, "plaintext_password");
356 }
357
358 #[rstest]
359 fn test_argon2_verification_works() {
360 let hasher = InternalArgon2Hasher;
362 let password = "test_password_123";
363
364 let hash = hasher.hash(password).unwrap();
366 let valid = hasher.verify(password, &hash).unwrap();
367 let invalid = hasher.verify("wrong_password", &hash).unwrap();
368
369 assert!(valid);
371 assert!(!invalid);
372 }
373}