auth_framework/session/
manager.rs

1//! Comprehensive session management with security hardening.
2//!
3//! This module provides secure session management with features like
4//! session rotation, concurrent session limits, device tracking,
5//! and advanced security protections.
6
7use crate::audit::{AuditLogger, AuditStorage, GeolocationInfo, RequestMetadata};
8use crate::errors::{AuthError, Result};
9use crate::threat_intelligence::{ThreatFeedManager, ThreatIntelConfig};
10use async_trait::async_trait;
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13use std::time::{Duration, SystemTime};
14
15// Additional imports for session security
16
17/// Session information
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct Session {
20    /// Unique session ID
21    pub id: String,
22    /// User ID this session belongs to
23    pub user_id: String,
24    /// When the session was created
25    pub created_at: SystemTime,
26    /// When the session was last accessed
27    pub last_accessed: SystemTime,
28    /// When the session expires
29    pub expires_at: SystemTime,
30    /// Session state
31    pub state: SessionState,
32    /// Device information
33    pub device_info: DeviceInfo,
34    /// Security metadata
35    pub security_metadata: SecurityMetadata,
36    /// Session data (custom application data)
37    pub data: HashMap<String, String>,
38    /// MFA verification status
39    pub mfa_verified: bool,
40    /// Permissions cache (for performance)
41    pub cached_permissions: Option<Vec<String>>,
42    /// Last activity details
43    pub last_activity: ActivityInfo,
44}
45
46/// Session state
47#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
48pub enum SessionState {
49    Active,
50    Expired,
51    Revoked,
52    Suspended,
53    RequiresMfa,
54    RequiresReauth,
55}
56
57/// Device information for session tracking
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct DeviceInfo {
60    /// Device fingerprint (unique identifier)
61    pub fingerprint: String,
62    /// Device type (mobile, desktop, tablet, etc.)
63    pub device_type: String,
64    /// Operating system
65    pub operating_system: Option<String>,
66    /// Browser information
67    pub browser: Option<String>,
68    /// Screen resolution
69    pub screen_resolution: Option<String>,
70    /// Timezone
71    pub timezone: Option<String>,
72    /// Language preferences
73    pub language: Option<String>,
74    /// Whether this is a trusted device
75    pub is_trusted: bool,
76    /// Device name (user-assigned)
77    pub device_name: Option<String>,
78}
79
80/// Security metadata for sessions
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct SecurityMetadata {
83    /// IP address when session was created
84    pub creation_ip: String,
85    /// Current IP address
86    pub current_ip: String,
87    /// Geographic location when created
88    pub creation_location: Option<GeolocationInfo>,
89    /// Current geographic location
90    pub current_location: Option<GeolocationInfo>,
91    /// Security flags
92    pub security_flags: Vec<SecurityFlag>,
93    /// Risk score (0-100)
94    pub risk_score: u8,
95    /// Whether location has changed
96    pub location_changed: bool,
97    /// Whether IP has changed
98    pub ip_changed: bool,
99    /// Number of failed authentication attempts
100    pub failed_auth_attempts: u32,
101}
102
103/// Security flags for sessions
104#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
105pub enum SecurityFlag {
106    SuspiciousActivity,
107    LocationAnomaly,
108    DeviceAnomaly,
109    TimeAnomaly,
110    ConcurrentSessionLimit,
111    BruteForceAttempt,
112    RequiresVerification,
113    ElevatedPrivileges,
114}
115
116/// Activity information
117#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct ActivityInfo {
119    /// Last endpoint accessed
120    pub endpoint: Option<String>,
121    /// Last action performed
122    pub action: Option<String>,
123    /// Request metadata
124    pub request_metadata: Option<RequestMetadata>,
125    /// Activity timestamp
126    pub timestamp: SystemTime,
127}
128
129/// Session configuration
130#[derive(Debug, Clone)]
131pub struct SessionConfig {
132    /// Default session duration
133    pub default_duration: Duration,
134    /// Maximum session duration
135    pub max_duration: Duration,
136    /// Session idle timeout
137    pub idle_timeout: Duration,
138    /// Whether to rotate session IDs on privilege escalation
139    pub rotate_on_privilege_escalation: bool,
140    /// Whether to rotate session IDs periodically
141    pub rotate_periodically: bool,
142    /// Rotation interval
143    pub rotation_interval: Duration,
144    /// Maximum concurrent sessions per user
145    pub max_concurrent_sessions: Option<u32>,
146    /// Whether to track device fingerprints
147    pub track_device_fingerprints: bool,
148    /// Whether to enforce geographic restrictions
149    pub enforce_geographic_restrictions: bool,
150    /// Allowed countries (if geographic restrictions enabled)
151    pub allowed_countries: Vec<String>,
152    /// Security policy
153    pub security_policy: SessionSecurityPolicy,
154}
155
156/// Session security policy
157#[derive(Debug, Clone)]
158pub struct SessionSecurityPolicy {
159    /// Require MFA for new devices
160    pub require_mfa_for_new_devices: bool,
161    /// Require re-auth for sensitive operations
162    pub require_reauth_for_sensitive_ops: bool,
163    /// Timeout for re-auth requirement
164    pub reauth_timeout: Duration,
165    /// Maximum risk score allowed
166    pub max_risk_score: u8,
167    /// Whether to auto-suspend suspicious sessions
168    pub auto_suspend_suspicious: bool,
169    /// Whether to require verification after location change
170    pub verify_location_changes: bool,
171    /// Whether to limit concurrent sessions
172    pub limit_concurrent_sessions: bool,
173}
174
175/// Session storage trait
176#[async_trait]
177pub trait SessionStorage: Send + Sync {
178    /// Create a new session
179    async fn create_session(&self, session: &Session) -> Result<()>;
180
181    /// Get session by ID
182    async fn get_session(&self, session_id: &str) -> Result<Option<Session>>;
183
184    /// Update existing session
185    async fn update_session(&self, session: &Session) -> Result<()>;
186
187    /// Delete session
188    async fn delete_session(&self, session_id: &str) -> Result<()>;
189
190    /// Get all sessions for a user
191    async fn get_user_sessions(&self, user_id: &str) -> Result<Vec<Session>>;
192
193    /// Get active sessions count for a user
194    async fn count_active_sessions(&self, user_id: &str) -> Result<u32>;
195
196    /// Clean up expired sessions
197    async fn cleanup_expired_sessions(&self) -> Result<u32>;
198
199    /// Find sessions by device fingerprint
200    async fn find_sessions_by_device(&self, device_fingerprint: &str) -> Result<Vec<Session>>;
201
202    /// Find sessions by IP address
203    async fn find_sessions_by_ip(&self, ip_address: &str) -> Result<Vec<Session>>;
204}
205
206/// Main session manager
207pub struct SessionManager<S: SessionStorage, A: AuditStorage> {
208    storage: S,
209    config: SessionConfig,
210    audit_logger: AuditLogger<A>,
211    fingerprint_generator: DeviceFingerprintGenerator,
212    risk_calculator: RiskCalculator,
213    threat_intel_manager: Option<ThreatFeedManager>,
214}
215
216impl<S: SessionStorage, A: AuditStorage> SessionManager<S, A> {
217    /// Create a new session manager
218    pub fn new(storage: S, config: SessionConfig, audit_logger: AuditLogger<A>) -> Self {
219        // Initialize automated threat intelligence if enabled
220        let threat_intel_manager = if std::env::var("THREAT_INTEL_ENABLED")
221            .unwrap_or_else(|_| "false".to_string())
222            .to_lowercase()
223            == "true"
224        {
225            match ThreatIntelConfig::from_env_and_config() {
226                Ok(intel_config) => {
227                    log::info!(
228                        "🟢 Automated threat intelligence enabled - feeds will update automatically"
229                    );
230                    match ThreatFeedManager::new(intel_config) {
231                        Ok(manager) => {
232                            // Start automated feed management in background
233                            if let Err(e) = manager.start_automated_updates() {
234                                log::error!("Failed to start automated threat feed updates: {}", e);
235                                None
236                            } else {
237                                log::info!(
238                                    "✅ Threat intelligence automation started successfully"
239                                );
240                                Some(manager)
241                            }
242                        }
243                        Err(e) => {
244                            log::error!("Failed to initialize threat intelligence manager: {}", e);
245                            None
246                        }
247                    }
248                }
249                Err(e) => {
250                    log::error!("Failed to load threat intelligence configuration: {}", e);
251                    None
252                }
253            }
254        } else {
255            log::info!("🔴 Automated threat intelligence disabled (THREAT_INTEL_ENABLED=false)");
256            None
257        };
258
259        Self {
260            storage,
261            config,
262            audit_logger,
263            fingerprint_generator: DeviceFingerprintGenerator::new(),
264            risk_calculator: RiskCalculator::new(),
265            threat_intel_manager,
266        }
267    }
268
269    /// Create a new session
270    pub async fn create_session(
271        &self,
272        user_id: &str,
273        mut device_info: DeviceInfo,
274        metadata: RequestMetadata,
275    ) -> Result<Session> {
276        // Generate device fingerprint if not already present
277        if device_info.fingerprint.is_empty() {
278            device_info.fingerprint = self.fingerprint_generator.generate_fingerprint(&metadata);
279        }
280
281        // Check concurrent session limits
282        if let Some(max_sessions) = self.config.max_concurrent_sessions {
283            let active_count = self.storage.count_active_sessions(user_id).await?;
284            if active_count >= max_sessions {
285                return Err(AuthError::TooManyConcurrentSessions);
286            }
287        }
288
289        let now = SystemTime::now();
290        let session_id = self.generate_session_id();
291
292        // Calculate risk score
293        let risk_score = self.risk_calculator.calculate_risk(
294            &device_info,
295            &metadata,
296            &self.get_user_session_history(user_id).await?,
297            self.threat_intel_manager.as_ref(),
298        );
299
300        let mut security_flags = Vec::new();
301        if risk_score > 70 {
302            security_flags.push(SecurityFlag::SuspiciousActivity);
303        }
304
305        // Check if this is a new device
306        let existing_sessions = self
307            .storage
308            .find_sessions_by_device(&device_info.fingerprint)
309            .await?;
310        let is_new_device = existing_sessions.is_empty();
311
312        if is_new_device && self.config.security_policy.require_mfa_for_new_devices {
313            security_flags.push(SecurityFlag::RequiresVerification);
314        }
315
316        let session = Session {
317            id: session_id.clone(),
318            user_id: user_id.to_string(),
319            created_at: now,
320            last_accessed: now,
321            expires_at: now + self.config.default_duration,
322            state: if security_flags.contains(&SecurityFlag::RequiresVerification) {
323                SessionState::RequiresMfa
324            } else {
325                SessionState::Active
326            },
327            device_info: device_info.clone(),
328            security_metadata: SecurityMetadata {
329                creation_ip: metadata.ip_address.clone().unwrap_or_default(),
330                current_ip: metadata.ip_address.clone().unwrap_or_default(),
331                creation_location: metadata.geolocation.clone(),
332                current_location: metadata.geolocation.clone(),
333                security_flags,
334                risk_score,
335                location_changed: false,
336                ip_changed: false,
337                failed_auth_attempts: 0,
338            },
339            data: HashMap::new(),
340            mfa_verified: false,
341            cached_permissions: None,
342            last_activity: ActivityInfo {
343                endpoint: metadata.endpoint.clone(),
344                action: Some("session_created".to_string()),
345                request_metadata: Some(metadata.clone()),
346                timestamp: now,
347            },
348        };
349
350        self.storage.create_session(&session).await?;
351
352        // Log session creation
353        self.audit_logger
354            .log_event(crate::audit::AuditEvent {
355                id: String::new(),
356                event_type: crate::audit::AuditEventType::LoginSuccess,
357                timestamp: now,
358                user_id: Some(user_id.to_string()),
359                session_id: Some(session_id),
360                outcome: crate::audit::EventOutcome::Success,
361                risk_level: if risk_score > 70 {
362                    crate::audit::RiskLevel::High
363                } else {
364                    crate::audit::RiskLevel::Low
365                },
366                description: "Session created".to_string(),
367                details: HashMap::new(),
368                request_metadata: metadata,
369                resource: None,
370                actor: crate::audit::ActorInfo {
371                    actor_type: "user".to_string(),
372                    actor_id: user_id.to_string(),
373                    actor_name: None,
374                    roles: vec![],
375                },
376                correlation_id: None,
377            })
378            .await?;
379
380        Ok(session)
381    }
382
383    /// Validate and refresh a session
384    pub async fn validate_session(
385        &self,
386        session_id: &str,
387        metadata: RequestMetadata,
388    ) -> Result<Option<Session>> {
389        let mut session = match self.storage.get_session(session_id).await? {
390            Some(session) => session,
391            None => return Ok(None),
392        };
393
394        let now = SystemTime::now();
395
396        // Check if session is expired
397        if session.expires_at <= now {
398            session.state = SessionState::Expired;
399            self.storage.update_session(&session).await?;
400            return Ok(None);
401        }
402
403        // Validate device fingerprint for security
404        let current_fingerprint = self.fingerprint_generator.generate_fingerprint(&metadata);
405        if current_fingerprint != session.device_info.fingerprint {
406            // Device fingerprint mismatch - potential session hijacking
407            session.state = SessionState::RequiresMfa;
408            self.storage.update_session(&session).await?;
409
410            // Log security event
411            self.audit_logger
412                .log_suspicious_activity(
413                    Some(&session.user_id),
414                    "device_fingerprint_mismatch",
415                    &format!(
416                        "Session ID: {}, Expected: {}, Got: {}",
417                        session_id, session.device_info.fingerprint, current_fingerprint
418                    ),
419                    metadata.clone(),
420                )
421                .await?;
422        }
423
424        // Check if session is idle too long
425        let idle_duration = now
426            .duration_since(session.last_accessed)
427            .unwrap_or_default();
428        if idle_duration > self.config.idle_timeout {
429            session.state = SessionState::Expired;
430            self.storage.update_session(&session).await?;
431            return Ok(None);
432        }
433
434        // Check session state
435        match session.state {
436            SessionState::Expired | SessionState::Revoked | SessionState::Suspended => {
437                return Ok(None);
438            }
439            SessionState::RequiresMfa | SessionState::RequiresReauth => {
440                // Return session but caller needs to handle MFA/reauth
441                return Ok(Some(session));
442            }
443            SessionState::Active => {}
444        }
445
446        // Update security metadata
447        let current_ip = metadata.ip_address.clone().unwrap_or_default();
448        let ip_changed = current_ip != session.security_metadata.current_ip;
449
450        if ip_changed {
451            session.security_metadata.ip_changed = true;
452            session.security_metadata.current_ip = current_ip;
453
454            // Check if location verification is required
455            if self.config.security_policy.verify_location_changes {
456                session
457                    .security_metadata
458                    .security_flags
459                    .push(SecurityFlag::LocationAnomaly);
460                session.state = SessionState::RequiresReauth;
461            }
462        }
463
464        // Update last accessed time and activity
465        session.last_accessed = now;
466        session.last_activity = ActivityInfo {
467            endpoint: metadata.endpoint.clone(),
468            action: Some("session_validated".to_string()),
469            request_metadata: Some(metadata),
470            timestamp: now,
471        };
472
473        // Check if session rotation is needed
474        let should_rotate = self.should_rotate_session(&session);
475        if should_rotate {
476            let new_session_id = self.generate_session_id();
477            let old_session_id = session.id.clone();
478            session.id = new_session_id;
479
480            // Delete old session and create new one
481            self.storage.delete_session(&old_session_id).await?;
482            self.storage.create_session(&session).await?;
483        } else {
484            self.storage.update_session(&session).await?;
485        }
486
487        Ok(Some(session))
488    }
489
490    /// Revoke a session
491    pub async fn revoke_session(&self, session_id: &str) -> Result<()> {
492        if let Some(mut session) = self.storage.get_session(session_id).await? {
493            session.state = SessionState::Revoked;
494            self.storage.update_session(&session).await?;
495
496            // Log session revocation
497            self.audit_logger
498                .log_event(crate::audit::AuditEvent {
499                    id: String::new(),
500                    event_type: crate::audit::AuditEventType::Logout,
501                    timestamp: SystemTime::now(),
502                    user_id: Some(session.user_id),
503                    session_id: Some(session_id.to_string()),
504                    outcome: crate::audit::EventOutcome::Success,
505                    risk_level: crate::audit::RiskLevel::Low,
506                    description: "Session revoked".to_string(),
507                    details: HashMap::new(),
508                    request_metadata: crate::audit::RequestMetadata::default(),
509                    resource: None,
510                    actor: crate::audit::ActorInfo {
511                        actor_type: "system".to_string(),
512                        actor_id: "session_manager".to_string(),
513                        actor_name: None,
514                        roles: vec![],
515                    },
516                    correlation_id: None,
517                })
518                .await?;
519        }
520        Ok(())
521    }
522
523    /// Revoke all sessions for a user
524    pub async fn revoke_all_user_sessions(&self, user_id: &str) -> Result<u32> {
525        let sessions = self.storage.get_user_sessions(user_id).await?;
526        let mut revoked_count = 0;
527
528        for mut session in sessions {
529            if session.state == SessionState::Active {
530                session.state = SessionState::Revoked;
531                self.storage.update_session(&session).await?;
532                revoked_count += 1;
533            }
534        }
535
536        Ok(revoked_count)
537    }
538
539    /// Get user sessions with filtering
540    pub async fn get_user_sessions(
541        &self,
542        user_id: &str,
543        include_inactive: bool,
544    ) -> Result<Vec<Session>> {
545        let mut sessions = self.storage.get_user_sessions(user_id).await?;
546
547        if !include_inactive {
548            sessions.retain(|s| s.state == SessionState::Active);
549        }
550
551        Ok(sessions)
552    }
553
554    /// Clean up expired sessions
555    pub async fn cleanup_expired_sessions(&self) -> Result<u32> {
556        self.storage.cleanup_expired_sessions().await
557    }
558
559    /// Generate device fingerprint for given request metadata
560    pub fn generate_device_fingerprint(&self, metadata: &RequestMetadata) -> String {
561        self.fingerprint_generator.generate_fingerprint(metadata)
562    }
563
564    /// Validate device fingerprint against session
565    pub fn validate_device_fingerprint(
566        &self,
567        session: &Session,
568        metadata: &RequestMetadata,
569    ) -> bool {
570        let current_fingerprint = self.fingerprint_generator.generate_fingerprint(metadata);
571        current_fingerprint == session.device_info.fingerprint
572    }
573
574    /// Suspend suspicious sessions
575    pub async fn suspend_session(&self, session_id: &str, reason: &str) -> Result<()> {
576        if let Some(mut session) = self.storage.get_session(session_id).await? {
577            session.state = SessionState::Suspended;
578            session
579                .security_metadata
580                .security_flags
581                .push(SecurityFlag::SuspiciousActivity);
582
583            self.storage.update_session(&session).await?;
584
585            // Log suspension
586            let mut details = HashMap::new();
587            details.insert("suspension_reason".to_string(), reason.to_string());
588
589            self.audit_logger
590                .log_event(crate::audit::AuditEvent {
591                    id: String::new(),
592                    event_type: crate::audit::AuditEventType::AccountLocked,
593                    timestamp: SystemTime::now(),
594                    user_id: Some(session.user_id),
595                    session_id: Some(session_id.to_string()),
596                    outcome: crate::audit::EventOutcome::Success,
597                    risk_level: crate::audit::RiskLevel::High,
598                    description: format!("Session suspended: {}", reason),
599                    details,
600                    request_metadata: crate::audit::RequestMetadata::default(),
601                    resource: None,
602                    actor: crate::audit::ActorInfo {
603                        actor_type: "system".to_string(),
604                        actor_id: "security_monitor".to_string(),
605                        actor_name: None,
606                        roles: vec![],
607                    },
608                    correlation_id: None,
609                })
610                .await?;
611        }
612        Ok(())
613    }
614
615    /// Update session data
616    pub async fn update_session_data(
617        &self,
618        session_id: &str,
619        key: &str,
620        value: &str,
621    ) -> Result<()> {
622        if let Some(mut session) = self.storage.get_session(session_id).await? {
623            session.data.insert(key.to_string(), value.to_string());
624            session.last_accessed = SystemTime::now();
625            self.storage.update_session(&session).await?;
626        }
627        Ok(())
628    }
629
630    /// Generate a new session ID
631    fn generate_session_id(&self) -> String {
632        format!("sess_{}", uuid::Uuid::new_v4())
633    }
634
635    /// Check if session should be rotated
636    fn should_rotate_session(&self, session: &Session) -> bool {
637        if !self.config.rotate_periodically {
638            return false;
639        }
640
641        let session_age = SystemTime::now()
642            .duration_since(session.created_at)
643            .unwrap_or_default();
644
645        session_age > self.config.rotation_interval
646    }
647
648    /// Get user session history for risk calculation
649    async fn get_user_session_history(&self, user_id: &str) -> Result<Vec<Session>> {
650        // This would typically be a more sophisticated query
651        // For now, just return recent sessions
652        self.storage.get_user_sessions(user_id).await
653    }
654}
655
656/// Device fingerprint generator
657pub struct DeviceFingerprintGenerator;
658
659impl Default for DeviceFingerprintGenerator {
660    fn default() -> Self {
661        Self::new()
662    }
663}
664
665impl DeviceFingerprintGenerator {
666    pub fn new() -> Self {
667        Self
668    }
669
670    /// Generate device fingerprint from request metadata
671    pub fn generate_fingerprint(&self, metadata: &RequestMetadata) -> String {
672        let mut fingerprint_data = Vec::new();
673
674        if let Some(ua) = &metadata.user_agent {
675            fingerprint_data.push(ua.clone());
676        }
677
678        // Comprehensive device fingerprinting implementation
679        self.add_advanced_fingerprinting_data(&mut fingerprint_data, metadata);
680
681        let fingerprint_string = fingerprint_data.join("|");
682        format!("fp_{:x}", crc32fast::hash(fingerprint_string.as_bytes()))
683    }
684
685    /// Add advanced fingerprinting data based on available metadata
686    fn add_advanced_fingerprinting_data(
687        &self,
688        fingerprint_data: &mut Vec<String>,
689        metadata: &RequestMetadata,
690    ) {
691        // Screen characteristics (if available)
692        if let Some(ref ip) = metadata.ip_address {
693            // Extract geographical and ISP information from IP
694            fingerprint_data.push(format!("geo:{}", self.get_ip_geolocation(ip)));
695        }
696
697        // Browser/client characteristics
698        fingerprint_data.push(format!("lang:{}", self.get_system_language()));
699        fingerprint_data.push(format!("tz:{}", self.get_timezone_offset()));
700        fingerprint_data.push(format!("hw:{}", self.get_hardware_concurrency()));
701
702        // Network characteristics
703        fingerprint_data.push(format!("conn:{}", self.get_connection_info()));
704
705        // Additional entropy sources
706        fingerprint_data.push(format!("caps:{}", self.get_client_capabilities()));
707    }
708
709    /// Get IP geolocation information for fingerprinting
710    fn get_ip_geolocation(&self, ip: &str) -> String {
711        use std::net::IpAddr;
712        use std::str::FromStr;
713
714        // Parse IP address for classification
715        if let Ok(ip_addr) = IpAddr::from_str(ip) {
716            match ip_addr {
717                IpAddr::V4(ipv4) => {
718                    let octets = ipv4.octets();
719
720                    // RFC 1918 private networks
721                    if (octets[0] == 10)
722                        || (octets[0] == 172 && octets[1] >= 16 && octets[1] <= 31)
723                        || (octets[0] == 192 && octets[1] == 168)
724                    {
725                        return "private_rfc1918".to_string();
726                    }
727
728                    // Loopback
729                    if octets[0] == 127 {
730                        return "loopback".to_string();
731                    }
732
733                    // Link-local (169.254.x.x)
734                    if octets[0] == 169 && octets[1] == 254 {
735                        return "link_local".to_string();
736                    }
737
738                    // Multicast
739                    if octets[0] >= 224 && octets[0] <= 239 {
740                        return "multicast".to_string();
741                    }
742
743                    // Known public DNS servers
744                    match (octets[0], octets[1], octets[2], octets[3]) {
745                        (8, 8, 8, 8) | (8, 8, 4, 4) => "google_dns".to_string(),
746                        (1, 1, 1, 1) | (1, 0, 0, 1) => "cloudflare_dns".to_string(),
747                        (208, 67, 222, 222) | (208, 67, 220, 220) => "opendns".to_string(),
748                        _ => {
749                            // Real MaxMind GeoIP2 database integration
750                            self.lookup_maxmind_geolocation(&ipv4).unwrap_or_else(|| {
751                                // Fallback to basic regional classification
752                                match octets[0] {
753                                    1..=23 => "apnic_region".to_string(),    // APNIC (Asia-Pacific)
754                                    24..=49 => "arin_region".to_string(),    // ARIN (North America)
755                                    50..=99 => "ripe_region".to_string(), // RIPE (Europe/Middle East)
756                                    100..=127 => "mixed_region".to_string(), // Various registries
757                                    128..=191 => "arin_region".to_string(), // ARIN
758                                    192..=223 => "ripe_apnic_region".to_string(), // RIPE/APNIC
759                                    _ => format!("public_class_{}", octets[0] / 64),
760                                }
761                            })
762                        }
763                    }
764                }
765                IpAddr::V6(ipv6) => {
766                    let segments = ipv6.segments();
767
768                    // IPv6 loopback
769                    if ipv6.is_loopback() {
770                        return "ipv6_loopback".to_string();
771                    }
772
773                    // IPv6 link-local (fe80::/10)
774                    if segments[0] & 0xffc0 == 0xfe80 {
775                        return "ipv6_link_local".to_string();
776                    }
777
778                    // IPv6 unique local (fc00::/7)
779                    if segments[0] & 0xfe00 == 0xfc00 {
780                        return "ipv6_unique_local".to_string();
781                    }
782
783                    // IPv6 multicast (ff00::/8)
784                    if segments[0] & 0xff00 == 0xff00 {
785                        return "ipv6_multicast".to_string();
786                    }
787
788                    // Global unicast
789                    format!("ipv6_global_{:x}", segments[0] / 0x1000)
790                }
791            }
792        } else {
793            "invalid_ip".to_string()
794        }
795    }
796
797    /// Get system language for fingerprinting
798    fn get_system_language(&self) -> String {
799        // Would be extracted from Accept-Language header or client info
800        "en-US".to_string()
801    }
802
803    /// Get timezone offset for fingerprinting
804    fn get_timezone_offset(&self) -> String {
805        // Would be provided by client-side JavaScript
806        "-05:00".to_string() // EST example
807    }
808
809    /// Get hardware concurrency for fingerprinting
810    fn get_hardware_concurrency(&self) -> String {
811        // Would be provided by client via navigator.hardwareConcurrency
812        "4".to_string()
813    }
814
815    /// Get connection information for fingerprinting
816    fn get_connection_info(&self) -> String {
817        // Would include connection speed, type, etc.
818        "wifi".to_string()
819    }
820
821    /// Get client capabilities for fingerprinting
822    fn get_client_capabilities(&self) -> String {
823        // Would include supported features, WebGL renderer, etc.
824        "webgl2_canvas_audio".to_string()
825    }
826
827    /// Lookup IP geolocation using MaxMind GeoIP2 database
828    fn lookup_maxmind_geolocation(&self, ip: &std::net::Ipv4Addr) -> Option<String> {
829        use std::path::Path;
830
831        // Path to MaxMind GeoLite2-City.mmdb (configurable via environment)
832        let db_path =
833            std::env::var("MAXMIND_DB_PATH").unwrap_or_else(|_| "GeoLite2-City.mmdb".to_string());
834
835        if !Path::new(&db_path).exists() {
836            log::warn!(
837                "MaxMind database not found at {}, falling back to basic geolocation",
838                db_path
839            );
840            return None;
841        }
842
843        match maxminddb::Reader::open_readfile(&db_path) {
844            Ok(reader) => {
845                match reader.lookup::<maxminddb::geoip2::City>((*ip).into()) {
846                    Ok(Some(city)) => {
847                        let mut location_parts = Vec::new();
848
849                        // Build location string from MaxMind data
850                        if let Some(country) = city.country.and_then(|c| c.names)
851                            && let Some(name) = country.get("en")
852                        {
853                            location_parts.push(format!("country:{}", name));
854                        }
855
856                        if let Some(subdivisions) = city.subdivisions
857                            && let Some(subdivision) = subdivisions.first()
858                            && let Some(names) = &subdivision.names
859                            && let Some(name) = names.get("en")
860                        {
861                            location_parts.push(format!("region:{}", name));
862                        }
863
864                        if let Some(city_data) = city.city.and_then(|c| c.names)
865                            && let Some(name) = city_data.get("en")
866                        {
867                            location_parts.push(format!("city:{}", name));
868                        }
869
870                        if let Some(location) = city.location
871                            && let (Some(lat), Some(lon)) = (location.latitude, location.longitude)
872                        {
873                            location_parts.push(format!("coords:{:.4},{:.4}", lat, lon));
874                        }
875
876                        // Add threat intelligence from MaxMind
877                        if let Some(traits) = city.traits {
878                            let mut risk_indicators = Vec::new();
879
880                            if traits.is_anonymous_proxy == Some(true) {
881                                risk_indicators.push("proxy");
882                            }
883                            if traits.is_satellite_provider == Some(true) {
884                                risk_indicators.push("satellite");
885                            }
886                            if traits.is_anycast == Some(true) {
887                                risk_indicators.push("anycast");
888                            }
889
890                            if !risk_indicators.is_empty() {
891                                location_parts
892                                    .push(format!("threats:{}", risk_indicators.join(",")));
893                            }
894                        }
895
896                        Some(location_parts.join("|"))
897                    }
898                    Ok(None) => {
899                        log::debug!("MaxMind lookup returned no data for {}", ip);
900                        None
901                    }
902                    Err(e) => {
903                        log::debug!("MaxMind lookup failed for {}: {}", ip, e);
904                        None
905                    }
906                }
907            }
908            Err(e) => {
909                log::warn!("Failed to open MaxMind database: {}", e);
910                None
911            }
912        }
913    }
914}
915
916/// Risk calculator for sessions
917pub struct RiskCalculator;
918
919impl Default for RiskCalculator {
920    fn default() -> Self {
921        Self::new()
922    }
923}
924
925impl RiskCalculator {
926    pub fn new() -> Self {
927        Self
928    }
929
930    /// Calculate risk score (0-100) for a session
931    pub fn calculate_risk(
932        &self,
933        device_info: &DeviceInfo,
934        metadata: &RequestMetadata,
935        _session_history: &[Session],
936        threat_intel_manager: Option<&ThreatFeedManager>,
937    ) -> u8 {
938        let mut risk_score = 0u8;
939
940        // Check for new device
941        if !device_info.is_trusted {
942            risk_score += 20;
943        }
944
945        // Check IP reputation (simplified)
946        if let Some(ip) = &metadata.ip_address
947            && self.is_suspicious_ip(ip, threat_intel_manager)
948        {
949            risk_score += 30;
950        }
951
952        // Check geolocation anomalies
953        if let Some(location) = &metadata.geolocation {
954            let mut geo_risk = 0;
955
956            // Check country-based risk
957            if let Some(country) = &location.country {
958                let country_lower = country.to_lowercase();
959
960                // High-risk indicators in country names
961                let high_risk_countries =
962                    ["tor", "anonymous", "vpn", "proxy", "hosting", "datacenter"];
963
964                for indicator in &high_risk_countries {
965                    if country_lower.contains(indicator) {
966                        geo_risk += 30;
967                        break;
968                    }
969                }
970
971                // Real threat intelligence integration for geographic risk
972                geo_risk += self.assess_country_threat_level(&country_lower) as u8;
973
974                // Check against known high-risk hosting providers
975                let elevated_risk_patterns = ["cloud", "aws", "azure", "gcp"];
976                for pattern in &elevated_risk_patterns {
977                    if country_lower.contains(pattern) {
978                        geo_risk += 20;
979                        break;
980                    }
981                }
982            }
983
984            // Check for impossible travel (basic latitude/longitude analysis)
985            if let (Some(lat), Some(lon)) = (location.latitude, location.longitude) {
986                // Validate coordinate ranges
987                if !(-90.0..=90.0).contains(&lat) || !(-180.0..=180.0).contains(&lon) {
988                    geo_risk += 25; // Invalid coordinates are suspicious
989                }
990
991                // Detect datacenter coordinates (often round numbers)
992                if (lat * 100.0).fract().abs() < 0.01 && (lon * 100.0).fract().abs() < 0.01 {
993                    geo_risk += 15; // Suspiciously precise coordinates
994                }
995            }
996
997            // Region-based analysis
998            if let Some(region) = &location.region {
999                let region_lower = region.to_lowercase();
1000                if region_lower.contains("hosting") || region_lower.contains("datacenter") {
1001                    geo_risk += 20;
1002                }
1003            }
1004
1005            // City-based analysis
1006            if let Some(city) = &location.city {
1007                let city_lower = city.to_lowercase();
1008                if city_lower.contains("server") || city_lower.contains("datacenter") {
1009                    geo_risk += 15;
1010                }
1011            }
1012
1013            risk_score += geo_risk;
1014        }
1015
1016        // Check time-based anomalies
1017        // - Unusual login times
1018        // - Rapid geographic movement
1019        // - Multiple simultaneous sessions
1020
1021        risk_score.min(100)
1022    }
1023
1024    /// Check if IP address is suspicious
1025    fn is_suspicious_ip(&self, ip: &str, threat_intel_manager: Option<&ThreatFeedManager>) -> bool {
1026        use std::net::IpAddr;
1027        use std::str::FromStr;
1028
1029        // Parse IP address for analysis
1030        if let Ok(ip_addr) = IpAddr::from_str(ip) {
1031            match ip_addr {
1032                IpAddr::V4(ipv4) => {
1033                    let octets = ipv4.octets();
1034
1035                    // Real threat intelligence feeds integration
1036                    if self.check_malicious_ip_feeds(&ipv4, threat_intel_manager) {
1037                        return true;
1038                    }
1039
1040                    // Suspicious hosting ranges (example patterns)
1041                    let suspicious_ranges = [
1042                        // Known VPN/hosting provider ranges (examples)
1043                        (5, 0, 0, 0, 8),   // Various hosting
1044                        (31, 0, 0, 0, 8),  // Various hosting
1045                        (37, 0, 0, 0, 8),  // Various hosting
1046                        (46, 0, 0, 0, 8),  // Various hosting
1047                        (95, 0, 0, 0, 8),  // Various hosting
1048                        (185, 0, 0, 0, 8), // Various hosting
1049                    ];
1050
1051                    for (net, _, _, _, _) in &suspicious_ranges {
1052                        if octets[0] == *net {
1053                            return true;
1054                        }
1055                    }
1056
1057                    // Check for reserved/special ranges that shouldn't be used
1058                    if octets[0] == 0 ||                              // "This" network
1059                       (octets[0] == 100 && octets[1] >= 64 && octets[1] <= 127) || // Carrier-grade NAT
1060                       (octets[0] == 169 && octets[1] == 254) ||      // Link-local
1061                       (octets[0] >= 224 && octets[0] <= 239) ||      // Multicast
1062                       (octets[0] >= 240)
1063                    {
1064                        // Reserved/experimental
1065                        return true;
1066                    }
1067
1068                    // Detect potential port scans (suspicious patterns in last octet)
1069                    if octets[3] == 0 || octets[3] == 255 {
1070                        return true;
1071                    }
1072
1073                    // Real specialized databases for proxy/VPN detection
1074                    if self.check_proxy_vpn_databases(&ipv4) {
1075                        return true;
1076                    }
1077
1078                    // Fallback proxy port pattern detection
1079                    let proxy_ports_in_ip = [
1080                        80, 443, 8080, 3128, 1080, 8000, 8888, 9050, // Common proxy ports
1081                    ];
1082
1083                    for &port in &proxy_ports_in_ip {
1084                        if octets[2] == (port / 256) as u8 && octets[3] == (port % 256) as u8 {
1085                            return true;
1086                        }
1087                    }
1088
1089                    false
1090                }
1091                IpAddr::V6(ipv6) => {
1092                    let segments = ipv6.segments();
1093
1094                    // Real-time Tor exit node detection
1095                    if self.check_tor_exit_nodes(&ipv6) {
1096                        return true;
1097                    }
1098
1099                    // Fallback static range checks
1100                    if segments[0] == 0x2001 && segments[1] == 0x67c {
1101                        // Example known Tor range (static fallback)
1102                        return true;
1103                    }
1104
1105                    // Suspicious IPv6 patterns (overly sequential or predictable)
1106                    let mut sequential_count = 0;
1107                    for i in 1..segments.len() {
1108                        if segments[i] == segments[i - 1] + 1 {
1109                            sequential_count += 1;
1110                        }
1111                    }
1112                    if sequential_count >= 4 {
1113                        // Too many sequential segments
1114                        return true;
1115                    }
1116
1117                    // Check for tunnel brokers (often used for anonymity)
1118                    if segments[0] == 0x2001 && segments[1] == 0x470 {
1119                        // Hurricane Electric
1120                        return true;
1121                    }
1122
1123                    false
1124                }
1125            }
1126        } else {
1127            true // Invalid IP format is suspicious
1128        }
1129    }
1130
1131    /// Assess threat level for a country using threat intelligence feeds
1132    fn assess_country_threat_level(&self, country: &str) -> u32 {
1133        use std::path::Path;
1134
1135        // Load country threat intelligence (configurable path)
1136        let threat_db_path = std::env::var("COUNTRY_THREAT_DB_PATH")
1137            .unwrap_or_else(|_| "country-threats.csv".to_string());
1138
1139        if Path::new(&threat_db_path).exists() {
1140            // Real implementation: Load from CSV threat feed
1141            if let Ok(contents) = std::fs::read_to_string(&threat_db_path) {
1142                let mut csv_reader = csv::Reader::from_reader(contents.as_bytes());
1143
1144                for result in csv_reader.records() {
1145                    if let Ok(record) = result
1146                        && record.len() >= 2
1147                    {
1148                        let threat_country = record[0].to_lowercase();
1149                        if let Ok(risk_score) = record[1].parse::<u32>()
1150                            && country.contains(&threat_country)
1151                        {
1152                            log::debug!("Country threat match: {} -> risk {}", country, risk_score);
1153                            return risk_score;
1154                        }
1155                    }
1156                }
1157            }
1158        }
1159
1160        // Fallback: Basic static threat assessment
1161        let high_risk_indicators = [
1162            ("botnet", 40),
1163            ("malware", 35),
1164            ("ransomware", 45),
1165            ("cybercrime", 30),
1166            ("hacking", 25),
1167            ("fraud", 20),
1168        ];
1169
1170        for (indicator, risk) in &high_risk_indicators {
1171            if country.contains(indicator) {
1172                return *risk;
1173            }
1174        }
1175
1176        // Countries with elevated hosting/VPN activity
1177        let hosting_risk_patterns = [
1178            ("hosting", 15),
1179            ("datacenter", 15),
1180            ("cloud", 10),
1181            ("server", 12),
1182            ("vps", 18),
1183            ("dedicated", 10),
1184        ];
1185
1186        for (pattern, risk) in &hosting_risk_patterns {
1187            if country.contains(pattern) {
1188                return *risk;
1189            }
1190        }
1191
1192        0 // No additional risk
1193    }
1194
1195    /// Check IP against malicious IP threat intelligence feeds
1196    fn check_malicious_ip_feeds(
1197        &self,
1198        ip: &std::net::Ipv4Addr,
1199        threat_intel_manager: Option<&ThreatFeedManager>,
1200    ) -> bool {
1201        // Try automated threat intelligence first
1202        if let Some(threat_manager) = threat_intel_manager {
1203            return threat_manager.is_malicious_ip(&std::net::IpAddr::V4(*ip));
1204        }
1205
1206        // Fall back to manual file checking for backward compatibility
1207        use std::path::Path;
1208
1209        // Load malicious IP feeds (multiple sources)
1210        let feed_paths = [
1211            std::env::var("MALICIOUS_IPS_DB_PATH")
1212                .unwrap_or_else(|_| "malicious-ips.txt".to_string()),
1213            std::env::var("BOTNET_IPS_DB_PATH").unwrap_or_else(|_| "botnet-ips.txt".to_string()),
1214            std::env::var("TOR_EXIT_NODES_DB_PATH").unwrap_or_else(|_| "tor-exits.txt".to_string()),
1215        ];
1216
1217        for feed_path in &feed_paths {
1218            if Path::new(feed_path).exists()
1219                && let Ok(contents) = std::fs::read_to_string(feed_path)
1220            {
1221                for line in contents.lines() {
1222                    let line = line.trim();
1223                    if line.is_empty() || line.starts_with('#') {
1224                        continue;
1225                    }
1226
1227                    // Check exact IP match
1228                    if line == ip.to_string() {
1229                        log::warn!("Malicious IP detected: {} (source: {})", ip, feed_path);
1230                        return true;
1231                    }
1232
1233                    // Check CIDR network match
1234                    if line.contains('/')
1235                        && let Ok(network) = line.parse::<ipnetwork::Ipv4Network>()
1236                        && network.contains(*ip)
1237                    {
1238                        log::warn!(
1239                            "Malicious network detected: {} in {} (source: {})",
1240                            ip,
1241                            network,
1242                            feed_path
1243                        );
1244                        return true;
1245                    }
1246                }
1247            }
1248        }
1249
1250        false
1251    }
1252
1253    /// Check against specialized proxy/VPN databases
1254    fn check_proxy_vpn_databases(&self, ip: &std::net::Ipv4Addr) -> bool {
1255        use std::path::Path;
1256
1257        // Multiple specialized databases for proxy/VPN detection
1258        let db_sources = [
1259            ("VPN_DATABASE_PATH", "vpn-ranges.txt"),
1260            ("PROXY_DATABASE_PATH", "proxy-ips.txt"),
1261            ("DATACENTER_DATABASE_PATH", "datacenter-ranges.txt"),
1262            ("HOSTING_DATABASE_PATH", "hosting-providers.txt"),
1263        ];
1264
1265        for (env_var, default_file) in &db_sources {
1266            let db_path = std::env::var(env_var).unwrap_or_else(|_| default_file.to_string());
1267
1268            if Path::new(&db_path).exists()
1269                && let Ok(contents) = std::fs::read_to_string(&db_path)
1270            {
1271                for line in contents.lines() {
1272                    let line = line.trim();
1273                    if line.is_empty() || line.starts_with('#') {
1274                        continue;
1275                    }
1276
1277                    // Support multiple formats: IP, CIDR, IP ranges
1278                    if line.contains('/') {
1279                        // CIDR notation
1280                        if let Ok(network) = line.parse::<ipnetwork::Ipv4Network>()
1281                            && network.contains(*ip)
1282                        {
1283                            log::info!(
1284                                "Proxy/VPN detected: {} in {} (source: {})",
1285                                ip,
1286                                network,
1287                                db_path
1288                            );
1289                            return true;
1290                        }
1291                    } else if line.contains('-') {
1292                        // IP range format: 1.2.3.4-1.2.3.10
1293                        let parts: Vec<&str> = line.split('-').collect();
1294                        if parts.len() == 2
1295                            && let (Ok(start_ip), Ok(end_ip)) = (
1296                                parts[0].trim().parse::<std::net::Ipv4Addr>(),
1297                                parts[1].trim().parse::<std::net::Ipv4Addr>(),
1298                            )
1299                        {
1300                            let ip_u32 = u32::from(*ip);
1301                            let start_u32 = u32::from(start_ip);
1302                            let end_u32 = u32::from(end_ip);
1303
1304                            if ip_u32 >= start_u32 && ip_u32 <= end_u32 {
1305                                log::info!(
1306                                    "Proxy/VPN range detected: {} in {}-{} (source: {})",
1307                                    ip,
1308                                    start_ip,
1309                                    end_ip,
1310                                    db_path
1311                                );
1312                                return true;
1313                            }
1314                        }
1315                    } else if line == ip.to_string() {
1316                        // Exact IP match
1317                        log::info!("Proxy/VPN exact match: {} (source: {})", ip, db_path);
1318                        return true;
1319                    }
1320                }
1321            }
1322        }
1323
1324        false
1325    }
1326
1327    /// Check against real-time Tor exit node lists
1328    fn check_tor_exit_nodes(&self, ip: &std::net::Ipv6Addr) -> bool {
1329        use std::path::Path;
1330
1331        // Real-time Tor exit node detection
1332        let tor_db_path = std::env::var("TOR_EXIT_NODES_IPV6_PATH")
1333            .unwrap_or_else(|_| "tor-exits-ipv6.txt".to_string());
1334
1335        if Path::new(&tor_db_path).exists()
1336            && let Ok(contents) = std::fs::read_to_string(&tor_db_path)
1337        {
1338            for line in contents.lines() {
1339                let line = line.trim();
1340                if line.is_empty() || line.starts_with('#') {
1341                    continue;
1342                }
1343
1344                // Check IPv6 exact match
1345                if let Ok(tor_ip) = line.parse::<std::net::Ipv6Addr>()
1346                    && tor_ip == *ip
1347                {
1348                    log::warn!("Tor exit node detected: {}", ip);
1349                    return true;
1350                }
1351
1352                // Check IPv6 network match
1353                if line.contains('/')
1354                    && let Ok(network) = line.parse::<ipnetwork::Ipv6Network>()
1355                    && network.contains(*ip)
1356                {
1357                    log::warn!("Tor exit network detected: {} in {}", ip, network);
1358                    return true;
1359                }
1360            }
1361        }
1362
1363        // Also check IPv4-mapped IPv6 addresses for Tor
1364        if let Some(ipv4) = ip.to_ipv4() {
1365            let tor_v4_path = std::env::var("TOR_EXIT_NODES_IPV4_PATH")
1366                .unwrap_or_else(|_| "tor-exits-ipv4.txt".to_string());
1367
1368            if Path::new(&tor_v4_path).exists()
1369                && let Ok(contents) = std::fs::read_to_string(&tor_v4_path)
1370            {
1371                for line in contents.lines() {
1372                    let line = line.trim();
1373                    if line == ipv4.to_string() {
1374                        log::warn!("Tor exit node detected (IPv4-mapped): {}", ip);
1375                        return true;
1376                    }
1377                }
1378            }
1379        }
1380
1381        false
1382    }
1383}
1384
1385impl Default for SessionConfig {
1386    fn default() -> Self {
1387        Self {
1388            default_duration: Duration::from_secs(3600), // 1 hour
1389            max_duration: Duration::from_secs(86400),    // 24 hours
1390            idle_timeout: Duration::from_secs(1800),     // 30 minutes
1391            rotate_on_privilege_escalation: true,
1392            rotate_periodically: true,
1393            rotation_interval: Duration::from_secs(1800), // 30 minutes
1394            max_concurrent_sessions: Some(5),
1395            track_device_fingerprints: true,
1396            enforce_geographic_restrictions: false,
1397            allowed_countries: vec![],
1398            security_policy: SessionSecurityPolicy::default(),
1399        }
1400    }
1401}
1402
1403impl Default for SessionSecurityPolicy {
1404    fn default() -> Self {
1405        Self {
1406            require_mfa_for_new_devices: true,
1407            require_reauth_for_sensitive_ops: true,
1408            reauth_timeout: Duration::from_secs(300), // 5 minutes
1409            max_risk_score: 70,
1410            auto_suspend_suspicious: true,
1411            verify_location_changes: true,
1412            limit_concurrent_sessions: true,
1413        }
1414    }
1415}
1416
1417#[cfg(test)]
1418mod tests {
1419    use super::*;
1420
1421    #[test]
1422    fn test_device_fingerprint_generation() {
1423        let generator = DeviceFingerprintGenerator::new();
1424        let metadata = RequestMetadata {
1425            user_agent: Some("Mozilla/5.0".to_string()),
1426            ..Default::default()
1427        };
1428
1429        let fp1 = generator.generate_fingerprint(&metadata);
1430        let fp2 = generator.generate_fingerprint(&metadata);
1431
1432        assert_eq!(fp1, fp2);
1433        assert!(fp1.starts_with("fp_"));
1434    }
1435
1436    #[test]
1437    fn test_risk_calculation() {
1438        let calculator = RiskCalculator::new();
1439        let device_info = DeviceInfo {
1440            fingerprint: "test".to_string(),
1441            device_type: "desktop".to_string(),
1442            operating_system: None,
1443            browser: None,
1444            screen_resolution: None,
1445            timezone: None,
1446            language: None,
1447            is_trusted: false,
1448            device_name: None,
1449        };
1450
1451        let metadata = RequestMetadata::default();
1452        let history = vec![];
1453
1454        let risk = calculator.calculate_risk(&device_info, &metadata, &history, None);
1455        assert!(risk >= 20); // Should have at least 20 for untrusted device
1456    }
1457}