1#![allow(deprecated)]
9use crate::core::hasher::PasswordHasher;
10use crate::rest_authentication::RestAuthentication;
11use crate::{AuthenticationBackend, AuthenticationError, SimpleUser, User};
12use base64::{Engine, engine::general_purpose::STANDARD};
13use reinhardt_http::Request;
14use std::collections::HashMap;
15use uuid::Uuid;
16
17struct InternalArgon2Hasher;
22
23impl PasswordHasher for InternalArgon2Hasher {
24 fn hash(&self, password: &str) -> Result<String, reinhardt_core::exception::Error> {
25 use argon2::Argon2;
26 use password_hash::{PasswordHasher as _, SaltString, rand_core::OsRng};
27
28 let salt = SaltString::generate(&mut OsRng);
29 let argon2 = Argon2::default();
30
31 argon2
32 .hash_password(password.as_bytes(), &salt)
33 .map(|hash| hash.to_string())
34 .map_err(|e| reinhardt_core::exception::Error::Authentication(e.to_string()))
35 }
36
37 fn verify(&self, password: &str, hash: &str) -> Result<bool, reinhardt_core::exception::Error> {
38 use argon2::Argon2;
39 use password_hash::{PasswordHash, PasswordVerifier};
40
41 let parsed_hash = PasswordHash::new(hash)
42 .map_err(|e| reinhardt_core::exception::Error::Authentication(e.to_string()))?;
43
44 Ok(Argon2::default()
46 .verify_password(password.as_bytes(), &parsed_hash)
47 .is_ok())
48 }
49}
50
51pub struct BasicAuthentication {
56 users: HashMap<String, String>,
58 hasher: InternalArgon2Hasher,
59}
60
61impl BasicAuthentication {
62 pub fn new() -> Self {
90 Self {
91 users: HashMap::new(),
92 hasher: InternalArgon2Hasher,
93 }
94 }
95
96 pub fn add_user(&mut self, username: impl Into<String>, password: impl Into<String>) {
137 let hash = self
138 .hasher
139 .hash(&password.into())
140 .expect("Argon2 hashing should not fail");
141 self.users.insert(username.into(), hash);
142 }
143
144 fn parse_auth_header(&self, header: &str) -> Option<(String, String)> {
146 if !header.starts_with("Basic ") {
147 return None;
148 }
149
150 let encoded = header.strip_prefix("Basic ")?;
151 let decoded = STANDARD.decode(encoded).ok()?;
152 let decoded_str = String::from_utf8(decoded).ok()?;
153
154 let parts: Vec<&str> = decoded_str.splitn(2, ':').collect();
155 if parts.len() != 2 {
156 return None;
157 }
158
159 Some((parts[0].to_string(), parts[1].to_string()))
160 }
161}
162
163impl Default for BasicAuthentication {
164 fn default() -> Self {
165 Self::new()
166 }
167}
168
169#[async_trait::async_trait]
170impl AuthenticationBackend for BasicAuthentication {
171 async fn authenticate(
172 &self,
173 request: &Request,
174 ) -> Result<Option<Box<dyn User>>, AuthenticationError> {
175 let auth_header = request
176 .headers
177 .get("Authorization")
178 .and_then(|h| h.to_str().ok());
179
180 if let Some(header) = auth_header
181 && let Some((username, password)) = self.parse_auth_header(header)
182 {
183 if let Some(stored_hash) = self.users.get(&username) {
184 let is_valid = self.hasher.verify(&password, stored_hash).unwrap_or(false);
186 if is_valid {
187 return Ok(Some(Box::new(SimpleUser {
188 id: Uuid::new_v5(&crate::USER_ID_NAMESPACE, username.as_bytes()),
189 username: username.clone(),
190 email: String::new(),
191 is_active: true,
192 is_admin: false,
193 is_staff: false,
194 is_superuser: false,
195 })));
196 }
197 }
198 return Err(AuthenticationError::InvalidCredentials);
199 }
200
201 Ok(None)
202 }
203
204 async fn get_user(&self, user_id: &str) -> Result<Option<Box<dyn User>>, AuthenticationError> {
205 if self.users.contains_key(user_id) {
206 Ok(Some(Box::new(SimpleUser {
207 id: Uuid::new_v5(&crate::USER_ID_NAMESPACE, user_id.as_bytes()),
208 username: user_id.to_string(),
209 email: String::new(),
210 is_active: true,
211 is_admin: false,
212 is_staff: false,
213 is_superuser: false,
214 })))
215 } else {
216 Ok(None)
217 }
218 }
219}
220
221#[async_trait::async_trait]
223impl RestAuthentication for BasicAuthentication {
224 async fn authenticate(
225 &self,
226 request: &Request,
227 ) -> Result<Option<Box<dyn User>>, AuthenticationError> {
228 AuthenticationBackend::authenticate(self, request).await
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236 use bytes::Bytes;
237 use hyper::{HeaderMap, Method};
238 use rstest::rstest;
239
240 fn create_request_with_auth(auth: &str) -> Request {
241 let mut headers = HeaderMap::new();
242 headers.insert("Authorization", auth.parse().unwrap());
243 Request::builder()
244 .method(Method::GET)
245 .uri("/")
246 .headers(headers)
247 .body(Bytes::new())
248 .build()
249 .unwrap()
250 }
251
252 #[rstest]
253 #[tokio::test]
254 async fn test_basic_auth_success() {
255 let mut backend = BasicAuthentication::new();
257 backend.add_user("testuser", "testpass");
258
259 let auth = "Basic dGVzdHVzZXI6dGVzdHBhc3M=";
261 let request = create_request_with_auth(auth);
262
263 let result = AuthenticationBackend::authenticate(&backend, &request)
265 .await
266 .unwrap();
267
268 assert!(result.is_some());
270 assert_eq!(result.unwrap().get_username(), "testuser");
271 }
272
273 #[rstest]
274 #[tokio::test]
275 async fn test_basic_auth_invalid_password() {
276 let mut backend = BasicAuthentication::new();
278 backend.add_user("testuser", "correctpass");
279
280 let auth = "Basic dGVzdHVzZXI6d3JvbmdwYXNz";
282 let request = create_request_with_auth(auth);
283
284 let result = AuthenticationBackend::authenticate(&backend, &request).await;
286
287 assert!(result.is_err());
289 }
290
291 #[rstest]
292 #[tokio::test]
293 async fn test_basic_auth_no_header() {
294 let backend = BasicAuthentication::new();
296 let request = Request::builder()
297 .method(Method::GET)
298 .uri("/")
299 .body(Bytes::new())
300 .build()
301 .unwrap();
302
303 let result = AuthenticationBackend::authenticate(&backend, &request)
305 .await
306 .unwrap();
307
308 assert!(result.is_none());
310 }
311
312 #[rstest]
313 fn test_parse_auth_header() {
314 let backend = BasicAuthentication::new();
316
317 let (user, pass) = backend.parse_auth_header("Basic dGVzdDpwYXNz").unwrap();
319
320 assert_eq!(user, "test");
322 assert_eq!(pass, "pass");
323 }
324
325 #[rstest]
326 #[tokio::test]
327 async fn test_get_user() {
328 let mut backend = BasicAuthentication::new();
330 backend.add_user("testuser", "testpass");
331
332 let user = backend.get_user("testuser").await.unwrap();
334 let no_user = backend.get_user("nonexistent").await.unwrap();
335
336 assert!(user.is_some());
338 assert_eq!(user.unwrap().get_username(), "testuser");
339 assert!(no_user.is_none());
340 }
341
342 #[rstest]
343 fn test_password_is_hashed_on_storage() {
344 let mut backend = BasicAuthentication::new();
346
347 backend.add_user("testuser", "plaintext_password");
349
350 let stored = backend.users.get("testuser").unwrap();
352 assert!(
354 stored.starts_with("$argon2"),
355 "Password should be stored as Argon2 hash, got: {}",
356 stored
357 );
358 assert_ne!(stored, "plaintext_password");
359 }
360
361 #[rstest]
362 #[tokio::test]
363 async fn test_authenticate_same_username_produces_same_id() {
364 let mut backend = BasicAuthentication::new();
366 backend.add_user("testuser", "testpass");
367
368 let auth = "Basic dGVzdHVzZXI6dGVzdHBhc3M=";
369 let request1 = create_request_with_auth(auth);
370 let request2 = create_request_with_auth(auth);
371
372 let user1 = AuthenticationBackend::authenticate(&backend, &request1)
374 .await
375 .unwrap()
376 .unwrap();
377 let user2 = AuthenticationBackend::authenticate(&backend, &request2)
378 .await
379 .unwrap()
380 .unwrap();
381
382 assert_eq!(
384 user1.id(),
385 user2.id(),
386 "same username must produce the same UUID"
387 );
388 }
389
390 #[rstest]
391 #[tokio::test]
392 async fn test_authenticated_user_id_is_deterministic_uuidv5() {
393 let mut backend = BasicAuthentication::new();
395 backend.add_user("testuser", "testpass");
396
397 let auth = "Basic dGVzdHVzZXI6dGVzdHBhc3M=";
398 let request = create_request_with_auth(auth);
399
400 let user = AuthenticationBackend::authenticate(&backend, &request)
402 .await
403 .unwrap()
404 .unwrap();
405 let id = Uuid::parse_str(&user.id()).unwrap();
406
407 assert_eq!(id.get_version_num(), 5, "user ID must be UUIDv5");
409 assert_eq!(
410 id.get_variant(),
411 uuid::Variant::RFC4122,
412 "user ID must use RFC 4122 variant"
413 );
414 }
415
416 #[rstest]
417 #[tokio::test]
418 async fn test_authenticated_user_has_default_privilege_flags() {
419 let mut backend = BasicAuthentication::new();
421 backend.add_user("testuser", "testpass");
422
423 let auth = "Basic dGVzdHVzZXI6dGVzdHBhc3M=";
424 let request = create_request_with_auth(auth);
425
426 let user = AuthenticationBackend::authenticate(&backend, &request)
428 .await
429 .unwrap()
430 .unwrap();
431
432 assert!(user.is_active());
434 assert!(!user.is_admin());
435 assert!(!user.is_staff());
436 assert!(!user.is_superuser());
437 }
438
439 #[rstest]
440 #[tokio::test]
441 async fn test_get_user_same_username_produces_same_id() {
442 let mut backend = BasicAuthentication::new();
444 backend.add_user("testuser", "testpass");
445
446 let user1 = backend.get_user("testuser").await.unwrap().unwrap();
448 let user2 = backend.get_user("testuser").await.unwrap().unwrap();
449
450 assert_eq!(
452 user1.id(),
453 user2.id(),
454 "same username must produce the same UUID"
455 );
456 }
457
458 #[rstest]
459 fn test_argon2_verification_works() {
460 let hasher = InternalArgon2Hasher;
462 let password = "test_password_123";
463
464 let hash = hasher.hash(password).unwrap();
466 let valid = hasher.verify(password, &hash).unwrap();
467 let invalid = hasher.verify("wrong_password", &hash).unwrap();
468
469 assert!(valid);
471 assert!(!invalid);
472 }
473}