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