1use 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#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct Session {
20 pub id: String,
22 pub user_id: String,
24 pub created_at: SystemTime,
26 pub last_accessed: SystemTime,
28 pub expires_at: SystemTime,
30 pub state: SessionState,
32 pub device_info: DeviceInfo,
34 pub security_metadata: SecurityMetadata,
36 pub data: HashMap<String, String>,
38 pub mfa_verified: bool,
40 pub cached_permissions: Option<Vec<String>>,
42 pub last_activity: ActivityInfo,
44}
45
46impl Session {
47 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#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
88pub enum SessionState {
89 Active,
90 Expired,
91 Revoked,
92 Suspended,
93 RequiresMfa,
94 RequiresReauth,
95 RequiresRotation,
97 HighRisk,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct DeviceInfo {
104 pub fingerprint: String,
106 pub device_type: String,
108 pub operating_system: Option<String>,
110 pub browser: Option<String>,
112 pub screen_resolution: Option<String>,
114 pub timezone: Option<String>,
116 pub language: Option<String>,
118 pub is_trusted: bool,
120 pub device_name: Option<String>,
122 pub is_mobile: bool,
124 pub ip_address: Option<String>,
126}
127
128impl DeviceInfo {
129 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#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct SecurityMetadata {
150 pub creation_ip: String,
152 pub current_ip: String,
154 pub creation_location: Option<GeolocationInfo>,
156 pub current_location: Option<GeolocationInfo>,
158 pub security_flags: Vec<SecurityFlag>,
160 pub risk_score: u8,
162 pub location_changed: bool,
164 pub ip_changed: bool,
166 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#[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#[derive(Debug, Clone, Serialize, Deserialize)]
201pub struct ActivityInfo {
202 pub endpoint: Option<String>,
204 pub action: Option<String>,
206 pub request_metadata: Option<RequestMetadata>,
208 pub timestamp: SystemTime,
210}
211
212#[derive(Debug, Clone)]
214pub struct SessionConfig {
215 pub default_duration: Duration,
217 pub max_duration: Duration,
219 pub idle_timeout: Duration,
221 pub rotate_on_privilege_escalation: bool,
223 pub rotate_periodically: bool,
225 pub rotation_interval: Duration,
227 pub max_concurrent_sessions: Option<u32>,
229 pub track_device_fingerprints: bool,
231 pub enforce_geographic_restrictions: bool,
233 pub allowed_countries: Vec<String>,
235 pub security_policy: SessionSecurityPolicy,
237}
238
239impl SessionConfig {
242 pub fn builder() -> SessionConfigBuilder {
244 SessionConfigBuilder::default()
245 }
246}
247
248pub 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 pub fn default_duration(mut self, duration: std::time::Duration) -> Self {
272 self.config.default_duration = duration;
273 self
274 }
275
276 pub fn max_duration(mut self, duration: std::time::Duration) -> Self {
285 self.config.max_duration = duration;
286 self
287 }
288
289 pub fn idle_timeout(mut self, timeout: std::time::Duration) -> Self {
298 self.config.idle_timeout = timeout;
299 self
300 }
301
302 pub fn rotate_on_privilege_escalation(mut self, rotate: bool) -> Self {
311 self.config.rotate_on_privilege_escalation = rotate;
312 self
313 }
314
315 pub fn rotate_periodically(mut self, rotate: bool) -> Self {
325 self.config.rotate_periodically = rotate;
326 self
327 }
328
329 pub fn rotation_interval(mut self, interval: std::time::Duration) -> Self {
339 self.config.rotation_interval = interval;
340 self
341 }
342
343 pub fn max_concurrent_sessions(mut self, max: u32) -> Self {
352 self.config.max_concurrent_sessions = Some(max);
353 self
354 }
355
356 pub fn allowed_countries(mut self, countries: Vec<String>) -> Self {
361 self.config.allowed_countries = countries;
362 self
363 }
364
365 pub fn for_web_app() -> Self {
374 Self {
375 config: SessionConfig {
376 default_duration: Duration::from_secs(3600), max_duration: Duration::from_secs(86400), idle_timeout: Duration::from_secs(1800), rotate_on_privilege_escalation: true,
380 rotate_periodically: true,
381 rotation_interval: Duration::from_secs(1800), 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 pub fn for_api_service() -> Self {
402 Self {
403 config: SessionConfig {
404 default_duration: Duration::from_secs(900), max_duration: Duration::from_secs(3600), idle_timeout: Duration::from_secs(600), rotate_on_privilege_escalation: true,
408 rotate_periodically: false,
409 rotation_interval: Duration::from_secs(3600),
410 max_concurrent_sessions: None, 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 pub fn for_high_security() -> Self {
437 Self {
438 config: SessionConfig {
439 default_duration: Duration::from_secs(1800), max_duration: Duration::from_secs(7200), idle_timeout: Duration::from_secs(300), rotate_on_privilege_escalation: true,
443 rotate_periodically: true,
444 rotation_interval: Duration::from_secs(900), max_concurrent_sessions: Some(1),
446 track_device_fingerprints: true,
447 enforce_geographic_restrictions: true,
448 allowed_countries: vec![], security_policy: SessionSecurityPolicy {
450 require_mfa_for_new_devices: true,
451 require_reauth_for_sensitive_ops: true,
452 reauth_timeout: Duration::from_secs(120), 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 pub fn build(self) -> SessionConfig {
472 self.config
473 }
474}
475
476#[derive(Debug, Clone)]
477pub struct SessionSecurityPolicy {
478 pub require_mfa_for_new_devices: bool,
480 pub require_reauth_for_sensitive_ops: bool,
482 pub reauth_timeout: Duration,
484 pub max_risk_score: u8,
486 pub auto_suspend_suspicious: bool,
488 pub verify_location_changes: bool,
490 pub limit_concurrent_sessions: bool,
492}
493
494#[async_trait]
496pub trait SessionStorage: Send + Sync {
497 async fn create_session(&self, session: &Session) -> Result<()>;
499
500 async fn get_session(&self, session_id: &str) -> Result<Option<Session>>;
502
503 async fn update_session(&self, session: &Session) -> Result<()>;
505
506 async fn delete_session(&self, session_id: &str) -> Result<()>;
508
509 async fn get_user_sessions(&self, user_id: &str) -> Result<Vec<Session>>;
511
512 async fn count_active_sessions(&self, user_id: &str) -> Result<u32>;
514
515 async fn cleanup_expired_sessions(&self) -> Result<u32>;
517
518 async fn find_sessions_by_device(&self, device_fingerprint: &str) -> Result<Vec<Session>>;
520
521 async fn find_sessions_by_ip(&self, ip_address: &str) -> Result<Vec<Session>>;
523}
524
525pub 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 pub fn new(storage: S, config: SessionConfig, audit_logger: AuditLogger<A>) -> Self {
541 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 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 pub async fn create_session(
601 &self,
602 user_id: &str,
603 mut device_info: DeviceInfo,
604 metadata: RequestMetadata,
605 ) -> Result<Session> {
606 if device_info.fingerprint.is_empty() {
608 device_info.fingerprint = self.fingerprint_generator.generate_fingerprint(&metadata);
609 }
610
611 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 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 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 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 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 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 if session.expires_at <= now {
732 session.state = SessionState::Expired;
733 self.storage.update_session(&session).await?;
734 return Ok(None);
735 }
736
737 let current_fingerprint = self.fingerprint_generator.generate_fingerprint(&metadata);
739 if current_fingerprint != session.device_info.fingerprint {
740 session.state = SessionState::RequiresMfa;
742 self.storage.update_session(&session).await?;
743
744 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 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 match session.state {
770 SessionState::Expired | SessionState::Revoked | SessionState::Suspended => {
771 return Ok(None);
772 }
773 SessionState::RequiresMfa | SessionState::RequiresReauth => {
774 return Ok(Some(session));
776 }
777 SessionState::Active | SessionState::RequiresRotation | SessionState::HighRisk => {}
778 }
779
780 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 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 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 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 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 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 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 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 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 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 pub async fn cleanup_expired_sessions(&self) -> Result<u32> {
904 self.storage.cleanup_expired_sessions().await
905 }
906
907 pub fn generate_device_fingerprint(&self, metadata: &RequestMetadata) -> String {
909 self.fingerprint_generator.generate_fingerprint(metadata)
910 }
911
912 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 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 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 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 fn generate_session_id(&self) -> String {
973 format!("sess_{}", uuid::Uuid::new_v4())
974 }
975
976 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 async fn get_user_session_history(&self, user_id: &str) -> Result<Vec<Session>> {
991 self.storage.get_user_sessions(user_id).await
994 }
995}
996
997pub 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 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 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 fn add_advanced_fingerprinting_data(
1031 &self,
1032 fingerprint_data: &mut Vec<String>,
1033 metadata: &RequestMetadata,
1034 ) {
1035 if let Some(ref ip) = metadata.ip_address {
1037 fingerprint_data.push(format!("geo:{}", self.get_ip_geolocation(ip)));
1039 }
1040
1041 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 fingerprint_data.push(format!("conn:{}", self.get_connection_info()));
1048
1049 fingerprint_data.push(format!("caps:{}", self.get_client_capabilities()));
1051 }
1052
1053 fn get_ip_geolocation(&self, ip: &str) -> String {
1055 use std::net::IpAddr;
1056 use std::str::FromStr;
1057
1058 if let Ok(ip_addr) = IpAddr::from_str(ip) {
1060 match ip_addr {
1061 IpAddr::V4(ipv4) => {
1062 let octets = ipv4.octets();
1063
1064 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 if octets[0] == 127 {
1074 return "loopback".to_string();
1075 }
1076
1077 if octets[0] == 169 && octets[1] == 254 {
1079 return "link_local".to_string();
1080 }
1081
1082 if octets[0] >= 224 && octets[0] <= 239 {
1084 return "multicast".to_string();
1085 }
1086
1087 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 self.lookup_maxmind_geolocation(&ipv4).unwrap_or_else(|| {
1095 match octets[0] {
1097 1..=23 => "apnic_region".to_string(), 24..=49 => "arin_region".to_string(), 50..=99 => "ripe_region".to_string(), 100..=127 => "mixed_region".to_string(), 128..=191 => "arin_region".to_string(), 192..=223 => "ripe_apnic_region".to_string(), _ => format!("public_class_{}", octets[0] / 64),
1104 }
1105 })
1106 }
1107 }
1108 }
1109 IpAddr::V6(ipv6) => {
1110 let segments = ipv6.segments();
1111
1112 if ipv6.is_loopback() {
1114 return "ipv6_loopback".to_string();
1115 }
1116
1117 if segments[0] & 0xffc0 == 0xfe80 {
1119 return "ipv6_link_local".to_string();
1120 }
1121
1122 if segments[0] & 0xfe00 == 0xfc00 {
1124 return "ipv6_unique_local".to_string();
1125 }
1126
1127 if segments[0] & 0xff00 == 0xff00 {
1129 return "ipv6_multicast".to_string();
1130 }
1131
1132 format!("ipv6_global_{:x}", segments[0] / 0x1000)
1134 }
1135 }
1136 } else {
1137 "invalid_ip".to_string()
1138 }
1139 }
1140
1141 fn get_system_language(&self) -> String {
1143 "en-US".to_string()
1145 }
1146
1147 fn get_timezone_offset(&self) -> String {
1149 "-05:00".to_string() }
1152
1153 fn get_hardware_concurrency(&self) -> String {
1155 "4".to_string()
1157 }
1158
1159 fn get_connection_info(&self) -> String {
1161 "wifi".to_string()
1163 }
1164
1165 fn get_client_capabilities(&self) -> String {
1167 "webgl2_canvas_audio".to_string()
1169 }
1170
1171 fn lookup_maxmind_geolocation(&self, ip: &std::net::Ipv4Addr) -> Option<String> {
1173 use std::path::Path;
1174
1175 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 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 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
1232pub 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 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 if !device_info.is_trusted {
1262 risk_score += 20;
1263 }
1264
1265 if let Some(ip) = &metadata.ip_address
1267 && self.is_suspicious_ip(ip, threat_intel_manager)
1268 {
1269 risk_score += 30;
1270 }
1271
1272 if let Some(location) = &metadata.geolocation {
1274 let mut geo_risk = 0;
1275
1276 if let Some(country) = &location.country {
1278 let country_lower = country.to_lowercase();
1279
1280 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 geo_risk += self.assess_country_threat_level(&country_lower) as u8;
1293
1294 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 if let (Some(lat), Some(lon)) = (location.latitude, location.longitude) {
1306 if !(-90.0..=90.0).contains(&lat) || !(-180.0..=180.0).contains(&lon) {
1308 geo_risk += 25; }
1310
1311 if (lat * 100.0).fract().abs() < 0.01 && (lon * 100.0).fract().abs() < 0.01 {
1313 geo_risk += 15; }
1315 }
1316
1317 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 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 risk_score.min(100)
1342 }
1343
1344 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 if let Ok(ip_addr) = IpAddr::from_str(ip) {
1351 match ip_addr {
1352 IpAddr::V4(ipv4) => {
1353 let octets = ipv4.octets();
1354
1355 if self.check_malicious_ip_feeds(&ipv4, threat_intel_manager) {
1357 return true;
1358 }
1359
1360 let suspicious_ranges = [
1362 (5, 0, 0, 0, 8), (31, 0, 0, 0, 8), (37, 0, 0, 0, 8), (46, 0, 0, 0, 8), (95, 0, 0, 0, 8), (185, 0, 0, 0, 8), ];
1370
1371 for (net, _, _, _, _) in &suspicious_ranges {
1372 if octets[0] == *net {
1373 return true;
1374 }
1375 }
1376
1377 if octets[0] == 0 || (octets[0] == 100 && octets[1] >= 64 && octets[1] <= 127) || (octets[0] == 169 && octets[1] == 254) || (octets[0] >= 224 && octets[0] <= 239) || (octets[0] >= 240)
1383 {
1384 return true;
1386 }
1387
1388 if octets[3] == 0 || octets[3] == 255 {
1390 return true;
1391 }
1392
1393 if self.check_proxy_vpn_databases(&ipv4) {
1395 return true;
1396 }
1397
1398 let proxy_ports_in_ip = [
1400 80, 443, 8080, 3128, 1080, 8000, 8888, 9050, ];
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 if self.check_tor_exit_nodes(&ipv6) {
1416 return true;
1417 }
1418
1419 if segments[0] == 0x2001 && segments[1] == 0x67c {
1421 return true;
1423 }
1424
1425 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 return true;
1435 }
1436
1437 if segments[0] == 0x2001 && segments[1] == 0x470 {
1439 return true;
1441 }
1442
1443 false
1444 }
1445 }
1446 } else {
1447 true }
1449 }
1450
1451 fn assess_country_threat_level(&self, country: &str) -> u32 {
1453 use std::path::Path;
1454
1455 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 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 0 }
1487
1488 fn check_malicious_ip_feeds(
1490 &self,
1491 ip: &std::net::Ipv4Addr,
1492 threat_intel_manager: Option<&ThreatFeedManager>,
1493 ) -> bool {
1494 if let Some(threat_manager) = threat_intel_manager {
1496 return threat_manager.is_malicious_ip(&std::net::IpAddr::V4(*ip));
1497 }
1498
1499 use std::path::Path;
1501
1502 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 if line == ip.to_string() {
1522 tracing::warn!("Malicious IP detected: {} (source: {})", ip, feed_path);
1523 return true;
1524 }
1525
1526 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 fn check_proxy_vpn_databases(&self, ip: &std::net::Ipv4Addr) -> bool {
1548 use std::path::Path;
1549
1550 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 if line.contains('/') {
1572 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 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 tracing::info!("Proxy/VPN exact match: {} (source: {})", ip, db_path);
1611 return true;
1612 }
1613 }
1614 }
1615 }
1616
1617 false
1618 }
1619
1620 fn check_tor_exit_nodes(&self, ip: &std::net::Ipv6Addr) -> bool {
1622 use std::path::Path;
1623
1624 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 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 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 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), max_duration: Duration::from_secs(86400), idle_timeout: Duration::from_secs(1800), rotate_on_privilege_escalation: true,
1685 rotate_periodically: true,
1686 rotation_interval: Duration::from_secs(1800), 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), 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); }
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}