mockforge_ui/
auth.rs

1//! Authentication and JWT token management
2//!
3//! This module provides authentication endpoints and JWT token generation/validation
4//! for the Admin UI.
5//!
6//! # Features
7//! - JWT token generation and validation
8//! - Password hashing with bcrypt
9//! - Rate limiting for login attempts
10//! - In-memory user store (can be replaced with database)
11//!
12//! # Database Integration
13//! See `auth/database.rs` for database-backed user store implementation.
14
15use 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
33/// JWT secret key (in production, load from environment variable)
34static JWT_SECRET: &[u8] = b"mockforge-secret-key-change-in-production";
35
36/// JWT claims structure
37#[derive(Debug, Serialize, Deserialize)]
38pub struct Claims {
39    /// Subject (user ID)
40    pub sub: String,
41    /// Username
42    pub username: String,
43    /// User role
44    pub role: String,
45    /// Email (optional)
46    pub email: Option<String>,
47    /// Issued at
48    pub iat: i64,
49    /// Expiration time
50    pub exp: i64,
51}
52
53/// Login request
54#[derive(Debug, Deserialize)]
55pub struct LoginRequest {
56    pub username: String,
57    pub password: String,
58}
59
60/// Login response
61#[derive(Debug, Serialize)]
62pub struct LoginResponse {
63    pub token: String,
64    pub refresh_token: String,
65    pub user: UserInfo,
66    pub expires_in: i64,
67}
68
69/// User information
70#[derive(Debug, Serialize, Clone)]
71pub struct UserInfo {
72    pub id: String,
73    pub username: String,
74    pub role: String,
75    pub email: Option<String>,
76}
77
78/// Refresh token request
79#[derive(Debug, Deserialize)]
80pub struct RefreshTokenRequest {
81    pub refresh_token: String,
82}
83
84/// In-memory user store (in production, use database)
85#[derive(Debug, Clone)]
86pub struct UserStore {
87    users: Arc<RwLock<HashMap<String, User>>>,
88    rate_limiter: RateLimiter,
89    account_lockout: AccountLockout,
90    password_policy: PasswordPolicy,
91}
92
93#[derive(Debug, Clone)]
94struct User {
95    id: String,
96    username: String,
97    password_hash: String, // Bcrypt hashed password
98    role: UserRole,
99    email: Option<String>,
100}
101
102/// Rate limiting for login attempts
103#[derive(Debug, Clone)]
104struct RateLimiter {
105    attempts: Arc<RwLock<HashMap<String, Vec<Instant>>>>,
106    max_attempts: usize,
107    window_seconds: u64,
108}
109
110/// Account lockout configuration
111#[derive(Debug, Clone)]
112struct AccountLockout {
113    /// Failed login attempts per account
114    failed_attempts: Arc<RwLock<HashMap<String, (usize, Option<Instant>)>>>,
115    /// Maximum failed attempts before lockout
116    max_failed_attempts: usize,
117    /// Lockout duration in seconds
118    lockout_duration_seconds: u64,
119}
120
121impl AccountLockout {
122    fn new(max_failed_attempts: usize, lockout_duration_seconds: u64) -> Self {
123        Self {
124            failed_attempts: Arc::new(RwLock::new(HashMap::new())),
125            max_failed_attempts,
126            lockout_duration_seconds,
127        }
128    }
129
130    /// Check if account is locked
131    async fn is_locked(&self, username: &str) -> bool {
132        let attempts = self.failed_attempts.read().await;
133        if let Some((count, locked_until)) = attempts.get(username) {
134            if *count >= self.max_failed_attempts {
135                if let Some(until) = locked_until {
136                    return until > &Instant::now();
137                }
138            }
139        }
140        false
141    }
142
143    /// Record a failed login attempt
144    async fn record_failure(&self, username: &str) {
145        let mut attempts = self.failed_attempts.write().await;
146        let entry = attempts.entry(username.to_string()).or_insert((0, None));
147        entry.0 += 1;
148
149        if entry.0 >= self.max_failed_attempts {
150            entry.1 = Some(Instant::now() + StdDuration::from_secs(self.lockout_duration_seconds));
151            tracing::warn!("Account locked: {} ({} failed attempts)", username, entry.0);
152        }
153    }
154
155    /// Reset failed attempts on successful login
156    async fn reset(&self, username: &str) {
157        let mut attempts = self.failed_attempts.write().await;
158        attempts.remove(username);
159    }
160
161    /// Get remaining lockout time in seconds
162    async fn remaining_lockout_time(&self, username: &str) -> Option<u64> {
163        let attempts = self.failed_attempts.read().await;
164        if let Some((_, locked_until)) = attempts.get(username) {
165            if let Some(until) = locked_until {
166                let now = Instant::now();
167                if until > &now {
168                    return Some(until.duration_since(now).as_secs());
169                }
170            }
171        }
172        None
173    }
174}
175
176impl RateLimiter {
177    fn new(max_attempts: usize, window_seconds: u64) -> Self {
178        Self {
179            attempts: Arc::new(RwLock::new(HashMap::new())),
180            max_attempts,
181            window_seconds,
182        }
183    }
184
185    async fn check_rate_limit(&self, key: &str) -> Result<(), String> {
186        let mut attempts = self.attempts.write().await;
187        let now = Instant::now();
188        let window = StdDuration::from_secs(self.window_seconds);
189
190        // Clean up old attempts
191        if let Some(attempt_times) = attempts.get_mut(key) {
192            attempt_times.retain(|&time| now.duration_since(time) < window);
193
194            if attempt_times.len() >= self.max_attempts {
195                return Err(format!(
196                    "Too many login attempts. Please try again in {} seconds.",
197                    self.window_seconds
198                ));
199            }
200        }
201
202        // Record this attempt
203        attempts.entry(key.to_string()).or_insert_with(Vec::new).push(now);
204
205        Ok(())
206    }
207
208    async fn reset_rate_limit(&self, key: &str) {
209        let mut attempts = self.attempts.write().await;
210        attempts.remove(key);
211    }
212}
213
214impl Default for UserStore {
215    fn default() -> Self {
216        Self::new()
217    }
218}
219
220impl UserStore {
221    pub fn new() -> Self {
222        let users = Arc::new(RwLock::new(HashMap::new()));
223        let rate_limiter = RateLimiter::new(5, 300); // 5 attempts per 5 minutes
224        let account_lockout = AccountLockout::new(5, 900); // 5 attempts, 15 minute lockout
225        let password_policy = PasswordPolicy::default(); // Use default policy
226
227        // Initialize with default users (in production, load from database)
228        // Passwords are hashed with bcrypt
229        let default_users = vec![
230            // admin / admin123
231            ("admin", "admin123", UserRole::Admin, "admin@mockforge.dev"),
232            // viewer / viewer123
233            ("viewer", "viewer123", UserRole::Viewer, "viewer@mockforge.dev"),
234            // editor / editor123
235            ("editor", "editor123", UserRole::Editor, "editor@mockforge.dev"),
236        ];
237
238        let store = Self {
239            users,
240            rate_limiter,
241            account_lockout,
242            password_policy,
243        };
244
245        // Hash passwords and create users asynchronously
246        let store_clone = store.clone();
247        tokio::spawn(async move {
248            let mut users = store_clone.users.write().await;
249            for (username, password, role, email) in default_users {
250                // Hash password with bcrypt
251                if let Ok(password_hash) = hash(password, DEFAULT_COST) {
252                    let user = User {
253                        id: format!("{}-001", username),
254                        username: username.to_string(),
255                        password_hash,
256                        role,
257                        email: Some(email.to_string()),
258                    };
259                    users.insert(username.to_string(), user);
260                } else {
261                    tracing::error!("Failed to hash password for user: {}", username);
262                }
263            }
264        });
265
266        store
267    }
268
269    pub async fn authenticate(&self, username: &str, password: &str) -> Result<User, String> {
270        // Check if account is locked
271        if self.account_lockout.is_locked(username).await {
272            if let Some(remaining) = self.account_lockout.remaining_lockout_time(username).await {
273                return Err(format!(
274                    "Account is locked due to too many failed login attempts. Please try again in {} seconds.",
275                    remaining
276                ));
277            }
278        }
279
280        // Check rate limiting
281        self.rate_limiter.check_rate_limit(username).await?;
282
283        let users = self.users.read().await;
284        if let Some(user) = users.get(username) {
285            // Verify password with bcrypt
286            match verify(password, &user.password_hash) {
287                Ok(true) => {
288                    // Successful login - reset rate limit and lockout
289                    self.rate_limiter.reset_rate_limit(username).await;
290                    self.account_lockout.reset(username).await;
291                    Ok(user.clone())
292                }
293                Ok(false) => {
294                    // Wrong password - record failure
295                    self.account_lockout.record_failure(username).await;
296                    Err("Invalid username or password".to_string())
297                }
298                Err(e) => {
299                    tracing::error!("Password verification error: {}", e);
300                    Err("Authentication error".to_string())
301                }
302            }
303        } else {
304            // User not found - still count as failed attempt (but don't lock non-existent accounts)
305            Err("Invalid username or password".to_string())
306        }
307    }
308
309    /// Create a new user with password policy validation
310    pub async fn create_user(
311        &self,
312        username: String,
313        password: String,
314        role: UserRole,
315        email: Option<String>,
316    ) -> Result<User, String> {
317        // Validate password against policy
318        #[cfg(feature = "password-policy")]
319        {
320            self.password_policy
321                .validate(&password, Some(&username))
322                .map_err(|e| e.to_string())?;
323        }
324
325        // Check if user already exists
326        let mut users = self.users.write().await;
327        if users.contains_key(&username) {
328            return Err("Username already exists".to_string());
329        }
330
331        // Hash password
332        let password_hash =
333            hash(&password, DEFAULT_COST).map_err(|e| format!("Failed to hash password: {}", e))?;
334
335        // Create user
336        let user = User {
337            id: format!("{}-{}", username, uuid::Uuid::new_v4()),
338            username: username.clone(),
339            password_hash,
340            role,
341            email,
342        };
343
344        users.insert(username, user.clone());
345        Ok(user)
346    }
347
348    pub async fn get_user_by_id(&self, user_id: &str) -> Option<User> {
349        let users = self.users.read().await;
350        users.values().find(|u| u.id == user_id).cloned()
351    }
352}
353
354/// Global user store instance
355static GLOBAL_USER_STORE: std::sync::OnceLock<Arc<UserStore>> = std::sync::OnceLock::new();
356
357/// Initialize the global user store
358pub fn init_global_user_store() -> Arc<UserStore> {
359    GLOBAL_USER_STORE.get_or_init(|| Arc::new(UserStore::new())).clone()
360}
361
362/// Get the global user store
363pub fn get_global_user_store() -> Option<Arc<UserStore>> {
364    GLOBAL_USER_STORE.get().cloned()
365}
366
367/// Generate JWT token
368pub fn generate_token(
369    user: &User,
370    expires_in_seconds: i64,
371) -> Result<String, jsonwebtoken::errors::Error> {
372    let now = Utc::now();
373    let exp = now + Duration::seconds(expires_in_seconds);
374    let secret = JWT_SECRET;
375
376    let claims = Claims {
377        sub: user.id.clone(),
378        username: user.username.clone(),
379        role: format!("{:?}", user.role).to_lowercase(),
380        email: user.email.clone(),
381        iat: now.timestamp(),
382        exp: exp.timestamp(),
383    };
384
385    encode(&Header::default(), &claims, &EncodingKey::from_secret(secret))
386}
387
388/// Generate refresh token
389pub fn generate_refresh_token(user: &User) -> Result<String, jsonwebtoken::errors::Error> {
390    // Refresh tokens expire in 7 days
391    generate_token(user, 7 * 24 * 60 * 60)
392}
393
394/// Validate JWT token
395pub fn validate_token(token: &str) -> Result<Claims, jsonwebtoken::errors::Error> {
396    let secret = JWT_SECRET;
397    let token_data =
398        decode::<Claims>(token, &DecodingKey::from_secret(secret), &Validation::default())?;
399
400    Ok(token_data.claims)
401}
402
403/// Login endpoint
404pub async fn login(
405    State(_state): State<AdminState>,
406    Json(request): Json<LoginRequest>,
407) -> Result<Json<ApiResponse<LoginResponse>>, StatusCode> {
408    let user_store = get_global_user_store().ok_or_else(|| {
409        tracing::error!("User store not initialized");
410        StatusCode::INTERNAL_SERVER_ERROR
411    })?;
412
413    // Authenticate user
414    let user =
415        user_store
416            .authenticate(&request.username, &request.password)
417            .await
418            .map_err(|e| {
419                tracing::warn!("Authentication failed for user {}: {}", request.username, e);
420                // Return appropriate status code based on error
421                if e.contains("Too many") {
422                    StatusCode::TOO_MANY_REQUESTS
423                } else {
424                    StatusCode::UNAUTHORIZED
425                }
426            })?;
427
428    // Generate tokens
429    let access_token = generate_token(&user, 24 * 60 * 60) // 24 hours
430        .map_err(|e| {
431            tracing::error!("Failed to generate access token: {}", e);
432            StatusCode::INTERNAL_SERVER_ERROR
433        })?;
434
435    let refresh_token = generate_refresh_token(&user).map_err(|e| {
436        tracing::error!("Failed to generate refresh token: {}", e);
437        StatusCode::INTERNAL_SERVER_ERROR
438    })?;
439
440    let user_info = UserInfo {
441        id: user.id,
442        username: user.username,
443        role: format!("{:?}", user.role).to_lowercase(),
444        email: user.email,
445    };
446
447    Ok(Json(ApiResponse::success(LoginResponse {
448        token: access_token,
449        refresh_token,
450        user: user_info,
451        expires_in: 24 * 60 * 60,
452    })))
453}
454
455/// Refresh token endpoint
456pub async fn refresh_token(
457    State(_state): State<AdminState>,
458    Json(request): Json<RefreshTokenRequest>,
459) -> Result<Json<ApiResponse<LoginResponse>>, StatusCode> {
460    // Validate refresh token
461    let claims = validate_token(&request.refresh_token).map_err(|_| {
462        tracing::warn!("Invalid refresh token");
463        StatusCode::UNAUTHORIZED
464    })?;
465
466    let user_store = get_global_user_store().ok_or_else(|| {
467        tracing::error!("User store not initialized");
468        StatusCode::INTERNAL_SERVER_ERROR
469    })?;
470
471    // Get user
472    let user = user_store.get_user_by_id(&claims.sub).await.ok_or_else(|| {
473        tracing::warn!("User not found: {}", claims.sub);
474        StatusCode::UNAUTHORIZED
475    })?;
476
477    // Generate new tokens
478    let access_token = generate_token(&user, 24 * 60 * 60) // 24 hours
479        .map_err(|e| {
480            tracing::error!("Failed to generate access token: {}", e);
481            StatusCode::INTERNAL_SERVER_ERROR
482        })?;
483
484    let refresh_token = generate_refresh_token(&user).map_err(|e| {
485        tracing::error!("Failed to generate refresh token: {}", e);
486        StatusCode::INTERNAL_SERVER_ERROR
487    })?;
488
489    let user_info = UserInfo {
490        id: user.id,
491        username: user.username,
492        role: format!("{:?}", user.role).to_lowercase(),
493        email: user.email,
494    };
495
496    Ok(Json(ApiResponse::success(LoginResponse {
497        token: access_token,
498        refresh_token,
499        user: user_info,
500        expires_in: 24 * 60 * 60,
501    })))
502}
503
504/// Get current user endpoint
505pub async fn get_current_user(
506    State(_state): State<AdminState>,
507) -> Result<Json<ApiResponse<UserInfo>>, StatusCode> {
508    // This would extract user from request extensions (set by middleware)
509    // For now, return error - should be called after authentication
510    Err(StatusCode::NOT_IMPLEMENTED)
511}
512
513/// Logout endpoint (client-side token removal, but can invalidate refresh tokens)
514pub async fn logout(State(_state): State<AdminState>) -> Json<ApiResponse<String>> {
515    // In production, invalidate refresh token in database
516    // For now, just return success (client removes token)
517    Json(ApiResponse::success("Logged out successfully".to_string()))
518}
519
520/// Convert Claims to UserContext
521pub fn claims_to_user_context(claims: &Claims) -> UserContext {
522    let role = match claims.role.as_str() {
523        "admin" => UserRole::Admin,
524        "editor" => UserRole::Editor,
525        "viewer" => UserRole::Viewer,
526        _ => UserRole::Viewer,
527    };
528
529    UserContext {
530        user_id: claims.sub.clone(),
531        username: claims.username.clone(),
532        role,
533        email: claims.email.clone(),
534    }
535}
536
537#[cfg(test)]
538mod tests {
539    use super::*;
540
541    #[tokio::test]
542    async fn test_user_store_creation() {
543        let store = UserStore::new();
544        // Wait a bit for async initialization
545        tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
546
547        // Verify default users exist
548        let result = store.authenticate("admin", "admin123").await;
549        assert!(result.is_ok(), "Admin user should exist");
550    }
551
552    #[tokio::test]
553    async fn test_user_store_default() {
554        let store = UserStore::default();
555        tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
556
557        let result = store.authenticate("admin", "admin123").await;
558        assert!(result.is_ok());
559    }
560
561    #[tokio::test]
562    async fn test_authenticate_success() {
563        let store = UserStore::new();
564        tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
565
566        let result = store.authenticate("admin", "admin123").await;
567        assert!(result.is_ok());
568
569        let user = result.unwrap();
570        assert_eq!(user.username, "admin");
571        assert!(matches!(user.role, UserRole::Admin));
572    }
573
574    #[tokio::test]
575    async fn test_authenticate_wrong_password() {
576        let store = UserStore::new();
577        tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
578
579        let result = store.authenticate("admin", "wrongpassword").await;
580        assert!(result.is_err());
581        assert_eq!(result.unwrap_err(), "Invalid username or password");
582    }
583
584    #[tokio::test]
585    async fn test_authenticate_nonexistent_user() {
586        let store = UserStore::new();
587        tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
588
589        let result = store.authenticate("nonexistent", "password").await;
590        assert!(result.is_err());
591        assert_eq!(result.unwrap_err(), "Invalid username or password");
592    }
593
594    #[tokio::test]
595    async fn test_rate_limiting() {
596        let store = UserStore::new();
597        tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
598
599        // Try to login with wrong password many times
600        for _ in 0..5 {
601            let _ = store.authenticate("admin", "wrongpassword").await;
602        }
603
604        // Next attempt should be rate limited or account locked
605        let result = store.authenticate("admin", "wrongpassword").await;
606        assert!(result.is_err());
607        let error = result.unwrap_err();
608        assert!(
609            error.contains("Too many") || error.contains("locked"),
610            "Expected rate limit or lockout error, got: {}",
611            error
612        );
613    }
614
615    #[tokio::test]
616    async fn test_account_lockout_after_failures() {
617        let store = UserStore::new();
618        tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
619
620        // Try to login with wrong password 5 times
621        for _ in 0..5 {
622            let _ = store.authenticate("editor", "wrongpassword").await;
623        }
624
625        // Account should now be locked
626        let result = store.authenticate("editor", "editor123").await;
627        assert!(result.is_err());
628        let error = result.unwrap_err();
629        assert!(
630            error.contains("locked") || error.contains("Too many"),
631            "Expected lockout error, got: {}",
632            error
633        );
634    }
635
636    #[tokio::test]
637    async fn test_account_lockout_reset_on_success() {
638        let store = UserStore::new();
639        tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
640
641        // Try wrong password a few times
642        for _ in 0..2 {
643            let _ = store.authenticate("viewer", "wrongpassword").await;
644        }
645
646        // Successful login should reset counter
647        let result = store.authenticate("viewer", "viewer123").await;
648        assert!(result.is_ok());
649
650        // Should be able to attempt again without hitting lockout
651        for _ in 0..2 {
652            let _ = store.authenticate("viewer", "wrongpassword").await;
653        }
654
655        // Not yet locked (reset worked)
656        let result = store.authenticate("viewer", "wrongpassword").await;
657        assert!(result.is_err());
658        assert!(!result.unwrap_err().contains("locked"));
659    }
660
661    #[tokio::test]
662    async fn test_create_user_success() {
663        let store = UserStore::new();
664        tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
665
666        let result = store
667            .create_user(
668                "newuser".to_string(),
669                "NewP@ssw0rd123".to_string(),
670                UserRole::Editor,
671                Some("newuser@example.com".to_string()),
672            )
673            .await;
674
675        assert!(result.is_ok());
676        let user = result.unwrap();
677        assert_eq!(user.username, "newuser");
678        assert!(matches!(user.role, UserRole::Editor));
679        assert_eq!(user.email, Some("newuser@example.com".to_string()));
680
681        // Verify we can authenticate with the new user
682        let auth_result = store.authenticate("newuser", "NewP@ssw0rd123").await;
683        assert!(auth_result.is_ok());
684    }
685
686    #[tokio::test]
687    async fn test_create_user_duplicate_username() {
688        let store = UserStore::new();
689        tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
690
691        let result = store
692            .create_user("admin".to_string(), "NewP@ssw0rd123".to_string(), UserRole::Editor, None)
693            .await;
694
695        assert!(result.is_err());
696        assert_eq!(result.unwrap_err(), "Username already exists");
697    }
698
699    #[tokio::test]
700    async fn test_get_user_by_id() {
701        let store = UserStore::new();
702        tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
703
704        // Authenticate to get user ID
705        let auth_result = store.authenticate("admin", "admin123").await.unwrap();
706        let user_id = auth_result.id.clone();
707
708        // Get user by ID
709        let result = store.get_user_by_id(&user_id).await;
710        assert!(result.is_some());
711
712        let user = result.unwrap();
713        assert_eq!(user.id, user_id);
714        assert_eq!(user.username, "admin");
715    }
716
717    #[tokio::test]
718    async fn test_get_user_by_id_not_found() {
719        let store = UserStore::new();
720        tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
721
722        let result = store.get_user_by_id("nonexistent-id").await;
723        assert!(result.is_none());
724    }
725
726    #[test]
727    fn test_generate_token_success() {
728        let user = User {
729            id: "test-id".to_string(),
730            username: "testuser".to_string(),
731            password_hash: "hash".to_string(),
732            role: UserRole::Editor,
733            email: Some("test@example.com".to_string()),
734        };
735
736        let result = generate_token(&user, 3600);
737        assert!(result.is_ok());
738
739        let token = result.unwrap();
740        assert!(!token.is_empty());
741    }
742
743    #[test]
744    fn test_generate_refresh_token() {
745        let user = User {
746            id: "test-id".to_string(),
747            username: "testuser".to_string(),
748            password_hash: "hash".to_string(),
749            role: UserRole::Editor,
750            email: None,
751        };
752
753        let result = generate_refresh_token(&user);
754        assert!(result.is_ok());
755
756        let token = result.unwrap();
757        assert!(!token.is_empty());
758    }
759
760    #[test]
761    fn test_validate_token_success() {
762        let user = User {
763            id: "test-id".to_string(),
764            username: "testuser".to_string(),
765            password_hash: "hash".to_string(),
766            role: UserRole::Viewer,
767            email: Some("test@example.com".to_string()),
768        };
769
770        let token = generate_token(&user, 3600).unwrap();
771        let result = validate_token(&token);
772
773        assert!(result.is_ok());
774        let claims = result.unwrap();
775        assert_eq!(claims.sub, "test-id");
776        assert_eq!(claims.username, "testuser");
777        assert_eq!(claims.role, "viewer");
778        assert_eq!(claims.email, Some("test@example.com".to_string()));
779    }
780
781    #[test]
782    fn test_validate_token_invalid() {
783        let result = validate_token("invalid.token.here");
784        assert!(result.is_err());
785    }
786
787    #[test]
788    fn test_validate_token_expired() {
789        let user = User {
790            id: "test-id".to_string(),
791            username: "testuser".to_string(),
792            password_hash: "hash".to_string(),
793            role: UserRole::Editor,
794            email: None,
795        };
796
797        // Generate token that's already expired (2 minutes ago to exceed default leeway of 60s)
798        let token = generate_token(&user, -120).unwrap();
799        let result = validate_token(&token);
800
801        // Should fail validation due to expiration
802        assert!(result.is_err());
803    }
804
805    #[test]
806    fn test_claims_serialization() {
807        let claims = Claims {
808            sub: "user123".to_string(),
809            username: "testuser".to_string(),
810            role: "admin".to_string(),
811            email: Some("test@example.com".to_string()),
812            iat: 1234567890,
813            exp: 1234567890 + 3600,
814        };
815
816        let serialized = serde_json::to_string(&claims).unwrap();
817        let deserialized: Claims = serde_json::from_str(&serialized).unwrap();
818
819        assert_eq!(deserialized.sub, claims.sub);
820        assert_eq!(deserialized.username, claims.username);
821        assert_eq!(deserialized.role, claims.role);
822        assert_eq!(deserialized.email, claims.email);
823    }
824
825    #[test]
826    fn test_claims_to_user_context() {
827        let claims = Claims {
828            sub: "user123".to_string(),
829            username: "testuser".to_string(),
830            role: "editor".to_string(),
831            email: Some("test@example.com".to_string()),
832            iat: 1234567890,
833            exp: 1234567890 + 3600,
834        };
835
836        let context = claims_to_user_context(&claims);
837        assert_eq!(context.user_id, "user123");
838        assert_eq!(context.username, "testuser");
839        assert_eq!(context.role, UserRole::Editor);
840        assert_eq!(context.email, Some("test@example.com".to_string()));
841    }
842
843    #[test]
844    fn test_claims_to_user_context_unknown_role_defaults_to_viewer() {
845        let claims = Claims {
846            sub: "user123".to_string(),
847            username: "testuser".to_string(),
848            role: "unknown".to_string(),
849            email: None,
850            iat: 1234567890,
851            exp: 1234567890 + 3600,
852        };
853
854        let context = claims_to_user_context(&claims);
855        assert_eq!(context.role, UserRole::Viewer);
856    }
857
858    #[test]
859    fn test_login_request_deserialization() {
860        let json = r#"{"username": "testuser", "password": "testpass"}"#;
861        let request: LoginRequest = serde_json::from_str(json).unwrap();
862        assert_eq!(request.username, "testuser");
863        assert_eq!(request.password, "testpass");
864    }
865
866    #[test]
867    fn test_refresh_token_request_deserialization() {
868        let json = r#"{"refresh_token": "token123"}"#;
869        let request: RefreshTokenRequest = serde_json::from_str(json).unwrap();
870        assert_eq!(request.refresh_token, "token123");
871    }
872
873    #[test]
874    fn test_user_info_serialization() {
875        let user_info = UserInfo {
876            id: "user123".to_string(),
877            username: "testuser".to_string(),
878            role: "admin".to_string(),
879            email: Some("test@example.com".to_string()),
880        };
881
882        let serialized = serde_json::to_string(&user_info).unwrap();
883        assert!(serialized.contains("user123"));
884        assert!(serialized.contains("testuser"));
885        assert!(serialized.contains("admin"));
886    }
887
888    #[test]
889    fn test_login_response_serialization() {
890        let user_info = UserInfo {
891            id: "user123".to_string(),
892            username: "testuser".to_string(),
893            role: "editor".to_string(),
894            email: None,
895        };
896
897        let response = LoginResponse {
898            token: "access.token.here".to_string(),
899            refresh_token: "refresh.token.here".to_string(),
900            user: user_info,
901            expires_in: 3600,
902        };
903
904        let serialized = serde_json::to_string(&response).unwrap();
905        assert!(serialized.contains("access.token.here"));
906        assert!(serialized.contains("refresh.token.here"));
907        assert!(serialized.contains("3600"));
908    }
909
910    #[tokio::test]
911    async fn test_rate_limiter_creation() {
912        let limiter = RateLimiter::new(5, 60);
913        let result = limiter.check_rate_limit("test-key").await;
914        assert!(result.is_ok());
915    }
916
917    #[tokio::test]
918    async fn test_rate_limiter_exceeds_limit() {
919        let limiter = RateLimiter::new(3, 60);
920
921        // First 3 attempts should succeed
922        for _ in 0..3 {
923            assert!(limiter.check_rate_limit("test-key").await.is_ok());
924        }
925
926        // 4th attempt should fail
927        let result = limiter.check_rate_limit("test-key").await;
928        assert!(result.is_err());
929        assert!(result.unwrap_err().contains("Too many"));
930    }
931
932    #[tokio::test]
933    async fn test_rate_limiter_reset() {
934        let limiter = RateLimiter::new(3, 60);
935
936        // Use up the limit
937        for _ in 0..3 {
938            limiter.check_rate_limit("test-key").await.ok();
939        }
940
941        // Reset
942        limiter.reset_rate_limit("test-key").await;
943
944        // Should be able to make requests again
945        let result = limiter.check_rate_limit("test-key").await;
946        assert!(result.is_ok());
947    }
948
949    #[tokio::test]
950    async fn test_rate_limiter_different_keys() {
951        let limiter = RateLimiter::new(2, 60);
952
953        // Use up limit for key1
954        for _ in 0..2 {
955            limiter.check_rate_limit("key1").await.ok();
956        }
957
958        // key2 should still work
959        let result = limiter.check_rate_limit("key2").await;
960        assert!(result.is_ok());
961
962        // key1 should be limited
963        let result = limiter.check_rate_limit("key1").await;
964        assert!(result.is_err());
965    }
966
967    #[tokio::test]
968    async fn test_account_lockout_creation() {
969        let lockout = AccountLockout::new(3, 900);
970        let is_locked = lockout.is_locked("test-user").await;
971        assert!(!is_locked);
972    }
973
974    #[tokio::test]
975    async fn test_account_lockout_record_failure() {
976        let lockout = AccountLockout::new(3, 900);
977
978        for _ in 0..2 {
979            lockout.record_failure("test-user").await;
980        }
981
982        let is_locked = lockout.is_locked("test-user").await;
983        assert!(!is_locked, "Should not be locked after 2 failures");
984
985        lockout.record_failure("test-user").await;
986        let is_locked = lockout.is_locked("test-user").await;
987        assert!(is_locked, "Should be locked after 3 failures");
988    }
989
990    #[tokio::test]
991    async fn test_account_lockout_reset() {
992        let lockout = AccountLockout::new(2, 900);
993
994        // Lock the account
995        for _ in 0..2 {
996            lockout.record_failure("test-user").await;
997        }
998
999        assert!(lockout.is_locked("test-user").await);
1000
1001        // Reset
1002        lockout.reset("test-user").await;
1003
1004        assert!(!lockout.is_locked("test-user").await);
1005    }
1006
1007    #[tokio::test]
1008    async fn test_account_lockout_remaining_time() {
1009        let lockout = AccountLockout::new(2, 5); // 5 second lockout
1010
1011        // Lock the account
1012        for _ in 0..2 {
1013            lockout.record_failure("test-user").await;
1014        }
1015
1016        let remaining = lockout.remaining_lockout_time("test-user").await;
1017        assert!(remaining.is_some());
1018        let time = remaining.unwrap();
1019        assert!(time > 0 && time <= 5);
1020    }
1021
1022    #[tokio::test]
1023    async fn test_global_user_store_initialization() {
1024        let store1 = init_global_user_store();
1025        let store2 = get_global_user_store();
1026
1027        assert!(store2.is_some());
1028
1029        // Both should be the same instance
1030        let store2 = store2.unwrap();
1031        assert!(Arc::ptr_eq(&store1, &store2));
1032    }
1033
1034    #[tokio::test]
1035    async fn test_all_default_users_exist() {
1036        let store = UserStore::new();
1037        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
1038
1039        // Test admin
1040        let result = store.authenticate("admin", "admin123").await;
1041        assert!(result.is_ok());
1042        assert!(matches!(result.unwrap().role, UserRole::Admin));
1043
1044        // Test viewer
1045        let result = store.authenticate("viewer", "viewer123").await;
1046        assert!(result.is_ok());
1047        assert!(matches!(result.unwrap().role, UserRole::Viewer));
1048
1049        // Test editor
1050        let result = store.authenticate("editor", "editor123").await;
1051        assert!(result.is_ok());
1052        assert!(matches!(result.unwrap().role, UserRole::Editor));
1053    }
1054
1055    #[tokio::test]
1056    async fn test_concurrent_authentication_attempts() {
1057        let store = Arc::new(UserStore::new());
1058        tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
1059
1060        let mut handles = vec![];
1061
1062        // Spawn multiple concurrent authentication attempts
1063        for i in 0..10 {
1064            let store_clone = store.clone();
1065            let handle = tokio::spawn(async move {
1066                if i % 2 == 0 {
1067                    store_clone.authenticate("admin", "admin123").await
1068                } else {
1069                    store_clone.authenticate("viewer", "viewer123").await
1070                }
1071            });
1072            handles.push(handle);
1073        }
1074
1075        // All should succeed
1076        for handle in handles {
1077            let result = handle.await.unwrap();
1078            assert!(result.is_ok());
1079        }
1080    }
1081}