Skip to main content

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