1use axum::{extract::State, http::StatusCode, response::Json};
16use bcrypt::{hash, verify, DEFAULT_COST};
17use chrono::{Duration, Utc};
18use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
19use serde::{Deserialize, Serialize};
20use std::collections::HashMap;
21use std::sync::Arc;
22use std::time::{Duration as StdDuration, Instant};
23use tokio::sync::RwLock;
24
25use crate::handlers::AdminState;
26use crate::models::ApiResponse;
27use crate::rbac::UserContext;
28use mockforge_collab::models::UserRole;
29
30mod password_policy;
31pub use password_policy::{PasswordPolicy, PasswordValidationError};
32
33const MIN_JWT_SECRET_LEN: usize = 32;
34
35fn is_truthy_env(name: &str) -> bool {
36 matches!(
37 std::env::var(name).ok().as_deref().map(str::to_ascii_lowercase).as_deref(),
38 Some("1") | Some("true") | Some("yes") | Some("on")
39 )
40}
41
42fn is_development_environment() -> bool {
43 if cfg!(test) {
44 return true;
45 }
46
47 matches!(
48 std::env::var("ENVIRONMENT")
49 .unwrap_or_else(|_| "production".to_string())
50 .to_ascii_lowercase()
51 .as_str(),
52 "development" | "dev" | "local"
53 )
54}
55
56fn is_dev_auth_enabled() -> bool {
57 cfg!(test) || is_truthy_env("MOCKFORGE_ENABLE_DEV_AUTH")
58}
59
60fn should_seed_default_users() -> bool {
61 let explicit_dev = is_development_environment() && is_dev_auth_enabled();
64 let no_production_auth = !is_development_environment() && std::env::var("JWT_SECRET").is_err();
65 (explicit_dev || no_production_auth) && !is_truthy_env("MOCKFORGE_DISABLE_DEV_SEED_USERS")
66}
67
68fn get_jwt_secret_bytes() -> Result<Vec<u8>, jsonwebtoken::errors::Error> {
69 if cfg!(test) {
70 return Ok(b"test-jwt-secret-which-is-long-enough".to_vec());
71 }
72
73 if let Ok(secret) = std::env::var("JWT_SECRET") {
74 if secret.len() < MIN_JWT_SECRET_LEN {
75 tracing::error!(
76 "JWT_SECRET is too short ({} chars). Minimum required is {} chars.",
77 secret.len(),
78 MIN_JWT_SECRET_LEN
79 );
80 return Err(jsonwebtoken::errors::Error::from(
81 jsonwebtoken::errors::ErrorKind::InvalidToken,
82 ));
83 }
84 return Ok(secret.into_bytes());
85 }
86
87 if is_development_environment() && is_dev_auth_enabled() {
88 let dev_secret = std::env::var("MOCKFORGE_DEV_JWT_SECRET")
89 .unwrap_or_else(|_| "mockforge-dev-only-secret-change-me-12345".to_string());
90 tracing::warn!(
91 "Using development JWT secret fallback. Set JWT_SECRET for production-like testing."
92 );
93 return Ok(dev_secret.into_bytes());
94 }
95
96 tracing::warn!(
98 "Using auto-generated JWT secret for in-memory auth. Set JWT_SECRET for production use."
99 );
100 Ok("mockforge-auto-jwt-secret-for-admin-ui-1234".as_bytes().to_vec())
101}
102
103pub fn validate_auth_config_on_startup() -> Result<(), String> {
104 if !is_development_environment() && !is_truthy_env("MOCKFORGE_ALLOW_INMEMORY_AUTH") {
105 tracing::warn!(
107 "No production auth backend configured. Using in-memory auth. \
108 Set ENVIRONMENT=production and configure a real auth backend for production use, \
109 or suppress this warning with MOCKFORGE_ALLOW_INMEMORY_AUTH=true"
110 );
111 }
112
113 get_jwt_secret_bytes()
114 .map(|_| ())
115 .map_err(|_| "JWT_SECRET is missing or invalid for current environment".to_string())
116}
117
118#[derive(Debug, Serialize, Deserialize)]
120pub struct Claims {
121 pub sub: String,
123 pub username: String,
125 pub role: String,
127 pub email: Option<String>,
129 pub iat: i64,
131 pub exp: i64,
133}
134
135#[derive(Debug, Deserialize)]
137pub struct LoginRequest {
138 pub username: String,
139 pub password: String,
140}
141
142#[derive(Debug, Serialize)]
144pub struct LoginResponse {
145 pub token: String,
146 pub refresh_token: String,
147 pub user: UserInfo,
148 pub expires_in: i64,
149}
150
151#[derive(Debug, Serialize, Clone)]
153pub struct UserInfo {
154 pub id: String,
155 pub username: String,
156 pub role: String,
157 pub email: Option<String>,
158}
159
160#[derive(Debug, Deserialize)]
162pub struct RefreshTokenRequest {
163 pub refresh_token: String,
164}
165
166#[derive(Debug, Clone)]
168pub struct UserStore {
169 users: Arc<RwLock<HashMap<String, User>>>,
170 rate_limiter: RateLimiter,
171 account_lockout: AccountLockout,
172 #[allow(dead_code)]
173 password_policy: PasswordPolicy,
174}
175
176#[derive(Debug, Clone)]
177pub(crate) struct User {
178 id: String,
179 username: String,
180 password_hash: String, role: UserRole,
182 email: Option<String>,
183}
184
185#[derive(Debug, Clone)]
187struct RateLimiter {
188 attempts: Arc<RwLock<HashMap<String, Vec<Instant>>>>,
189 max_attempts: usize,
190 window_seconds: u64,
191}
192
193#[derive(Debug, Clone)]
195struct AccountLockout {
196 #[allow(clippy::type_complexity)]
198 failed_attempts: Arc<RwLock<HashMap<String, (usize, Option<Instant>)>>>,
199 max_failed_attempts: usize,
201 lockout_duration_seconds: u64,
203}
204
205impl AccountLockout {
206 fn new(max_failed_attempts: usize, lockout_duration_seconds: u64) -> Self {
207 Self {
208 failed_attempts: Arc::new(RwLock::new(HashMap::new())),
209 max_failed_attempts,
210 lockout_duration_seconds,
211 }
212 }
213
214 async fn is_locked(&self, username: &str) -> bool {
216 let attempts = self.failed_attempts.read().await;
217 if let Some((count, locked_until)) = attempts.get(username) {
218 if *count >= self.max_failed_attempts {
219 if let Some(until) = locked_until {
220 return until > &Instant::now();
221 }
222 }
223 }
224 false
225 }
226
227 async fn record_failure(&self, username: &str) {
229 let mut attempts = self.failed_attempts.write().await;
230 let entry = attempts.entry(username.to_string()).or_insert((0, None));
231 entry.0 += 1;
232
233 if entry.0 >= self.max_failed_attempts {
234 entry.1 = Some(Instant::now() + StdDuration::from_secs(self.lockout_duration_seconds));
235 tracing::warn!("Account locked: {} ({} failed attempts)", username, entry.0);
236 }
237 }
238
239 async fn reset(&self, username: &str) {
241 let mut attempts = self.failed_attempts.write().await;
242 attempts.remove(username);
243 }
244
245 #[allow(clippy::collapsible_match)]
247 async fn remaining_lockout_time(&self, username: &str) -> Option<u64> {
248 let attempts = self.failed_attempts.read().await;
249 if let Some((_, locked_until)) = attempts.get(username) {
250 if let Some(until) = locked_until {
251 let now = Instant::now();
252 if until > &now {
253 return Some(until.duration_since(now).as_secs());
254 }
255 }
256 }
257 None
258 }
259}
260
261impl RateLimiter {
262 fn new(max_attempts: usize, window_seconds: u64) -> Self {
263 Self {
264 attempts: Arc::new(RwLock::new(HashMap::new())),
265 max_attempts,
266 window_seconds,
267 }
268 }
269
270 async fn check_rate_limit(&self, key: &str) -> Result<(), String> {
271 let mut attempts = self.attempts.write().await;
272 let now = Instant::now();
273 let window = StdDuration::from_secs(self.window_seconds);
274
275 if let Some(attempt_times) = attempts.get_mut(key) {
277 attempt_times.retain(|&time| now.duration_since(time) < window);
278
279 if attempt_times.len() >= self.max_attempts {
280 return Err(format!(
281 "Too many login attempts. Please try again in {} seconds.",
282 self.window_seconds
283 ));
284 }
285 }
286
287 attempts.entry(key.to_string()).or_insert_with(Vec::new).push(now);
289
290 Ok(())
291 }
292
293 async fn reset_rate_limit(&self, key: &str) {
294 let mut attempts = self.attempts.write().await;
295 attempts.remove(key);
296 }
297}
298
299impl Default for UserStore {
300 fn default() -> Self {
301 Self::new()
302 }
303}
304
305impl UserStore {
306 pub fn new() -> Self {
307 let users = Arc::new(RwLock::new(HashMap::new()));
308 let rate_limiter = RateLimiter::new(5, 300); let account_lockout = AccountLockout::new(5, 900); let password_policy = PasswordPolicy::default(); let store = Self {
313 users,
314 rate_limiter,
315 account_lockout,
316 password_policy,
317 };
318
319 if should_seed_default_users() {
320 let default_users = vec![
322 ("admin", "admin123", UserRole::Admin, "admin@mockforge.dev"),
323 ("viewer", "viewer123", UserRole::Viewer, "viewer@mockforge.dev"),
324 ("editor", "editor123", UserRole::Editor, "editor@mockforge.dev"),
325 ];
326
327 let store_clone = store.clone();
328 tokio::spawn(async move {
329 let mut users = store_clone.users.write().await;
330 for (username, password, role, email) in default_users {
331 if let Ok(password_hash) = hash(password, DEFAULT_COST) {
332 let user = User {
333 id: format!("{}-001", username),
334 username: username.to_string(),
335 password_hash,
336 role,
337 email: Some(email.to_string()),
338 };
339 users.insert(username.to_string(), user);
340 } else {
341 tracing::error!("Failed to hash password for user: {}", username);
342 }
343 }
344 });
345 }
346
347 store
348 }
349
350 pub(crate) async fn authenticate(
351 &self,
352 username: &str,
353 password: &str,
354 ) -> Result<User, String> {
355 if self.account_lockout.is_locked(username).await {
357 if let Some(remaining) = self.account_lockout.remaining_lockout_time(username).await {
358 return Err(format!(
359 "Account is locked due to too many failed login attempts. Please try again in {} seconds.",
360 remaining
361 ));
362 }
363 }
364
365 self.rate_limiter.check_rate_limit(username).await?;
367
368 let users = self.users.read().await;
369 if let Some(user) = users.get(username) {
370 match verify(password, &user.password_hash) {
372 Ok(true) => {
373 self.rate_limiter.reset_rate_limit(username).await;
375 self.account_lockout.reset(username).await;
376 Ok(user.clone())
377 }
378 Ok(false) => {
379 self.account_lockout.record_failure(username).await;
381 Err("Invalid username or password".to_string())
382 }
383 Err(e) => {
384 tracing::error!("Password verification error: {}", e);
385 Err("Authentication error".to_string())
386 }
387 }
388 } else {
389 Err("Invalid username or password".to_string())
391 }
392 }
393
394 #[allow(dead_code)]
396 pub(crate) async fn create_user(
397 &self,
398 username: String,
399 password: String,
400 role: UserRole,
401 email: Option<String>,
402 ) -> Result<User, String> {
403 #[cfg(feature = "password-policy")]
405 {
406 self.password_policy
407 .validate(&password, Some(&username))
408 .map_err(|e| e.to_string())?;
409 }
410
411 let mut users = self.users.write().await;
413 if users.contains_key(&username) {
414 return Err("Username already exists".to_string());
415 }
416
417 let password_hash =
419 hash(&password, DEFAULT_COST).map_err(|e| format!("Failed to hash password: {}", e))?;
420
421 let user = User {
423 id: format!("{}-{}", username, uuid::Uuid::new_v4()),
424 username: username.clone(),
425 password_hash,
426 role,
427 email,
428 };
429
430 users.insert(username, user.clone());
431 Ok(user)
432 }
433
434 pub(crate) async fn get_user_by_id(&self, user_id: &str) -> Option<User> {
435 let users = self.users.read().await;
436 users.values().find(|u| u.id == user_id).cloned()
437 }
438}
439
440static GLOBAL_USER_STORE: std::sync::OnceLock<Arc<UserStore>> = std::sync::OnceLock::new();
442
443static REVOKED_TOKENS: std::sync::OnceLock<Arc<RwLock<HashMap<String, i64>>>> =
446 std::sync::OnceLock::new();
447
448fn get_revoked_tokens() -> Arc<RwLock<HashMap<String, i64>>> {
450 REVOKED_TOKENS.get_or_init(|| Arc::new(RwLock::new(HashMap::new()))).clone()
451}
452
453pub async fn revoke_token(token: &str) {
455 let exp = validate_token(token)
457 .map(|c| c.exp)
458 .unwrap_or_else(|_| Utc::now().timestamp() + 7 * 24 * 60 * 60);
459 let store = get_revoked_tokens();
460 let mut revoked = store.write().await;
461 revoked.insert(token.to_string(), exp);
462 let now = Utc::now().timestamp();
464 revoked.retain(|_, &mut exp_time| exp_time > now);
465}
466
467pub async fn is_token_revoked(token: &str) -> bool {
469 let store = get_revoked_tokens();
470 let revoked = store.read().await;
471 revoked.contains_key(token)
472}
473
474pub fn init_global_user_store() -> Arc<UserStore> {
476 if let Err(e) = validate_auth_config_on_startup() {
477 panic!("Authentication startup validation failed: {}", e);
478 }
479 GLOBAL_USER_STORE.get_or_init(|| Arc::new(UserStore::new())).clone()
480}
481
482pub fn get_global_user_store() -> Option<Arc<UserStore>> {
484 GLOBAL_USER_STORE.get().cloned()
485}
486
487pub(crate) fn generate_token(
489 user: &User,
490 expires_in_seconds: i64,
491) -> Result<String, jsonwebtoken::errors::Error> {
492 let now = Utc::now();
493 let exp = now + Duration::seconds(expires_in_seconds);
494 let secret = get_jwt_secret_bytes()?;
495
496 let claims = Claims {
497 sub: user.id.clone(),
498 username: user.username.clone(),
499 role: format!("{:?}", user.role).to_lowercase(),
500 email: user.email.clone(),
501 iat: now.timestamp(),
502 exp: exp.timestamp(),
503 };
504
505 encode(&Header::default(), &claims, &EncodingKey::from_secret(&secret))
506}
507
508pub(crate) fn generate_refresh_token(user: &User) -> Result<String, jsonwebtoken::errors::Error> {
510 generate_token(user, 7 * 24 * 60 * 60)
512}
513
514pub fn validate_token(token: &str) -> Result<Claims, jsonwebtoken::errors::Error> {
516 let secret = get_jwt_secret_bytes()?;
517 let token_data =
518 decode::<Claims>(token, &DecodingKey::from_secret(&secret), &Validation::default())?;
519
520 Ok(token_data.claims)
521}
522
523pub async fn login(
525 State(_state): State<AdminState>,
526 Json(request): Json<LoginRequest>,
527) -> Result<Json<ApiResponse<LoginResponse>>, StatusCode> {
528 let user_store = get_global_user_store().ok_or_else(|| {
529 tracing::error!("User store not initialized");
530 StatusCode::INTERNAL_SERVER_ERROR
531 })?;
532
533 let user =
535 user_store
536 .authenticate(&request.username, &request.password)
537 .await
538 .map_err(|e| {
539 tracing::warn!("Authentication failed for user {}: {}", request.username, e);
540 if e.contains("Too many") {
542 StatusCode::TOO_MANY_REQUESTS
543 } else {
544 StatusCode::UNAUTHORIZED
545 }
546 })?;
547
548 let access_token = generate_token(&user, 24 * 60 * 60) .map_err(|e| {
551 tracing::error!("Failed to generate access token: {}", e);
552 StatusCode::INTERNAL_SERVER_ERROR
553 })?;
554
555 let refresh_token = generate_refresh_token(&user).map_err(|e| {
556 tracing::error!("Failed to generate refresh token: {}", e);
557 StatusCode::INTERNAL_SERVER_ERROR
558 })?;
559
560 let user_info = UserInfo {
561 id: user.id,
562 username: user.username,
563 role: format!("{:?}", user.role).to_lowercase(),
564 email: user.email,
565 };
566
567 Ok(Json(ApiResponse::success(LoginResponse {
568 token: access_token,
569 refresh_token,
570 user: user_info,
571 expires_in: 24 * 60 * 60,
572 })))
573}
574
575pub async fn refresh_token(
577 State(_state): State<AdminState>,
578 Json(request): Json<RefreshTokenRequest>,
579) -> Result<Json<ApiResponse<LoginResponse>>, StatusCode> {
580 if is_token_revoked(&request.refresh_token).await {
582 tracing::warn!("Attempt to use revoked refresh token");
583 return Err(StatusCode::UNAUTHORIZED);
584 }
585
586 let claims = validate_token(&request.refresh_token).map_err(|_| {
588 tracing::warn!("Invalid refresh token");
589 StatusCode::UNAUTHORIZED
590 })?;
591
592 let user_store = get_global_user_store().ok_or_else(|| {
593 tracing::error!("User store not initialized");
594 StatusCode::INTERNAL_SERVER_ERROR
595 })?;
596
597 let user = user_store.get_user_by_id(&claims.sub).await.ok_or_else(|| {
599 tracing::warn!("User not found: {}", claims.sub);
600 StatusCode::UNAUTHORIZED
601 })?;
602
603 let access_token = generate_token(&user, 24 * 60 * 60) .map_err(|e| {
606 tracing::error!("Failed to generate access token: {}", e);
607 StatusCode::INTERNAL_SERVER_ERROR
608 })?;
609
610 let refresh_token = generate_refresh_token(&user).map_err(|e| {
611 tracing::error!("Failed to generate refresh token: {}", e);
612 StatusCode::INTERNAL_SERVER_ERROR
613 })?;
614
615 let user_info = UserInfo {
616 id: user.id,
617 username: user.username,
618 role: format!("{:?}", user.role).to_lowercase(),
619 email: user.email,
620 };
621
622 Ok(Json(ApiResponse::success(LoginResponse {
623 token: access_token,
624 refresh_token,
625 user: user_info,
626 expires_in: 24 * 60 * 60,
627 })))
628}
629
630pub async fn get_current_user(
632 headers: axum::http::HeaderMap,
633) -> Result<Json<ApiResponse<UserInfo>>, StatusCode> {
634 let auth_header = headers
635 .get("authorization")
636 .and_then(|h| h.to_str().ok())
637 .ok_or(StatusCode::UNAUTHORIZED)?;
638
639 let token = auth_header.strip_prefix("Bearer ").ok_or(StatusCode::UNAUTHORIZED)?;
640
641 let claims = validate_token(token).map_err(|_| StatusCode::UNAUTHORIZED)?;
642 let role = claims_to_user_context(&claims).role;
643
644 Ok(Json(ApiResponse::success(UserInfo {
645 id: claims.sub,
646 username: claims.username,
647 role: format!("{:?}", role).to_lowercase(),
648 email: claims.email,
649 })))
650}
651
652pub async fn logout(
654 headers: axum::http::HeaderMap,
655 State(_state): State<AdminState>,
656) -> Json<ApiResponse<String>> {
657 if let Some(auth_header) = headers.get("authorization").and_then(|h| h.to_str().ok()) {
659 if let Some(token) = auth_header.strip_prefix("Bearer ") {
660 revoke_token(token).await;
661 tracing::info!("Token revoked on logout");
662 }
663 }
664 Json(ApiResponse::success("Logged out successfully".to_string()))
665}
666
667pub fn claims_to_user_context(claims: &Claims) -> UserContext {
669 let role = match claims.role.as_str() {
670 "admin" => UserRole::Admin,
671 "editor" => UserRole::Editor,
672 "viewer" => UserRole::Viewer,
673 _ => UserRole::Viewer,
674 };
675
676 UserContext {
677 user_id: claims.sub.clone(),
678 username: claims.username.clone(),
679 role,
680 email: claims.email.clone(),
681 }
682}
683
684#[cfg(test)]
685mod tests {
686 use super::*;
687
688 #[tokio::test]
689 async fn test_user_store_creation() {
690 let store = UserStore::new();
691 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
693
694 let result = store.authenticate("admin", "admin123").await;
696 assert!(result.is_ok(), "Admin user should exist");
697 }
698
699 #[tokio::test]
700 async fn test_user_store_default() {
701 let store = UserStore::default();
702 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
703
704 let result = store.authenticate("admin", "admin123").await;
705 assert!(result.is_ok());
706 }
707
708 #[tokio::test]
709 async fn test_authenticate_success() {
710 let store = UserStore::new();
711 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
712
713 let result = store.authenticate("admin", "admin123").await;
714 assert!(result.is_ok());
715
716 let user = result.unwrap();
717 assert_eq!(user.username, "admin");
718 assert!(matches!(user.role, UserRole::Admin));
719 }
720
721 #[tokio::test]
722 async fn test_authenticate_wrong_password() {
723 let store = UserStore::new();
724 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
725
726 let result = store.authenticate("admin", "wrongpassword").await;
727 assert!(result.is_err());
728 assert_eq!(result.unwrap_err(), "Invalid username or password");
729 }
730
731 #[tokio::test]
732 async fn test_authenticate_nonexistent_user() {
733 let store = UserStore::new();
734 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
735
736 let result = store.authenticate("nonexistent", "password").await;
737 assert!(result.is_err());
738 assert_eq!(result.unwrap_err(), "Invalid username or password");
739 }
740
741 #[tokio::test]
742 async fn test_rate_limiting() {
743 let store = UserStore::new();
744 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
745
746 for _ in 0..5 {
748 let _ = store.authenticate("admin", "wrongpassword").await;
749 }
750
751 let result = store.authenticate("admin", "wrongpassword").await;
753 assert!(result.is_err());
754 let error = result.unwrap_err();
755 assert!(
756 error.contains("Too many") || error.contains("locked"),
757 "Expected rate limit or lockout error, got: {}",
758 error
759 );
760 }
761
762 #[tokio::test]
763 async fn test_account_lockout_after_failures() {
764 let store = UserStore::new();
765 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
766
767 for _ in 0..5 {
769 let _ = store.authenticate("editor", "wrongpassword").await;
770 }
771
772 let result = store.authenticate("editor", "editor123").await;
774 assert!(result.is_err());
775 let error = result.unwrap_err();
776 assert!(
777 error.contains("locked") || error.contains("Too many"),
778 "Expected lockout error, got: {}",
779 error
780 );
781 }
782
783 #[tokio::test]
784 async fn test_account_lockout_reset_on_success() {
785 let store = UserStore::new();
786 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
787
788 for _ in 0..2 {
790 let _ = store.authenticate("viewer", "wrongpassword").await;
791 }
792
793 let result = store.authenticate("viewer", "viewer123").await;
795 assert!(result.is_ok());
796
797 for _ in 0..2 {
799 let _ = store.authenticate("viewer", "wrongpassword").await;
800 }
801
802 let result = store.authenticate("viewer", "wrongpassword").await;
804 assert!(result.is_err());
805 assert!(!result.unwrap_err().contains("locked"));
806 }
807
808 #[tokio::test]
809 async fn test_create_user_success() {
810 let store = UserStore::new();
811 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
812
813 let result = store
814 .create_user(
815 "newuser".to_string(),
816 "NewP@ssw0rd123".to_string(),
817 UserRole::Editor,
818 Some("newuser@example.com".to_string()),
819 )
820 .await;
821
822 assert!(result.is_ok());
823 let user = result.unwrap();
824 assert_eq!(user.username, "newuser");
825 assert!(matches!(user.role, UserRole::Editor));
826 assert_eq!(user.email, Some("newuser@example.com".to_string()));
827
828 let auth_result = store.authenticate("newuser", "NewP@ssw0rd123").await;
830 assert!(auth_result.is_ok());
831 }
832
833 #[tokio::test]
834 async fn test_create_user_duplicate_username() {
835 let store = UserStore::new();
836 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
837
838 let result = store
839 .create_user("admin".to_string(), "NewP@ssw0rd123".to_string(), UserRole::Editor, None)
840 .await;
841
842 assert!(result.is_err());
843 assert_eq!(result.unwrap_err(), "Username already exists");
844 }
845
846 #[tokio::test]
847 async fn test_get_user_by_id() {
848 let store = UserStore::new();
849 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
850
851 let auth_result = store.authenticate("admin", "admin123").await.unwrap();
853 let user_id = auth_result.id.clone();
854
855 let result = store.get_user_by_id(&user_id).await;
857 assert!(result.is_some());
858
859 let user = result.unwrap();
860 assert_eq!(user.id, user_id);
861 assert_eq!(user.username, "admin");
862 }
863
864 #[tokio::test]
865 async fn test_get_user_by_id_not_found() {
866 let store = UserStore::new();
867 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
868
869 let result = store.get_user_by_id("nonexistent-id").await;
870 assert!(result.is_none());
871 }
872
873 #[test]
874 fn test_generate_token_success() {
875 let user = User {
876 id: "test-id".to_string(),
877 username: "testuser".to_string(),
878 password_hash: "hash".to_string(),
879 role: UserRole::Editor,
880 email: Some("test@example.com".to_string()),
881 };
882
883 let result = generate_token(&user, 3600);
884 assert!(result.is_ok());
885
886 let token = result.unwrap();
887 assert!(!token.is_empty());
888 }
889
890 #[test]
891 fn test_generate_refresh_token() {
892 let user = User {
893 id: "test-id".to_string(),
894 username: "testuser".to_string(),
895 password_hash: "hash".to_string(),
896 role: UserRole::Editor,
897 email: None,
898 };
899
900 let result = generate_refresh_token(&user);
901 assert!(result.is_ok());
902
903 let token = result.unwrap();
904 assert!(!token.is_empty());
905 }
906
907 #[test]
908 fn test_validate_token_success() {
909 let user = User {
910 id: "test-id".to_string(),
911 username: "testuser".to_string(),
912 password_hash: "hash".to_string(),
913 role: UserRole::Viewer,
914 email: Some("test@example.com".to_string()),
915 };
916
917 let token = generate_token(&user, 3600).unwrap();
918 let result = validate_token(&token);
919
920 assert!(result.is_ok());
921 let claims = result.unwrap();
922 assert_eq!(claims.sub, "test-id");
923 assert_eq!(claims.username, "testuser");
924 assert_eq!(claims.role, "viewer");
925 assert_eq!(claims.email, Some("test@example.com".to_string()));
926 }
927
928 #[test]
929 fn test_validate_token_invalid() {
930 let result = validate_token("invalid.token.here");
931 assert!(result.is_err());
932 }
933
934 #[test]
935 fn test_validate_token_expired() {
936 let user = User {
937 id: "test-id".to_string(),
938 username: "testuser".to_string(),
939 password_hash: "hash".to_string(),
940 role: UserRole::Editor,
941 email: None,
942 };
943
944 let token = generate_token(&user, -120).unwrap();
946 let result = validate_token(&token);
947
948 assert!(result.is_err());
950 }
951
952 #[test]
953 fn test_claims_serialization() {
954 let claims = Claims {
955 sub: "user123".to_string(),
956 username: "testuser".to_string(),
957 role: "admin".to_string(),
958 email: Some("test@example.com".to_string()),
959 iat: 1234567890,
960 exp: 1234567890 + 3600,
961 };
962
963 let serialized = serde_json::to_string(&claims).unwrap();
964 let deserialized: Claims = serde_json::from_str(&serialized).unwrap();
965
966 assert_eq!(deserialized.sub, claims.sub);
967 assert_eq!(deserialized.username, claims.username);
968 assert_eq!(deserialized.role, claims.role);
969 assert_eq!(deserialized.email, claims.email);
970 }
971
972 #[test]
973 fn test_claims_to_user_context() {
974 let claims = Claims {
975 sub: "user123".to_string(),
976 username: "testuser".to_string(),
977 role: "editor".to_string(),
978 email: Some("test@example.com".to_string()),
979 iat: 1234567890,
980 exp: 1234567890 + 3600,
981 };
982
983 let context = claims_to_user_context(&claims);
984 assert_eq!(context.user_id, "user123");
985 assert_eq!(context.username, "testuser");
986 assert_eq!(context.role, UserRole::Editor);
987 assert_eq!(context.email, Some("test@example.com".to_string()));
988 }
989
990 #[test]
991 fn test_claims_to_user_context_unknown_role_defaults_to_viewer() {
992 let claims = Claims {
993 sub: "user123".to_string(),
994 username: "testuser".to_string(),
995 role: "unknown".to_string(),
996 email: None,
997 iat: 1234567890,
998 exp: 1234567890 + 3600,
999 };
1000
1001 let context = claims_to_user_context(&claims);
1002 assert_eq!(context.role, UserRole::Viewer);
1003 }
1004
1005 #[test]
1006 fn test_login_request_deserialization() {
1007 let json = r#"{"username": "testuser", "password": "testpass"}"#;
1008 let request: LoginRequest = serde_json::from_str(json).unwrap();
1009 assert_eq!(request.username, "testuser");
1010 assert_eq!(request.password, "testpass");
1011 }
1012
1013 #[test]
1014 fn test_refresh_token_request_deserialization() {
1015 let json = r#"{"refresh_token": "token123"}"#;
1016 let request: RefreshTokenRequest = serde_json::from_str(json).unwrap();
1017 assert_eq!(request.refresh_token, "token123");
1018 }
1019
1020 #[test]
1021 fn test_user_info_serialization() {
1022 let user_info = UserInfo {
1023 id: "user123".to_string(),
1024 username: "testuser".to_string(),
1025 role: "admin".to_string(),
1026 email: Some("test@example.com".to_string()),
1027 };
1028
1029 let serialized = serde_json::to_string(&user_info).unwrap();
1030 assert!(serialized.contains("user123"));
1031 assert!(serialized.contains("testuser"));
1032 assert!(serialized.contains("admin"));
1033 }
1034
1035 #[test]
1036 fn test_login_response_serialization() {
1037 let user_info = UserInfo {
1038 id: "user123".to_string(),
1039 username: "testuser".to_string(),
1040 role: "editor".to_string(),
1041 email: None,
1042 };
1043
1044 let response = LoginResponse {
1045 token: "access.token.here".to_string(),
1046 refresh_token: "refresh.token.here".to_string(),
1047 user: user_info,
1048 expires_in: 3600,
1049 };
1050
1051 let serialized = serde_json::to_string(&response).unwrap();
1052 assert!(serialized.contains("access.token.here"));
1053 assert!(serialized.contains("refresh.token.here"));
1054 assert!(serialized.contains("3600"));
1055 }
1056
1057 #[tokio::test]
1058 async fn test_rate_limiter_creation() {
1059 let limiter = RateLimiter::new(5, 60);
1060 let result = limiter.check_rate_limit("test-key").await;
1061 assert!(result.is_ok());
1062 }
1063
1064 #[tokio::test]
1065 async fn test_rate_limiter_exceeds_limit() {
1066 let limiter = RateLimiter::new(3, 60);
1067
1068 for _ in 0..3 {
1070 assert!(limiter.check_rate_limit("test-key").await.is_ok());
1071 }
1072
1073 let result = limiter.check_rate_limit("test-key").await;
1075 assert!(result.is_err());
1076 assert!(result.unwrap_err().contains("Too many"));
1077 }
1078
1079 #[tokio::test]
1080 async fn test_rate_limiter_reset() {
1081 let limiter = RateLimiter::new(3, 60);
1082
1083 for _ in 0..3 {
1085 limiter.check_rate_limit("test-key").await.ok();
1086 }
1087
1088 limiter.reset_rate_limit("test-key").await;
1090
1091 let result = limiter.check_rate_limit("test-key").await;
1093 assert!(result.is_ok());
1094 }
1095
1096 #[tokio::test]
1097 async fn test_rate_limiter_different_keys() {
1098 let limiter = RateLimiter::new(2, 60);
1099
1100 for _ in 0..2 {
1102 limiter.check_rate_limit("key1").await.ok();
1103 }
1104
1105 let result = limiter.check_rate_limit("key2").await;
1107 assert!(result.is_ok());
1108
1109 let result = limiter.check_rate_limit("key1").await;
1111 assert!(result.is_err());
1112 }
1113
1114 #[tokio::test]
1115 async fn test_account_lockout_creation() {
1116 let lockout = AccountLockout::new(3, 900);
1117 let is_locked = lockout.is_locked("test-user").await;
1118 assert!(!is_locked);
1119 }
1120
1121 #[tokio::test]
1122 async fn test_account_lockout_record_failure() {
1123 let lockout = AccountLockout::new(3, 900);
1124
1125 for _ in 0..2 {
1126 lockout.record_failure("test-user").await;
1127 }
1128
1129 let is_locked = lockout.is_locked("test-user").await;
1130 assert!(!is_locked, "Should not be locked after 2 failures");
1131
1132 lockout.record_failure("test-user").await;
1133 let is_locked = lockout.is_locked("test-user").await;
1134 assert!(is_locked, "Should be locked after 3 failures");
1135 }
1136
1137 #[tokio::test]
1138 async fn test_account_lockout_reset() {
1139 let lockout = AccountLockout::new(2, 900);
1140
1141 for _ in 0..2 {
1143 lockout.record_failure("test-user").await;
1144 }
1145
1146 assert!(lockout.is_locked("test-user").await);
1147
1148 lockout.reset("test-user").await;
1150
1151 assert!(!lockout.is_locked("test-user").await);
1152 }
1153
1154 #[tokio::test]
1155 async fn test_account_lockout_remaining_time() {
1156 let lockout = AccountLockout::new(2, 5); for _ in 0..2 {
1160 lockout.record_failure("test-user").await;
1161 }
1162
1163 let remaining = lockout.remaining_lockout_time("test-user").await;
1164 assert!(remaining.is_some());
1165 let time = remaining.unwrap();
1166 assert!(time > 0 && time <= 5);
1167 }
1168
1169 #[tokio::test]
1170 async fn test_global_user_store_initialization() {
1171 let store1 = init_global_user_store();
1172 let store2 = get_global_user_store();
1173
1174 assert!(store2.is_some());
1175
1176 let store2 = store2.unwrap();
1178 assert!(Arc::ptr_eq(&store1, &store2));
1179 }
1180
1181 #[tokio::test]
1182 async fn test_all_default_users_exist() {
1183 let store = UserStore::new();
1184 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
1185
1186 let result = store.authenticate("admin", "admin123").await;
1188 assert!(result.is_ok());
1189 assert!(matches!(result.unwrap().role, UserRole::Admin));
1190
1191 let result = store.authenticate("viewer", "viewer123").await;
1193 assert!(result.is_ok());
1194 assert!(matches!(result.unwrap().role, UserRole::Viewer));
1195
1196 let result = store.authenticate("editor", "editor123").await;
1198 assert!(result.is_ok());
1199 assert!(matches!(result.unwrap().role, UserRole::Editor));
1200 }
1201
1202 #[tokio::test]
1203 async fn test_concurrent_authentication_attempts() {
1204 let store = Arc::new(UserStore::new());
1205 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
1206
1207 let mut handles = vec![];
1208
1209 for i in 0..10 {
1211 let store_clone = store.clone();
1212 let handle = tokio::spawn(async move {
1213 if i % 2 == 0 {
1214 store_clone.authenticate("admin", "admin123").await
1215 } else {
1216 store_clone.authenticate("viewer", "viewer123").await
1217 }
1218 });
1219 handles.push(handle);
1220 }
1221
1222 for handle in handles {
1224 let result = handle.await.unwrap();
1225 assert!(result.is_ok());
1226 }
1227 }
1228}