aegis_server/
auth.rs

1//! Aegis Authentication Module
2//!
3//! Enterprise authentication with LDAP, OAuth2/OIDC, RBAC, and audit logging.
4//!
5//! @version 0.2.0
6//! @author AutomataNexus Development Team
7
8use parking_lot::RwLock;
9use serde::{Deserialize, Serialize};
10use std::collections::{HashMap, HashSet};
11use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
12
13// =============================================================================
14// Authentication Provider Types
15// =============================================================================
16
17/// Authentication provider type.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
19#[serde(rename_all = "lowercase")]
20pub enum AuthProvider {
21    Local,
22    Ldap,
23    OAuth2,
24    Oidc,
25    Saml,
26}
27
28/// LDAP configuration.
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct LdapConfig {
31    pub server_url: String,
32    pub bind_dn: String,
33    pub bind_password: String,
34    pub base_dn: String,
35    pub user_filter: String,
36    pub group_filter: String,
37    pub use_tls: bool,
38    pub group_attribute: String,
39    pub admin_groups: Vec<String>,
40    pub operator_groups: Vec<String>,
41}
42
43impl Default for LdapConfig {
44    fn default() -> Self {
45        Self {
46            server_url: "ldap://localhost:389".to_string(),
47            bind_dn: "cn=admin,dc=aegisdb,dc=io".to_string(),
48            bind_password: String::new(),
49            base_dn: "dc=aegisdb,dc=io".to_string(),
50            user_filter: "(uid={username})".to_string(),
51            group_filter: "(member={dn})".to_string(),
52            use_tls: true,
53            group_attribute: "memberOf".to_string(),
54            admin_groups: vec!["cn=admins,ou=groups,dc=aegisdb,dc=io".to_string()],
55            operator_groups: vec!["cn=operators,ou=groups,dc=aegisdb,dc=io".to_string()],
56        }
57    }
58}
59
60/// OAuth2/OIDC configuration.
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct OAuth2Config {
63    pub provider_name: String,
64    pub client_id: String,
65    pub client_secret: String,
66    pub authorization_url: String,
67    pub token_url: String,
68    pub userinfo_url: String,
69    pub redirect_uri: String,
70    pub scopes: Vec<String>,
71    pub role_claim: String,
72    pub admin_roles: Vec<String>,
73    pub operator_roles: Vec<String>,
74}
75
76impl Default for OAuth2Config {
77    fn default() -> Self {
78        Self {
79            provider_name: "default".to_string(),
80            client_id: String::new(),
81            client_secret: String::new(),
82            authorization_url: "https://auth.example.com/authorize".to_string(),
83            token_url: "https://auth.example.com/token".to_string(),
84            userinfo_url: "https://auth.example.com/userinfo".to_string(),
85            redirect_uri: "http://localhost:8080/callback".to_string(),
86            scopes: vec!["openid".to_string(), "profile".to_string(), "email".to_string()],
87            role_claim: "roles".to_string(),
88            admin_roles: vec!["admin".to_string()],
89            operator_roles: vec!["operator".to_string()],
90        }
91    }
92}
93
94/// LDAP authentication result.
95#[derive(Debug, Clone)]
96pub struct LdapAuthResult {
97    pub success: bool,
98    pub user_dn: Option<String>,
99    pub email: Option<String>,
100    pub display_name: Option<String>,
101    pub groups: Vec<String>,
102    pub error: Option<String>,
103}
104
105/// OAuth2 token response.
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct OAuth2TokenResponse {
108    pub access_token: String,
109    pub token_type: String,
110    pub expires_in: Option<u64>,
111    pub refresh_token: Option<String>,
112    pub id_token: Option<String>,
113}
114
115/// OAuth2 user info.
116#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct OAuth2UserInfo {
118    pub sub: String,
119    pub email: Option<String>,
120    pub name: Option<String>,
121    pub preferred_username: Option<String>,
122    pub roles: Option<Vec<String>>,
123}
124
125// =============================================================================
126// User Types
127// =============================================================================
128
129/// User role enumeration.
130#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
131#[serde(rename_all = "lowercase")]
132pub enum UserRole {
133    Admin,
134    Operator,
135    Viewer,
136}
137
138impl std::fmt::Display for UserRole {
139    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140        match self {
141            UserRole::Admin => write!(f, "admin"),
142            UserRole::Operator => write!(f, "operator"),
143            UserRole::Viewer => write!(f, "viewer"),
144        }
145    }
146}
147
148/// User information stored in the system.
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct User {
151    pub id: String,
152    pub username: String,
153    pub email: String,
154    pub password_hash: String,
155    pub role: UserRole,
156    pub mfa_enabled: bool,
157    pub mfa_secret: Option<String>,
158    pub created_at: u64,
159    pub last_login: Option<u64>,
160}
161
162/// User information returned to clients (no sensitive data).
163#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct UserInfo {
165    pub id: String,
166    pub username: String,
167    pub email: String,
168    pub role: UserRole,
169    pub mfa_enabled: bool,
170    pub created_at: String,
171}
172
173impl From<&User> for UserInfo {
174    fn from(user: &User) -> Self {
175        Self {
176            id: user.id.clone(),
177            username: user.username.clone(),
178            email: user.email.clone(),
179            role: user.role,
180            mfa_enabled: user.mfa_enabled,
181            created_at: format_timestamp(user.created_at),
182        }
183    }
184}
185
186// =============================================================================
187// Session Types
188// =============================================================================
189
190/// Active session information.
191#[derive(Debug, Clone)]
192pub struct Session {
193    pub token: String,
194    pub user_id: String,
195    pub created_at: Instant,
196    pub expires_at: Instant,
197    pub mfa_verified: bool,
198}
199
200impl Session {
201    pub fn is_expired(&self) -> bool {
202        Instant::now() > self.expires_at
203    }
204}
205
206/// Pending MFA verification session.
207#[derive(Debug, Clone)]
208pub struct PendingMfaSession {
209    pub temp_token: String,
210    pub user_id: String,
211    pub created_at: Instant,
212    pub expires_at: Instant,
213}
214
215impl PendingMfaSession {
216    pub fn is_expired(&self) -> bool {
217        Instant::now() > self.expires_at
218    }
219}
220
221// =============================================================================
222// Request/Response Types
223// =============================================================================
224
225/// Login request.
226#[derive(Debug, Clone, Deserialize)]
227pub struct LoginRequest {
228    pub username: String,
229    pub password: String,
230}
231
232/// MFA verification request.
233#[derive(Debug, Clone, Deserialize)]
234pub struct MfaVerifyRequest {
235    pub code: String,
236    pub token: String,
237}
238
239/// MFA setup data for new MFA enrollment.
240#[derive(Debug, Clone, Serialize, Deserialize)]
241pub struct MfaSetupData {
242    pub secret: String,
243    pub qr_code: String,
244    pub backup_codes: Vec<String>,
245}
246
247/// Authentication response.
248#[derive(Debug, Clone, Serialize)]
249pub struct AuthResponse {
250    pub token: Option<String>,
251    pub user: Option<UserInfo>,
252    pub requires_mfa: Option<bool>,
253    pub requires_mfa_setup: Option<bool>,
254    pub mfa_setup_data: Option<MfaSetupData>,
255    pub error: Option<String>,
256}
257
258impl AuthResponse {
259    pub fn success(token: String, user: UserInfo) -> Self {
260        Self {
261            token: Some(token),
262            user: Some(user),
263            requires_mfa: None,
264            requires_mfa_setup: None,
265            mfa_setup_data: None,
266            error: None,
267        }
268    }
269
270    pub fn requires_mfa(temp_token: String) -> Self {
271        Self {
272            token: Some(temp_token),
273            user: None,
274            requires_mfa: Some(true),
275            requires_mfa_setup: None,
276            mfa_setup_data: None,
277            error: None,
278        }
279    }
280
281    pub fn error(message: &str) -> Self {
282        Self {
283            token: None,
284            user: None,
285            requires_mfa: None,
286            requires_mfa_setup: None,
287            mfa_setup_data: None,
288            error: Some(message.to_string()),
289        }
290    }
291}
292
293// =============================================================================
294// Authentication Service
295// =============================================================================
296
297/// Authentication service managing users and sessions.
298pub struct AuthService {
299    users: RwLock<HashMap<String, User>>,
300    sessions: RwLock<HashMap<String, Session>>,
301    pending_mfa: RwLock<HashMap<String, PendingMfaSession>>,
302    session_duration: Duration,
303    mfa_timeout: Duration,
304}
305
306impl AuthService {
307    /// Create a new authentication service.
308    pub fn new() -> Self {
309        let mut users = HashMap::new();
310
311        // Create default admin user
312        let admin = User {
313            id: "user-001".to_string(),
314            username: "admin".to_string(),
315            email: "admin@aegisdb.io".to_string(),
316            password_hash: hash_password("admin"),
317            role: UserRole::Admin,
318            mfa_enabled: true,
319            mfa_secret: Some("JBSWY3DPEHPK3PXP".to_string()), // Base32 encoded secret
320            created_at: now_timestamp(),
321            last_login: None,
322        };
323        users.insert(admin.username.clone(), admin);
324
325        // Create demo user (no MFA)
326        let demo = User {
327            id: "user-002".to_string(),
328            username: "demo".to_string(),
329            email: "demo@aegisdb.io".to_string(),
330            password_hash: hash_password("demo"),
331            role: UserRole::Viewer,
332            mfa_enabled: false,
333            mfa_secret: None,
334            created_at: now_timestamp(),
335            last_login: None,
336        };
337        users.insert(demo.username.clone(), demo);
338
339        // Create operator user
340        let operator = User {
341            id: "user-003".to_string(),
342            username: "operator".to_string(),
343            email: "operator@aegisdb.io".to_string(),
344            password_hash: hash_password("operator"),
345            role: UserRole::Operator,
346            mfa_enabled: false,
347            mfa_secret: None,
348            created_at: now_timestamp(),
349            last_login: None,
350        };
351        users.insert(operator.username.clone(), operator);
352
353        Self {
354            users: RwLock::new(users),
355            sessions: RwLock::new(HashMap::new()),
356            pending_mfa: RwLock::new(HashMap::new()),
357            session_duration: Duration::from_secs(24 * 60 * 60), // 24 hours
358            mfa_timeout: Duration::from_secs(5 * 60), // 5 minutes
359        }
360    }
361
362    /// Authenticate user with username and password.
363    pub fn login(&self, username: &str, password: &str) -> AuthResponse {
364        let users = self.users.read();
365
366        let user = match users.get(username) {
367            Some(u) => u,
368            None => return AuthResponse::error("Invalid credentials"),
369        };
370
371        if !verify_password(password, &user.password_hash) {
372            return AuthResponse::error("Invalid credentials");
373        }
374
375        // Update last login
376        drop(users);
377        {
378            let mut users = self.users.write();
379            if let Some(u) = users.get_mut(username) {
380                u.last_login = Some(now_timestamp());
381            }
382        }
383        let users = self.users.read();
384        let user = users.get(username).unwrap();
385
386        if user.mfa_enabled {
387            // Create pending MFA session
388            let temp_token = generate_token();
389            let pending = PendingMfaSession {
390                temp_token: temp_token.clone(),
391                user_id: user.id.clone(),
392                created_at: Instant::now(),
393                expires_at: Instant::now() + self.mfa_timeout,
394            };
395            self.pending_mfa.write().insert(temp_token.clone(), pending);
396
397            AuthResponse::requires_mfa(temp_token)
398        } else {
399            // Create session directly
400            let token = generate_token();
401            let session = Session {
402                token: token.clone(),
403                user_id: user.id.clone(),
404                created_at: Instant::now(),
405                expires_at: Instant::now() + self.session_duration,
406                mfa_verified: true,
407            };
408            self.sessions.write().insert(token.clone(), session);
409
410            AuthResponse::success(token, UserInfo::from(user))
411        }
412    }
413
414    /// Verify MFA code and complete authentication.
415    pub fn verify_mfa(&self, code: &str, temp_token: &str) -> AuthResponse {
416        // Get pending MFA session
417        let pending = {
418            let pending_sessions = self.pending_mfa.read();
419            match pending_sessions.get(temp_token) {
420                Some(p) if !p.is_expired() => p.clone(),
421                Some(_) => return AuthResponse::error("MFA session expired"),
422                None => return AuthResponse::error("Invalid MFA session"),
423            }
424        };
425
426        // Get user
427        let users = self.users.read();
428        let user = match users.values().find(|u| u.id == pending.user_id) {
429            Some(u) => u,
430            None => return AuthResponse::error("User not found"),
431        };
432
433        // Verify TOTP code
434        let secret = match &user.mfa_secret {
435            Some(s) => s,
436            None => return AuthResponse::error("MFA not configured"),
437        };
438
439        if !verify_totp(code, secret) {
440            return AuthResponse::error("Invalid MFA code");
441        }
442
443        // Remove pending session
444        self.pending_mfa.write().remove(temp_token);
445
446        // Create authenticated session
447        let token = generate_token();
448        let session = Session {
449            token: token.clone(),
450            user_id: user.id.clone(),
451            created_at: Instant::now(),
452            expires_at: Instant::now() + self.session_duration,
453            mfa_verified: true,
454        };
455        self.sessions.write().insert(token.clone(), session);
456
457        AuthResponse::success(token, UserInfo::from(user))
458    }
459
460    /// Validate a session token and return user info.
461    pub fn validate_session(&self, token: &str) -> Option<UserInfo> {
462        let sessions = self.sessions.read();
463        let session = sessions.get(token)?;
464
465        if session.is_expired() {
466            return None;
467        }
468
469        let users = self.users.read();
470        let user = users.values().find(|u| u.id == session.user_id)?;
471
472        Some(UserInfo::from(user))
473    }
474
475    /// Logout and invalidate session.
476    pub fn logout(&self, token: &str) -> bool {
477        self.sessions.write().remove(token).is_some()
478    }
479
480    /// Get user by ID.
481    pub fn get_user(&self, user_id: &str) -> Option<UserInfo> {
482        let users = self.users.read();
483        users.values()
484            .find(|u| u.id == user_id)
485            .map(UserInfo::from)
486    }
487
488    /// List all users.
489    pub fn list_users(&self) -> Vec<UserInfo> {
490        let users = self.users.read();
491        users.values().map(UserInfo::from).collect()
492    }
493
494    /// Clean up expired sessions.
495    pub fn cleanup_expired(&self) {
496        let mut sessions = self.sessions.write();
497        sessions.retain(|_, s| !s.is_expired());
498
499        let mut pending = self.pending_mfa.write();
500        pending.retain(|_, p| !p.is_expired());
501    }
502}
503
504impl Default for AuthService {
505    fn default() -> Self {
506        Self::new()
507    }
508}
509
510// =============================================================================
511// Helper Functions
512// =============================================================================
513
514/// Generate a secure random token.
515fn generate_token() -> String {
516    use std::collections::hash_map::DefaultHasher;
517    use std::hash::{Hash, Hasher};
518
519    let mut hasher = DefaultHasher::new();
520    SystemTime::now().hash(&mut hasher);
521    std::process::id().hash(&mut hasher);
522
523    // Add some randomness from timing
524    let start = Instant::now();
525    for _ in 0..1000 {
526        std::hint::black_box(1 + 1);
527    }
528    start.elapsed().as_nanos().hash(&mut hasher);
529
530    format!("{:016x}{:016x}", hasher.finish(), now_timestamp())
531}
532
533/// Hash a password (simplified - in production use bcrypt/argon2).
534fn hash_password(password: &str) -> String {
535    use std::collections::hash_map::DefaultHasher;
536    use std::hash::{Hash, Hasher};
537
538    let mut hasher = DefaultHasher::new();
539    password.hash(&mut hasher);
540    "aegis_salt_v1".hash(&mut hasher);
541    format!("{:016x}", hasher.finish())
542}
543
544/// Verify a password against its hash.
545fn verify_password(password: &str, hash: &str) -> bool {
546    hash_password(password) == hash
547}
548
549/// Verify a TOTP code.
550fn verify_totp(code: &str, _secret: &str) -> bool {
551    // Simplified TOTP verification
552    // In production, use a proper TOTP library
553    // Accept "123456" for testing, or check if it's a 6-digit number
554    if code == "123456" {
555        return true;
556    }
557
558    // Generate current TOTP based on time
559    let timestamp = now_timestamp() / 1000; // seconds
560    let time_step = timestamp / 30; // 30-second windows
561
562    // Simple TOTP simulation - in production use proper HMAC-SHA1
563    let expected = format!("{:06}", (time_step % 1_000_000) as u32);
564    code == expected
565}
566
567/// Get current timestamp in milliseconds.
568fn now_timestamp() -> u64 {
569    SystemTime::now()
570        .duration_since(UNIX_EPOCH)
571        .unwrap_or_default()
572        .as_millis() as u64
573}
574
575/// Format a timestamp to RFC3339 string.
576fn format_timestamp(timestamp_ms: u64) -> String {
577    let secs = timestamp_ms / 1000;
578    let datetime = UNIX_EPOCH + Duration::from_secs(secs);
579
580    // Simple ISO 8601 formatting
581    let duration = datetime.duration_since(UNIX_EPOCH).unwrap_or_default();
582    let total_secs = duration.as_secs();
583
584    let days_since_epoch = total_secs / 86400;
585    let secs_today = total_secs % 86400;
586
587    let hours = secs_today / 3600;
588    let minutes = (secs_today % 3600) / 60;
589    let seconds = secs_today % 60;
590
591    // Calculate year/month/day (simplified)
592    let mut year = 1970;
593    let mut remaining_days = days_since_epoch;
594
595    loop {
596        let days_in_year = if is_leap_year(year) { 366 } else { 365 };
597        if remaining_days < days_in_year {
598            break;
599        }
600        remaining_days -= days_in_year;
601        year += 1;
602    }
603
604    let days_in_months: [u64; 12] = if is_leap_year(year) {
605        [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
606    } else {
607        [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
608    };
609
610    let mut month = 1;
611    for &days in &days_in_months {
612        if remaining_days < days {
613            break;
614        }
615        remaining_days -= days;
616        month += 1;
617    }
618    let day = remaining_days + 1;
619
620    format!("{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z", year, month, day, hours, minutes, seconds)
621}
622
623fn is_leap_year(year: u64) -> bool {
624    (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
625}
626
627// =============================================================================
628// RBAC - Role-Based Access Control
629// =============================================================================
630
631/// Permission types for database operations.
632#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
633#[serde(rename_all = "snake_case")]
634pub enum Permission {
635    // Database operations
636    DatabaseCreate,
637    DatabaseDrop,
638    DatabaseList,
639
640    // Table operations
641    TableCreate,
642    TableDrop,
643    TableAlter,
644    TableList,
645
646    // Data operations
647    DataSelect,
648    DataInsert,
649    DataUpdate,
650    DataDelete,
651
652    // Admin operations
653    UserCreate,
654    UserDelete,
655    UserModify,
656    RoleCreate,
657    RoleDelete,
658    RoleAssign,
659
660    // System operations
661    ConfigView,
662    ConfigModify,
663    MetricsView,
664    LogsView,
665    BackupCreate,
666    BackupRestore,
667
668    // Cluster operations
669    NodeAdd,
670    NodeRemove,
671    ClusterManage,
672}
673
674/// A role with a set of permissions.
675#[derive(Debug, Clone, Serialize, Deserialize)]
676pub struct Role {
677    pub name: String,
678    pub description: String,
679    pub permissions: HashSet<Permission>,
680    pub created_at: u64,
681    pub created_by: String,
682}
683
684impl Role {
685    /// Create a new role with the given permissions.
686    pub fn new(name: &str, description: &str, permissions: Vec<Permission>) -> Self {
687        Self {
688            name: name.to_string(),
689            description: description.to_string(),
690            permissions: permissions.into_iter().collect(),
691            created_at: now_timestamp(),
692            created_by: "system".to_string(),
693        }
694    }
695
696    /// Check if this role has a specific permission.
697    pub fn has_permission(&self, permission: Permission) -> bool {
698        self.permissions.contains(&permission)
699    }
700}
701
702/// RBAC manager for role and permission management.
703pub struct RbacManager {
704    roles: RwLock<HashMap<String, Role>>,
705    user_roles: RwLock<HashMap<String, HashSet<String>>>,
706    row_policies: RwLock<Vec<RowLevelPolicy>>,
707}
708
709impl RbacManager {
710    /// Create a new RBAC manager with default roles.
711    pub fn new() -> Self {
712        let mut roles = HashMap::new();
713
714        // Create admin role with all permissions
715        let admin_permissions = vec![
716            Permission::DatabaseCreate, Permission::DatabaseDrop, Permission::DatabaseList,
717            Permission::TableCreate, Permission::TableDrop, Permission::TableAlter, Permission::TableList,
718            Permission::DataSelect, Permission::DataInsert, Permission::DataUpdate, Permission::DataDelete,
719            Permission::UserCreate, Permission::UserDelete, Permission::UserModify,
720            Permission::RoleCreate, Permission::RoleDelete, Permission::RoleAssign,
721            Permission::ConfigView, Permission::ConfigModify, Permission::MetricsView, Permission::LogsView,
722            Permission::BackupCreate, Permission::BackupRestore,
723            Permission::NodeAdd, Permission::NodeRemove, Permission::ClusterManage,
724        ];
725        roles.insert("admin".to_string(), Role::new("admin", "Full system administrator", admin_permissions));
726
727        // Create operator role
728        let operator_permissions = vec![
729            Permission::DatabaseList,
730            Permission::TableCreate, Permission::TableAlter, Permission::TableList,
731            Permission::DataSelect, Permission::DataInsert, Permission::DataUpdate, Permission::DataDelete,
732            Permission::ConfigView, Permission::MetricsView, Permission::LogsView,
733            Permission::BackupCreate,
734        ];
735        roles.insert("operator".to_string(), Role::new("operator", "Database operator", operator_permissions));
736
737        // Create viewer role
738        let viewer_permissions = vec![
739            Permission::DatabaseList,
740            Permission::TableList,
741            Permission::DataSelect,
742            Permission::MetricsView,
743        ];
744        roles.insert("viewer".to_string(), Role::new("viewer", "Read-only viewer", viewer_permissions));
745
746        // Create analyst role
747        let analyst_permissions = vec![
748            Permission::DatabaseList,
749            Permission::TableList,
750            Permission::DataSelect,
751            Permission::MetricsView,
752            Permission::LogsView,
753        ];
754        roles.insert("analyst".to_string(), Role::new("analyst", "Data analyst with read access", analyst_permissions));
755
756        // Default user-role mappings
757        let mut user_roles: HashMap<String, HashSet<String>> = HashMap::new();
758        user_roles.insert("user-001".to_string(), ["admin".to_string()].into_iter().collect());
759        user_roles.insert("user-002".to_string(), ["viewer".to_string()].into_iter().collect());
760        user_roles.insert("user-003".to_string(), ["operator".to_string()].into_iter().collect());
761
762        Self {
763            roles: RwLock::new(roles),
764            user_roles: RwLock::new(user_roles),
765            row_policies: RwLock::new(Vec::new()),
766        }
767    }
768
769    /// Create a new role.
770    pub fn create_role(&self, name: &str, description: &str, permissions: Vec<Permission>, created_by: &str) -> Result<(), String> {
771        let mut roles = self.roles.write();
772        if roles.contains_key(name) {
773            return Err(format!("Role '{}' already exists", name));
774        }
775
776        let mut role = Role::new(name, description, permissions);
777        role.created_by = created_by.to_string();
778        roles.insert(name.to_string(), role);
779        Ok(())
780    }
781
782    /// Delete a role.
783    pub fn delete_role(&self, name: &str) -> Result<(), String> {
784        let mut roles = self.roles.write();
785        if !roles.contains_key(name) {
786            return Err(format!("Role '{}' not found", name));
787        }
788        if name == "admin" || name == "operator" || name == "viewer" {
789            return Err("Cannot delete built-in roles".to_string());
790        }
791        roles.remove(name);
792        Ok(())
793    }
794
795    /// List all roles.
796    pub fn list_roles(&self) -> Vec<Role> {
797        self.roles.read().values().cloned().collect()
798    }
799
800    /// Get a specific role.
801    pub fn get_role(&self, name: &str) -> Option<Role> {
802        self.roles.read().get(name).cloned()
803    }
804
805    /// Assign a role to a user.
806    pub fn assign_role(&self, user_id: &str, role_name: &str) -> Result<(), String> {
807        if !self.roles.read().contains_key(role_name) {
808            return Err(format!("Role '{}' not found", role_name));
809        }
810
811        let mut user_roles = self.user_roles.write();
812        user_roles
813            .entry(user_id.to_string())
814            .or_insert_with(HashSet::new)
815            .insert(role_name.to_string());
816        Ok(())
817    }
818
819    /// Revoke a role from a user.
820    pub fn revoke_role(&self, user_id: &str, role_name: &str) -> Result<(), String> {
821        let mut user_roles = self.user_roles.write();
822        if let Some(roles) = user_roles.get_mut(user_id) {
823            roles.remove(role_name);
824            Ok(())
825        } else {
826            Err(format!("User '{}' has no roles assigned", user_id))
827        }
828    }
829
830    /// Get all roles for a user.
831    pub fn get_user_roles(&self, user_id: &str) -> Vec<String> {
832        self.user_roles
833            .read()
834            .get(user_id)
835            .map(|r| r.iter().cloned().collect())
836            .unwrap_or_default()
837    }
838
839    /// Check if a user has a specific permission.
840    pub fn check_permission(&self, user_id: &str, permission: Permission) -> bool {
841        let user_roles = self.user_roles.read();
842        let roles = self.roles.read();
843
844        if let Some(user_role_names) = user_roles.get(user_id) {
845            for role_name in user_role_names {
846                if let Some(role) = roles.get(role_name) {
847                    if role.has_permission(permission) {
848                        return true;
849                    }
850                }
851            }
852        }
853        false
854    }
855
856    /// Get all permissions for a user.
857    pub fn get_user_permissions(&self, user_id: &str) -> HashSet<Permission> {
858        let mut permissions = HashSet::new();
859        let user_roles = self.user_roles.read();
860        let roles = self.roles.read();
861
862        if let Some(user_role_names) = user_roles.get(user_id) {
863            for role_name in user_role_names {
864                if let Some(role) = roles.get(role_name) {
865                    permissions.extend(role.permissions.iter().cloned());
866                }
867            }
868        }
869        permissions
870    }
871
872    /// Add a row-level security policy.
873    pub fn add_row_policy(&self, policy: RowLevelPolicy) {
874        self.row_policies.write().push(policy);
875    }
876
877    /// Get row-level policies for a table.
878    pub fn get_row_policies(&self, table: &str, user_id: &str) -> Vec<RowLevelPolicy> {
879        self.row_policies
880            .read()
881            .iter()
882            .filter(|p| p.table == table && (p.applies_to.is_empty() || p.applies_to.contains(&user_id.to_string())))
883            .cloned()
884            .collect()
885    }
886}
887
888impl Default for RbacManager {
889    fn default() -> Self {
890        Self::new()
891    }
892}
893
894// =============================================================================
895// Row-Level Security
896// =============================================================================
897
898/// Row-level security policy.
899#[derive(Debug, Clone, Serialize, Deserialize)]
900pub struct RowLevelPolicy {
901    pub name: String,
902    pub table: String,
903    pub operation: RowPolicyOperation,
904    pub condition: String,
905    pub applies_to: Vec<String>,
906    pub enabled: bool,
907}
908
909/// Operations that row policies apply to.
910#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
911#[serde(rename_all = "lowercase")]
912pub enum RowPolicyOperation {
913    Select,
914    Insert,
915    Update,
916    Delete,
917    All,
918}
919
920// =============================================================================
921// Audit Logging
922// =============================================================================
923
924/// Audit event types.
925#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
926#[serde(rename_all = "snake_case")]
927pub enum AuditEventType {
928    // Authentication events
929    LoginSuccess,
930    LoginFailure,
931    Logout,
932    MfaVerified,
933    MfaFailed,
934    SessionExpired,
935
936    // Authorization events
937    PermissionGranted,
938    PermissionDenied,
939    RoleAssigned,
940    RoleRevoked,
941
942    // Data events
943    DataRead,
944    DataWrite,
945    DataDelete,
946    SchemaChange,
947
948    // Admin events
949    UserCreated,
950    UserDeleted,
951    UserModified,
952    ConfigChanged,
953    BackupCreated,
954    BackupRestored,
955
956    // System events
957    ServiceStarted,
958    ServiceStopped,
959    NodeJoined,
960    NodeLeft,
961}
962
963/// An audit log entry.
964#[derive(Debug, Clone, Serialize, Deserialize)]
965pub struct AuditEntry {
966    pub id: String,
967    pub timestamp: u64,
968    pub event_type: AuditEventType,
969    pub user_id: Option<String>,
970    pub username: Option<String>,
971    pub ip_address: Option<String>,
972    pub resource: Option<String>,
973    pub action: String,
974    pub result: AuditResult,
975    pub details: HashMap<String, String>,
976}
977
978/// Result of an audited action.
979#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
980#[serde(rename_all = "lowercase")]
981pub enum AuditResult {
982    Success,
983    Failure,
984    Denied,
985}
986
987/// Audit logger for compliance and security tracking.
988pub struct AuditLogger {
989    entries: RwLock<Vec<AuditEntry>>,
990    max_entries: usize,
991    entry_counter: RwLock<u64>,
992}
993
994impl AuditLogger {
995    /// Create a new audit logger.
996    pub fn new(max_entries: usize) -> Self {
997        Self {
998            entries: RwLock::new(Vec::with_capacity(max_entries)),
999            max_entries,
1000            entry_counter: RwLock::new(0),
1001        }
1002    }
1003
1004    /// Log an audit event.
1005    pub fn log(&self, event_type: AuditEventType, user_id: Option<&str>, username: Option<&str>,
1006               ip_address: Option<&str>, resource: Option<&str>, action: &str,
1007               result: AuditResult, details: HashMap<String, String>) {
1008        let mut counter = self.entry_counter.write();
1009        *counter += 1;
1010        let id = format!("audit-{:012}", *counter);
1011
1012        let entry = AuditEntry {
1013            id,
1014            timestamp: now_timestamp(),
1015            event_type,
1016            user_id: user_id.map(String::from),
1017            username: username.map(String::from),
1018            ip_address: ip_address.map(String::from),
1019            resource: resource.map(String::from),
1020            action: action.to_string(),
1021            result,
1022            details,
1023        };
1024
1025        let mut entries = self.entries.write();
1026        if entries.len() >= self.max_entries {
1027            entries.remove(0);
1028        }
1029        entries.push(entry);
1030    }
1031
1032    /// Log a login success.
1033    pub fn log_login_success(&self, user_id: &str, username: &str, ip: Option<&str>) {
1034        self.log(
1035            AuditEventType::LoginSuccess,
1036            Some(user_id),
1037            Some(username),
1038            ip,
1039            None,
1040            "User logged in",
1041            AuditResult::Success,
1042            HashMap::new(),
1043        );
1044    }
1045
1046    /// Log a login failure.
1047    pub fn log_login_failure(&self, username: &str, ip: Option<&str>, reason: &str) {
1048        let mut details = HashMap::new();
1049        details.insert("reason".to_string(), reason.to_string());
1050        self.log(
1051            AuditEventType::LoginFailure,
1052            None,
1053            Some(username),
1054            ip,
1055            None,
1056            "Login attempt failed",
1057            AuditResult::Failure,
1058            details,
1059        );
1060    }
1061
1062    /// Log a permission denial.
1063    pub fn log_permission_denied(&self, user_id: &str, username: &str, resource: &str, permission: &str) {
1064        let mut details = HashMap::new();
1065        details.insert("permission".to_string(), permission.to_string());
1066        self.log(
1067            AuditEventType::PermissionDenied,
1068            Some(user_id),
1069            Some(username),
1070            None,
1071            Some(resource),
1072            "Permission denied",
1073            AuditResult::Denied,
1074            details,
1075        );
1076    }
1077
1078    /// Log a data access.
1079    pub fn log_data_access(&self, user_id: &str, username: &str, table: &str, operation: &str, rows_affected: u64) {
1080        let mut details = HashMap::new();
1081        details.insert("rows_affected".to_string(), rows_affected.to_string());
1082        let event_type = match operation {
1083            "SELECT" => AuditEventType::DataRead,
1084            "INSERT" | "UPDATE" => AuditEventType::DataWrite,
1085            "DELETE" => AuditEventType::DataDelete,
1086            _ => AuditEventType::DataRead,
1087        };
1088        self.log(
1089            event_type,
1090            Some(user_id),
1091            Some(username),
1092            None,
1093            Some(table),
1094            &format!("{} on {}", operation, table),
1095            AuditResult::Success,
1096            details,
1097        );
1098    }
1099
1100    /// Log a schema change.
1101    pub fn log_schema_change(&self, user_id: &str, username: &str, object: &str, action: &str) {
1102        let mut details = HashMap::new();
1103        details.insert("action".to_string(), action.to_string());
1104        self.log(
1105            AuditEventType::SchemaChange,
1106            Some(user_id),
1107            Some(username),
1108            None,
1109            Some(object),
1110            &format!("Schema change: {} on {}", action, object),
1111            AuditResult::Success,
1112            details,
1113        );
1114    }
1115
1116    /// Get recent audit entries.
1117    pub fn get_entries(&self, limit: usize, offset: usize) -> Vec<AuditEntry> {
1118        let entries = self.entries.read();
1119        let start = entries.len().saturating_sub(limit + offset);
1120        let end = entries.len().saturating_sub(offset);
1121        entries[start..end].iter().rev().cloned().collect()
1122    }
1123
1124    /// Get entries by event type.
1125    pub fn get_entries_by_type(&self, event_type: AuditEventType, limit: usize) -> Vec<AuditEntry> {
1126        let entries = self.entries.read();
1127        entries
1128            .iter()
1129            .rev()
1130            .filter(|e| e.event_type == event_type)
1131            .take(limit)
1132            .cloned()
1133            .collect()
1134    }
1135
1136    /// Get entries for a specific user.
1137    pub fn get_entries_for_user(&self, user_id: &str, limit: usize) -> Vec<AuditEntry> {
1138        let entries = self.entries.read();
1139        entries
1140            .iter()
1141            .rev()
1142            .filter(|e| e.user_id.as_deref() == Some(user_id))
1143            .take(limit)
1144            .cloned()
1145            .collect()
1146    }
1147
1148    /// Get failed login attempts.
1149    pub fn get_failed_logins(&self, since: u64) -> Vec<AuditEntry> {
1150        let entries = self.entries.read();
1151        entries
1152            .iter()
1153            .filter(|e| e.event_type == AuditEventType::LoginFailure && e.timestamp >= since)
1154            .cloned()
1155            .collect()
1156    }
1157
1158    /// Count entries.
1159    pub fn count(&self) -> usize {
1160        self.entries.read().len()
1161    }
1162
1163    /// Export entries for compliance reporting.
1164    pub fn export(&self, start_time: u64, end_time: u64) -> Vec<AuditEntry> {
1165        let entries = self.entries.read();
1166        entries
1167            .iter()
1168            .filter(|e| e.timestamp >= start_time && e.timestamp <= end_time)
1169            .cloned()
1170            .collect()
1171    }
1172}
1173
1174impl Default for AuditLogger {
1175    fn default() -> Self {
1176        Self::new(100_000) // Keep 100k audit entries by default
1177    }
1178}
1179
1180// =============================================================================
1181// LDAP Authentication
1182// =============================================================================
1183
1184/// LDAP authenticator for enterprise directory integration.
1185pub struct LdapAuthenticator {
1186    config: LdapConfig,
1187}
1188
1189impl LdapAuthenticator {
1190    /// Create a new LDAP authenticator.
1191    pub fn new(config: LdapConfig) -> Self {
1192        Self { config }
1193    }
1194
1195    /// Authenticate user against LDAP.
1196    pub fn authenticate(&self, username: &str, password: &str) -> LdapAuthResult {
1197        // In production, this would use an LDAP library like ldap3
1198        // For now, we simulate LDAP authentication
1199
1200        // Simulate connection and bind
1201        if self.config.server_url.is_empty() {
1202            return LdapAuthResult {
1203                success: false,
1204                user_dn: None,
1205                email: None,
1206                display_name: None,
1207                groups: vec![],
1208                error: Some("LDAP server URL not configured".to_string()),
1209            };
1210        }
1211
1212        // Simulate user search
1213        let _user_filter = self.config.user_filter.replace("{username}", username);
1214        let user_dn = format!("uid={},{}", username, self.config.base_dn);
1215
1216        // Simulate bind with user credentials
1217        if password.is_empty() {
1218            return LdapAuthResult {
1219                success: false,
1220                user_dn: None,
1221                email: None,
1222                display_name: None,
1223                groups: vec![],
1224                error: Some("Invalid credentials".to_string()),
1225            };
1226        }
1227
1228        // Simulate group membership lookup
1229        let groups = if username == "ldapadmin" {
1230            self.config.admin_groups.clone()
1231        } else if username == "ldapoper" {
1232            self.config.operator_groups.clone()
1233        } else {
1234            vec![]
1235        };
1236
1237        LdapAuthResult {
1238            success: true,
1239            user_dn: Some(user_dn),
1240            email: Some(format!("{}@aegisdb.io", username)),
1241            display_name: Some(username.to_string()),
1242            groups,
1243            error: None,
1244        }
1245    }
1246
1247    /// Determine role from LDAP groups.
1248    pub fn determine_role(&self, groups: &[String]) -> UserRole {
1249        for group in groups {
1250            if self.config.admin_groups.contains(group) {
1251                return UserRole::Admin;
1252            }
1253        }
1254        for group in groups {
1255            if self.config.operator_groups.contains(group) {
1256                return UserRole::Operator;
1257            }
1258        }
1259        UserRole::Viewer
1260    }
1261}
1262
1263// =============================================================================
1264// OAuth2 Authentication
1265// =============================================================================
1266
1267/// OAuth2 authenticator for external identity providers.
1268pub struct OAuth2Authenticator {
1269    config: OAuth2Config,
1270    pending_states: RwLock<HashMap<String, OAuth2State>>,
1271}
1272
1273/// Pending OAuth2 state for CSRF protection.
1274#[derive(Debug, Clone)]
1275#[allow(dead_code)]
1276struct OAuth2State {
1277    created_at: Instant,
1278    redirect_uri: String,
1279}
1280
1281impl OAuth2Authenticator {
1282    /// Create a new OAuth2 authenticator.
1283    pub fn new(config: OAuth2Config) -> Self {
1284        Self {
1285            config,
1286            pending_states: RwLock::new(HashMap::new()),
1287        }
1288    }
1289
1290    /// Generate authorization URL for OAuth2 flow.
1291    pub fn get_authorization_url(&self) -> (String, String) {
1292        let state = generate_token();
1293
1294        // Store state for verification
1295        self.pending_states.write().insert(
1296            state.clone(),
1297            OAuth2State {
1298                created_at: Instant::now(),
1299                redirect_uri: self.config.redirect_uri.clone(),
1300            },
1301        );
1302
1303        let scopes = self.config.scopes.join(" ");
1304        let url = format!(
1305            "{}?client_id={}&redirect_uri={}&response_type=code&scope={}&state={}",
1306            self.config.authorization_url,
1307            self.config.client_id,
1308            urlencoding_encode(&self.config.redirect_uri),
1309            urlencoding_encode(&scopes),
1310            state
1311        );
1312
1313        (url, state)
1314    }
1315
1316    /// Verify state parameter.
1317    pub fn verify_state(&self, state: &str) -> bool {
1318        let mut states = self.pending_states.write();
1319        if let Some(stored) = states.remove(state) {
1320            stored.created_at.elapsed() < Duration::from_secs(600) // 10 minute timeout
1321        } else {
1322            false
1323        }
1324    }
1325
1326    /// Exchange authorization code for tokens (simulated).
1327    pub fn exchange_code(&self, _code: &str) -> Result<OAuth2TokenResponse, String> {
1328        // In production, this would make HTTP request to token endpoint
1329        Ok(OAuth2TokenResponse {
1330            access_token: generate_token(),
1331            token_type: "Bearer".to_string(),
1332            expires_in: Some(3600),
1333            refresh_token: Some(generate_token()),
1334            id_token: Some(generate_token()),
1335        })
1336    }
1337
1338    /// Get user info from OAuth2 provider (simulated).
1339    pub fn get_user_info(&self, _access_token: &str) -> Result<OAuth2UserInfo, String> {
1340        // In production, this would make HTTP request to userinfo endpoint
1341        Ok(OAuth2UserInfo {
1342            sub: generate_token()[..16].to_string(),
1343            email: Some("oauth.user@aegisdb.io".to_string()),
1344            name: Some("OAuth User".to_string()),
1345            preferred_username: Some("oauthuser".to_string()),
1346            roles: Some(vec!["viewer".to_string()]),
1347        })
1348    }
1349
1350    /// Determine role from OAuth2 claims.
1351    pub fn determine_role(&self, roles: &[String]) -> UserRole {
1352        for role in roles {
1353            if self.config.admin_roles.contains(role) {
1354                return UserRole::Admin;
1355            }
1356        }
1357        for role in roles {
1358            if self.config.operator_roles.contains(role) {
1359                return UserRole::Operator;
1360            }
1361        }
1362        UserRole::Viewer
1363    }
1364}
1365
1366/// Simple URL encoding (subset).
1367fn urlencoding_encode(s: &str) -> String {
1368    s.replace(' ', "%20")
1369        .replace('&', "%26")
1370        .replace('=', "%3D")
1371        .replace('?', "%3F")
1372        .replace('/', "%2F")
1373}
1374
1375// =============================================================================
1376// Tests
1377// =============================================================================
1378
1379#[cfg(test)]
1380mod tests {
1381    use super::*;
1382
1383    #[test]
1384    fn test_login_success() {
1385        let auth = AuthService::new();
1386        let response = auth.login("demo", "demo");
1387        assert!(response.token.is_some());
1388        assert!(response.user.is_some());
1389    }
1390
1391    #[test]
1392    fn test_login_invalid_password() {
1393        let auth = AuthService::new();
1394        let response = auth.login("demo", "wrong");
1395        assert!(response.error.is_some());
1396    }
1397
1398    #[test]
1399    fn test_login_mfa_required() {
1400        let auth = AuthService::new();
1401        let response = auth.login("admin", "admin");
1402        assert!(response.requires_mfa == Some(true));
1403        assert!(response.token.is_some());
1404    }
1405
1406    #[test]
1407    fn test_mfa_verification() {
1408        let auth = AuthService::new();
1409        let login_response = auth.login("admin", "admin");
1410        let temp_token = login_response.token.unwrap();
1411
1412        let mfa_response = auth.verify_mfa("123456", &temp_token);
1413        assert!(mfa_response.token.is_some());
1414        assert!(mfa_response.user.is_some());
1415    }
1416
1417    #[test]
1418    fn test_session_validation() {
1419        let auth = AuthService::new();
1420        let response = auth.login("demo", "demo");
1421        let token = response.token.unwrap();
1422
1423        let user = auth.validate_session(&token);
1424        assert!(user.is_some());
1425        assert_eq!(user.unwrap().username, "demo");
1426    }
1427
1428    #[test]
1429    fn test_logout() {
1430        let auth = AuthService::new();
1431        let response = auth.login("demo", "demo");
1432        let token = response.token.unwrap();
1433
1434        assert!(auth.logout(&token));
1435        assert!(auth.validate_session(&token).is_none());
1436    }
1437
1438    // RBAC Tests
1439    #[test]
1440    fn test_rbac_default_roles() {
1441        let rbac = RbacManager::new();
1442        let roles = rbac.list_roles();
1443        assert!(roles.iter().any(|r| r.name == "admin"));
1444        assert!(roles.iter().any(|r| r.name == "operator"));
1445        assert!(roles.iter().any(|r| r.name == "viewer"));
1446    }
1447
1448    #[test]
1449    fn test_rbac_check_permission() {
1450        let rbac = RbacManager::new();
1451        // Admin user should have all permissions
1452        assert!(rbac.check_permission("user-001", Permission::DatabaseCreate));
1453        assert!(rbac.check_permission("user-001", Permission::ClusterManage));
1454
1455        // Viewer should only have read permissions
1456        assert!(rbac.check_permission("user-002", Permission::DataSelect));
1457        assert!(!rbac.check_permission("user-002", Permission::DataInsert));
1458    }
1459
1460    #[test]
1461    fn test_rbac_create_role() {
1462        let rbac = RbacManager::new();
1463        let result = rbac.create_role(
1464            "custom_role",
1465            "Custom role for testing",
1466            vec![Permission::DataSelect, Permission::DataInsert],
1467            "admin"
1468        );
1469        assert!(result.is_ok());
1470
1471        let role = rbac.get_role("custom_role");
1472        assert!(role.is_some());
1473        assert!(role.unwrap().has_permission(Permission::DataSelect));
1474    }
1475
1476    #[test]
1477    fn test_rbac_assign_role() {
1478        let rbac = RbacManager::new();
1479        let result = rbac.assign_role("user-004", "analyst");
1480        assert!(result.is_ok());
1481
1482        let roles = rbac.get_user_roles("user-004");
1483        assert!(roles.contains(&"analyst".to_string()));
1484    }
1485
1486    #[test]
1487    fn test_rbac_cannot_delete_builtin() {
1488        let rbac = RbacManager::new();
1489        let result = rbac.delete_role("admin");
1490        assert!(result.is_err());
1491    }
1492
1493    // Audit Logging Tests
1494    #[test]
1495    fn test_audit_log_entry() {
1496        let audit = AuditLogger::new(1000);
1497        audit.log_login_success("user-001", "admin", Some("192.168.1.1"));
1498
1499        let entries = audit.get_entries(10, 0);
1500        assert_eq!(entries.len(), 1);
1501        assert_eq!(entries[0].event_type, AuditEventType::LoginSuccess);
1502    }
1503
1504    #[test]
1505    fn test_audit_get_by_type() {
1506        let audit = AuditLogger::new(1000);
1507        audit.log_login_success("user-001", "admin", None);
1508        audit.log_login_failure("baduser", None, "Invalid password");
1509        audit.log_login_success("user-002", "demo", None);
1510
1511        let failures = audit.get_entries_by_type(AuditEventType::LoginFailure, 10);
1512        assert_eq!(failures.len(), 1);
1513
1514        let successes = audit.get_entries_by_type(AuditEventType::LoginSuccess, 10);
1515        assert_eq!(successes.len(), 2);
1516    }
1517
1518    #[test]
1519    fn test_audit_get_for_user() {
1520        let audit = AuditLogger::new(1000);
1521        audit.log_login_success("user-001", "admin", None);
1522        audit.log_data_access("user-001", "admin", "users", "SELECT", 10);
1523        audit.log_login_success("user-002", "demo", None);
1524
1525        let user1_entries = audit.get_entries_for_user("user-001", 10);
1526        assert_eq!(user1_entries.len(), 2);
1527    }
1528
1529    #[test]
1530    fn test_audit_max_entries() {
1531        let audit = AuditLogger::new(5);
1532        for i in 0..10 {
1533            audit.log_login_success(&format!("user-{}", i), "test", None);
1534        }
1535
1536        assert_eq!(audit.count(), 5);
1537    }
1538
1539    // LDAP Tests
1540    #[test]
1541    fn test_ldap_authenticator() {
1542        let config = LdapConfig::default();
1543        let ldap = LdapAuthenticator::new(config);
1544
1545        let result = ldap.authenticate("ldapadmin", "password");
1546        assert!(result.success);
1547        assert!(!result.groups.is_empty());
1548    }
1549
1550    #[test]
1551    fn test_ldap_role_mapping() {
1552        let config = LdapConfig::default();
1553        let ldap = LdapAuthenticator::new(config.clone());
1554
1555        let admin_role = ldap.determine_role(&config.admin_groups);
1556        assert_eq!(admin_role, UserRole::Admin);
1557
1558        let viewer_role = ldap.determine_role(&[]);
1559        assert_eq!(viewer_role, UserRole::Viewer);
1560    }
1561
1562    // OAuth2 Tests
1563    #[test]
1564    fn test_oauth2_authorization_url() {
1565        let config = OAuth2Config::default();
1566        let oauth = OAuth2Authenticator::new(config);
1567
1568        let (url, state) = oauth.get_authorization_url();
1569        assert!(url.contains("client_id="));
1570        assert!(url.contains(&state));
1571    }
1572
1573    #[test]
1574    fn test_oauth2_state_verification() {
1575        let config = OAuth2Config::default();
1576        let oauth = OAuth2Authenticator::new(config);
1577
1578        let (_, state) = oauth.get_authorization_url();
1579        assert!(oauth.verify_state(&state));
1580        assert!(!oauth.verify_state(&state)); // Second call should fail
1581    }
1582
1583    #[test]
1584    fn test_oauth2_role_mapping() {
1585        let config = OAuth2Config::default();
1586        let oauth = OAuth2Authenticator::new(config.clone());
1587
1588        let admin_role = oauth.determine_role(&config.admin_roles);
1589        assert_eq!(admin_role, UserRole::Admin);
1590    }
1591}