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
46#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
48pub enum SessionState {
49 Active,
50 Expired,
51 Revoked,
52 Suspended,
53 RequiresMfa,
54 RequiresReauth,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct DeviceInfo {
60 pub fingerprint: String,
62 pub device_type: String,
64 pub operating_system: Option<String>,
66 pub browser: Option<String>,
68 pub screen_resolution: Option<String>,
70 pub timezone: Option<String>,
72 pub language: Option<String>,
74 pub is_trusted: bool,
76 pub device_name: Option<String>,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct SecurityMetadata {
83 pub creation_ip: String,
85 pub current_ip: String,
87 pub creation_location: Option<GeolocationInfo>,
89 pub current_location: Option<GeolocationInfo>,
91 pub security_flags: Vec<SecurityFlag>,
93 pub risk_score: u8,
95 pub location_changed: bool,
97 pub ip_changed: bool,
99 pub failed_auth_attempts: u32,
101}
102
103#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
105pub enum SecurityFlag {
106 SuspiciousActivity,
107 LocationAnomaly,
108 DeviceAnomaly,
109 TimeAnomaly,
110 ConcurrentSessionLimit,
111 BruteForceAttempt,
112 RequiresVerification,
113 ElevatedPrivileges,
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct ActivityInfo {
119 pub endpoint: Option<String>,
121 pub action: Option<String>,
123 pub request_metadata: Option<RequestMetadata>,
125 pub timestamp: SystemTime,
127}
128
129#[derive(Debug, Clone)]
131pub struct SessionConfig {
132 pub default_duration: Duration,
134 pub max_duration: Duration,
136 pub idle_timeout: Duration,
138 pub rotate_on_privilege_escalation: bool,
140 pub rotate_periodically: bool,
142 pub rotation_interval: Duration,
144 pub max_concurrent_sessions: Option<u32>,
146 pub track_device_fingerprints: bool,
148 pub enforce_geographic_restrictions: bool,
150 pub allowed_countries: Vec<String>,
152 pub security_policy: SessionSecurityPolicy,
154}
155
156#[derive(Debug, Clone)]
158pub struct SessionSecurityPolicy {
159 pub require_mfa_for_new_devices: bool,
161 pub require_reauth_for_sensitive_ops: bool,
163 pub reauth_timeout: Duration,
165 pub max_risk_score: u8,
167 pub auto_suspend_suspicious: bool,
169 pub verify_location_changes: bool,
171 pub limit_concurrent_sessions: bool,
173}
174
175#[async_trait]
177pub trait SessionStorage: Send + Sync {
178 async fn create_session(&self, session: &Session) -> Result<()>;
180
181 async fn get_session(&self, session_id: &str) -> Result<Option<Session>>;
183
184 async fn update_session(&self, session: &Session) -> Result<()>;
186
187 async fn delete_session(&self, session_id: &str) -> Result<()>;
189
190 async fn get_user_sessions(&self, user_id: &str) -> Result<Vec<Session>>;
192
193 async fn count_active_sessions(&self, user_id: &str) -> Result<u32>;
195
196 async fn cleanup_expired_sessions(&self) -> Result<u32>;
198
199 async fn find_sessions_by_device(&self, device_fingerprint: &str) -> Result<Vec<Session>>;
201
202 async fn find_sessions_by_ip(&self, ip_address: &str) -> Result<Vec<Session>>;
204}
205
206pub struct SessionManager<S: SessionStorage, A: AuditStorage> {
208 storage: S,
209 config: SessionConfig,
210 audit_logger: AuditLogger<A>,
211 fingerprint_generator: DeviceFingerprintGenerator,
212 risk_calculator: RiskCalculator,
213 threat_intel_manager: Option<ThreatFeedManager>,
214}
215
216impl<S: SessionStorage, A: AuditStorage> SessionManager<S, A> {
217 pub fn new(storage: S, config: SessionConfig, audit_logger: AuditLogger<A>) -> Self {
219 let threat_intel_manager = if std::env::var("THREAT_INTEL_ENABLED")
221 .unwrap_or_else(|_| "false".to_string())
222 .to_lowercase()
223 == "true"
224 {
225 match ThreatIntelConfig::from_env_and_config() {
226 Ok(intel_config) => {
227 log::info!(
228 "🟢 Automated threat intelligence enabled - feeds will update automatically"
229 );
230 match ThreatFeedManager::new(intel_config) {
231 Ok(manager) => {
232 if let Err(e) = manager.start_automated_updates() {
234 log::error!("Failed to start automated threat feed updates: {}", e);
235 None
236 } else {
237 log::info!(
238 "✅ Threat intelligence automation started successfully"
239 );
240 Some(manager)
241 }
242 }
243 Err(e) => {
244 log::error!("Failed to initialize threat intelligence manager: {}", e);
245 None
246 }
247 }
248 }
249 Err(e) => {
250 log::error!("Failed to load threat intelligence configuration: {}", e);
251 None
252 }
253 }
254 } else {
255 log::info!("🔴 Automated threat intelligence disabled (THREAT_INTEL_ENABLED=false)");
256 None
257 };
258
259 Self {
260 storage,
261 config,
262 audit_logger,
263 fingerprint_generator: DeviceFingerprintGenerator::new(),
264 risk_calculator: RiskCalculator::new(),
265 threat_intel_manager,
266 }
267 }
268
269 pub async fn create_session(
271 &self,
272 user_id: &str,
273 mut device_info: DeviceInfo,
274 metadata: RequestMetadata,
275 ) -> Result<Session> {
276 if device_info.fingerprint.is_empty() {
278 device_info.fingerprint = self.fingerprint_generator.generate_fingerprint(&metadata);
279 }
280
281 if let Some(max_sessions) = self.config.max_concurrent_sessions {
283 let active_count = self.storage.count_active_sessions(user_id).await?;
284 if active_count >= max_sessions {
285 return Err(AuthError::TooManyConcurrentSessions);
286 }
287 }
288
289 let now = SystemTime::now();
290 let session_id = self.generate_session_id();
291
292 let risk_score = self.risk_calculator.calculate_risk(
294 &device_info,
295 &metadata,
296 &self.get_user_session_history(user_id).await?,
297 self.threat_intel_manager.as_ref(),
298 );
299
300 let mut security_flags = Vec::new();
301 if risk_score > 70 {
302 security_flags.push(SecurityFlag::SuspiciousActivity);
303 }
304
305 let existing_sessions = self
307 .storage
308 .find_sessions_by_device(&device_info.fingerprint)
309 .await?;
310 let is_new_device = existing_sessions.is_empty();
311
312 if is_new_device && self.config.security_policy.require_mfa_for_new_devices {
313 security_flags.push(SecurityFlag::RequiresVerification);
314 }
315
316 let session = Session {
317 id: session_id.clone(),
318 user_id: user_id.to_string(),
319 created_at: now,
320 last_accessed: now,
321 expires_at: now + self.config.default_duration,
322 state: if security_flags.contains(&SecurityFlag::RequiresVerification) {
323 SessionState::RequiresMfa
324 } else {
325 SessionState::Active
326 },
327 device_info: device_info.clone(),
328 security_metadata: SecurityMetadata {
329 creation_ip: metadata.ip_address.clone().unwrap_or_default(),
330 current_ip: metadata.ip_address.clone().unwrap_or_default(),
331 creation_location: metadata.geolocation.clone(),
332 current_location: metadata.geolocation.clone(),
333 security_flags,
334 risk_score,
335 location_changed: false,
336 ip_changed: false,
337 failed_auth_attempts: 0,
338 },
339 data: HashMap::new(),
340 mfa_verified: false,
341 cached_permissions: None,
342 last_activity: ActivityInfo {
343 endpoint: metadata.endpoint.clone(),
344 action: Some("session_created".to_string()),
345 request_metadata: Some(metadata.clone()),
346 timestamp: now,
347 },
348 };
349
350 self.storage.create_session(&session).await?;
351
352 self.audit_logger
354 .log_event(crate::audit::AuditEvent {
355 id: String::new(),
356 event_type: crate::audit::AuditEventType::LoginSuccess,
357 timestamp: now,
358 user_id: Some(user_id.to_string()),
359 session_id: Some(session_id),
360 outcome: crate::audit::EventOutcome::Success,
361 risk_level: if risk_score > 70 {
362 crate::audit::RiskLevel::High
363 } else {
364 crate::audit::RiskLevel::Low
365 },
366 description: "Session created".to_string(),
367 details: HashMap::new(),
368 request_metadata: metadata,
369 resource: None,
370 actor: crate::audit::ActorInfo {
371 actor_type: "user".to_string(),
372 actor_id: user_id.to_string(),
373 actor_name: None,
374 roles: vec![],
375 },
376 correlation_id: None,
377 })
378 .await?;
379
380 Ok(session)
381 }
382
383 pub async fn validate_session(
385 &self,
386 session_id: &str,
387 metadata: RequestMetadata,
388 ) -> Result<Option<Session>> {
389 let mut session = match self.storage.get_session(session_id).await? {
390 Some(session) => session,
391 None => return Ok(None),
392 };
393
394 let now = SystemTime::now();
395
396 if session.expires_at <= now {
398 session.state = SessionState::Expired;
399 self.storage.update_session(&session).await?;
400 return Ok(None);
401 }
402
403 let current_fingerprint = self.fingerprint_generator.generate_fingerprint(&metadata);
405 if current_fingerprint != session.device_info.fingerprint {
406 session.state = SessionState::RequiresMfa;
408 self.storage.update_session(&session).await?;
409
410 self.audit_logger
412 .log_suspicious_activity(
413 Some(&session.user_id),
414 "device_fingerprint_mismatch",
415 &format!(
416 "Session ID: {}, Expected: {}, Got: {}",
417 session_id, session.device_info.fingerprint, current_fingerprint
418 ),
419 metadata.clone(),
420 )
421 .await?;
422 }
423
424 let idle_duration = now
426 .duration_since(session.last_accessed)
427 .unwrap_or_default();
428 if idle_duration > self.config.idle_timeout {
429 session.state = SessionState::Expired;
430 self.storage.update_session(&session).await?;
431 return Ok(None);
432 }
433
434 match session.state {
436 SessionState::Expired | SessionState::Revoked | SessionState::Suspended => {
437 return Ok(None);
438 }
439 SessionState::RequiresMfa | SessionState::RequiresReauth => {
440 return Ok(Some(session));
442 }
443 SessionState::Active => {}
444 }
445
446 let current_ip = metadata.ip_address.clone().unwrap_or_default();
448 let ip_changed = current_ip != session.security_metadata.current_ip;
449
450 if ip_changed {
451 session.security_metadata.ip_changed = true;
452 session.security_metadata.current_ip = current_ip;
453
454 if self.config.security_policy.verify_location_changes {
456 session
457 .security_metadata
458 .security_flags
459 .push(SecurityFlag::LocationAnomaly);
460 session.state = SessionState::RequiresReauth;
461 }
462 }
463
464 session.last_accessed = now;
466 session.last_activity = ActivityInfo {
467 endpoint: metadata.endpoint.clone(),
468 action: Some("session_validated".to_string()),
469 request_metadata: Some(metadata),
470 timestamp: now,
471 };
472
473 let should_rotate = self.should_rotate_session(&session);
475 if should_rotate {
476 let new_session_id = self.generate_session_id();
477 let old_session_id = session.id.clone();
478 session.id = new_session_id;
479
480 self.storage.delete_session(&old_session_id).await?;
482 self.storage.create_session(&session).await?;
483 } else {
484 self.storage.update_session(&session).await?;
485 }
486
487 Ok(Some(session))
488 }
489
490 pub async fn revoke_session(&self, session_id: &str) -> Result<()> {
492 if let Some(mut session) = self.storage.get_session(session_id).await? {
493 session.state = SessionState::Revoked;
494 self.storage.update_session(&session).await?;
495
496 self.audit_logger
498 .log_event(crate::audit::AuditEvent {
499 id: String::new(),
500 event_type: crate::audit::AuditEventType::Logout,
501 timestamp: SystemTime::now(),
502 user_id: Some(session.user_id),
503 session_id: Some(session_id.to_string()),
504 outcome: crate::audit::EventOutcome::Success,
505 risk_level: crate::audit::RiskLevel::Low,
506 description: "Session revoked".to_string(),
507 details: HashMap::new(),
508 request_metadata: crate::audit::RequestMetadata::default(),
509 resource: None,
510 actor: crate::audit::ActorInfo {
511 actor_type: "system".to_string(),
512 actor_id: "session_manager".to_string(),
513 actor_name: None,
514 roles: vec![],
515 },
516 correlation_id: None,
517 })
518 .await?;
519 }
520 Ok(())
521 }
522
523 pub async fn revoke_all_user_sessions(&self, user_id: &str) -> Result<u32> {
525 let sessions = self.storage.get_user_sessions(user_id).await?;
526 let mut revoked_count = 0;
527
528 for mut session in sessions {
529 if session.state == SessionState::Active {
530 session.state = SessionState::Revoked;
531 self.storage.update_session(&session).await?;
532 revoked_count += 1;
533 }
534 }
535
536 Ok(revoked_count)
537 }
538
539 pub async fn get_user_sessions(
541 &self,
542 user_id: &str,
543 include_inactive: bool,
544 ) -> Result<Vec<Session>> {
545 let mut sessions = self.storage.get_user_sessions(user_id).await?;
546
547 if !include_inactive {
548 sessions.retain(|s| s.state == SessionState::Active);
549 }
550
551 Ok(sessions)
552 }
553
554 pub async fn cleanup_expired_sessions(&self) -> Result<u32> {
556 self.storage.cleanup_expired_sessions().await
557 }
558
559 pub fn generate_device_fingerprint(&self, metadata: &RequestMetadata) -> String {
561 self.fingerprint_generator.generate_fingerprint(metadata)
562 }
563
564 pub fn validate_device_fingerprint(
566 &self,
567 session: &Session,
568 metadata: &RequestMetadata,
569 ) -> bool {
570 let current_fingerprint = self.fingerprint_generator.generate_fingerprint(metadata);
571 current_fingerprint == session.device_info.fingerprint
572 }
573
574 pub async fn suspend_session(&self, session_id: &str, reason: &str) -> Result<()> {
576 if let Some(mut session) = self.storage.get_session(session_id).await? {
577 session.state = SessionState::Suspended;
578 session
579 .security_metadata
580 .security_flags
581 .push(SecurityFlag::SuspiciousActivity);
582
583 self.storage.update_session(&session).await?;
584
585 let mut details = HashMap::new();
587 details.insert("suspension_reason".to_string(), reason.to_string());
588
589 self.audit_logger
590 .log_event(crate::audit::AuditEvent {
591 id: String::new(),
592 event_type: crate::audit::AuditEventType::AccountLocked,
593 timestamp: SystemTime::now(),
594 user_id: Some(session.user_id),
595 session_id: Some(session_id.to_string()),
596 outcome: crate::audit::EventOutcome::Success,
597 risk_level: crate::audit::RiskLevel::High,
598 description: format!("Session suspended: {}", reason),
599 details,
600 request_metadata: crate::audit::RequestMetadata::default(),
601 resource: None,
602 actor: crate::audit::ActorInfo {
603 actor_type: "system".to_string(),
604 actor_id: "security_monitor".to_string(),
605 actor_name: None,
606 roles: vec![],
607 },
608 correlation_id: None,
609 })
610 .await?;
611 }
612 Ok(())
613 }
614
615 pub async fn update_session_data(
617 &self,
618 session_id: &str,
619 key: &str,
620 value: &str,
621 ) -> Result<()> {
622 if let Some(mut session) = self.storage.get_session(session_id).await? {
623 session.data.insert(key.to_string(), value.to_string());
624 session.last_accessed = SystemTime::now();
625 self.storage.update_session(&session).await?;
626 }
627 Ok(())
628 }
629
630 fn generate_session_id(&self) -> String {
632 format!("sess_{}", uuid::Uuid::new_v4())
633 }
634
635 fn should_rotate_session(&self, session: &Session) -> bool {
637 if !self.config.rotate_periodically {
638 return false;
639 }
640
641 let session_age = SystemTime::now()
642 .duration_since(session.created_at)
643 .unwrap_or_default();
644
645 session_age > self.config.rotation_interval
646 }
647
648 async fn get_user_session_history(&self, user_id: &str) -> Result<Vec<Session>> {
650 self.storage.get_user_sessions(user_id).await
653 }
654}
655
656pub struct DeviceFingerprintGenerator;
658
659impl Default for DeviceFingerprintGenerator {
660 fn default() -> Self {
661 Self::new()
662 }
663}
664
665impl DeviceFingerprintGenerator {
666 pub fn new() -> Self {
667 Self
668 }
669
670 pub fn generate_fingerprint(&self, metadata: &RequestMetadata) -> String {
672 let mut fingerprint_data = Vec::new();
673
674 if let Some(ua) = &metadata.user_agent {
675 fingerprint_data.push(ua.clone());
676 }
677
678 self.add_advanced_fingerprinting_data(&mut fingerprint_data, metadata);
680
681 let fingerprint_string = fingerprint_data.join("|");
682 format!("fp_{:x}", crc32fast::hash(fingerprint_string.as_bytes()))
683 }
684
685 fn add_advanced_fingerprinting_data(
687 &self,
688 fingerprint_data: &mut Vec<String>,
689 metadata: &RequestMetadata,
690 ) {
691 if let Some(ref ip) = metadata.ip_address {
693 fingerprint_data.push(format!("geo:{}", self.get_ip_geolocation(ip)));
695 }
696
697 fingerprint_data.push(format!("lang:{}", self.get_system_language()));
699 fingerprint_data.push(format!("tz:{}", self.get_timezone_offset()));
700 fingerprint_data.push(format!("hw:{}", self.get_hardware_concurrency()));
701
702 fingerprint_data.push(format!("conn:{}", self.get_connection_info()));
704
705 fingerprint_data.push(format!("caps:{}", self.get_client_capabilities()));
707 }
708
709 fn get_ip_geolocation(&self, ip: &str) -> String {
711 use std::net::IpAddr;
712 use std::str::FromStr;
713
714 if let Ok(ip_addr) = IpAddr::from_str(ip) {
716 match ip_addr {
717 IpAddr::V4(ipv4) => {
718 let octets = ipv4.octets();
719
720 if (octets[0] == 10)
722 || (octets[0] == 172 && octets[1] >= 16 && octets[1] <= 31)
723 || (octets[0] == 192 && octets[1] == 168)
724 {
725 return "private_rfc1918".to_string();
726 }
727
728 if octets[0] == 127 {
730 return "loopback".to_string();
731 }
732
733 if octets[0] == 169 && octets[1] == 254 {
735 return "link_local".to_string();
736 }
737
738 if octets[0] >= 224 && octets[0] <= 239 {
740 return "multicast".to_string();
741 }
742
743 match (octets[0], octets[1], octets[2], octets[3]) {
745 (8, 8, 8, 8) | (8, 8, 4, 4) => "google_dns".to_string(),
746 (1, 1, 1, 1) | (1, 0, 0, 1) => "cloudflare_dns".to_string(),
747 (208, 67, 222, 222) | (208, 67, 220, 220) => "opendns".to_string(),
748 _ => {
749 self.lookup_maxmind_geolocation(&ipv4).unwrap_or_else(|| {
751 match octets[0] {
753 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),
760 }
761 })
762 }
763 }
764 }
765 IpAddr::V6(ipv6) => {
766 let segments = ipv6.segments();
767
768 if ipv6.is_loopback() {
770 return "ipv6_loopback".to_string();
771 }
772
773 if segments[0] & 0xffc0 == 0xfe80 {
775 return "ipv6_link_local".to_string();
776 }
777
778 if segments[0] & 0xfe00 == 0xfc00 {
780 return "ipv6_unique_local".to_string();
781 }
782
783 if segments[0] & 0xff00 == 0xff00 {
785 return "ipv6_multicast".to_string();
786 }
787
788 format!("ipv6_global_{:x}", segments[0] / 0x1000)
790 }
791 }
792 } else {
793 "invalid_ip".to_string()
794 }
795 }
796
797 fn get_system_language(&self) -> String {
799 "en-US".to_string()
801 }
802
803 fn get_timezone_offset(&self) -> String {
805 "-05:00".to_string() }
808
809 fn get_hardware_concurrency(&self) -> String {
811 "4".to_string()
813 }
814
815 fn get_connection_info(&self) -> String {
817 "wifi".to_string()
819 }
820
821 fn get_client_capabilities(&self) -> String {
823 "webgl2_canvas_audio".to_string()
825 }
826
827 fn lookup_maxmind_geolocation(&self, ip: &std::net::Ipv4Addr) -> Option<String> {
829 use std::path::Path;
830
831 let db_path =
833 std::env::var("MAXMIND_DB_PATH").unwrap_or_else(|_| "GeoLite2-City.mmdb".to_string());
834
835 if !Path::new(&db_path).exists() {
836 log::warn!(
837 "MaxMind database not found at {}, falling back to basic geolocation",
838 db_path
839 );
840 return None;
841 }
842
843 match maxminddb::Reader::open_readfile(&db_path) {
844 Ok(reader) => {
845 match reader.lookup::<maxminddb::geoip2::City>((*ip).into()) {
846 Ok(Some(city)) => {
847 let mut location_parts = Vec::new();
848
849 if let Some(country) = city.country.and_then(|c| c.names)
851 && let Some(name) = country.get("en")
852 {
853 location_parts.push(format!("country:{}", name));
854 }
855
856 if let Some(subdivisions) = city.subdivisions
857 && let Some(subdivision) = subdivisions.first()
858 && let Some(names) = &subdivision.names
859 && let Some(name) = names.get("en")
860 {
861 location_parts.push(format!("region:{}", name));
862 }
863
864 if let Some(city_data) = city.city.and_then(|c| c.names)
865 && let Some(name) = city_data.get("en")
866 {
867 location_parts.push(format!("city:{}", name));
868 }
869
870 if let Some(location) = city.location
871 && let (Some(lat), Some(lon)) = (location.latitude, location.longitude)
872 {
873 location_parts.push(format!("coords:{:.4},{:.4}", lat, lon));
874 }
875
876 if let Some(traits) = city.traits {
878 let mut risk_indicators = Vec::new();
879
880 if traits.is_anonymous_proxy == Some(true) {
881 risk_indicators.push("proxy");
882 }
883 if traits.is_satellite_provider == Some(true) {
884 risk_indicators.push("satellite");
885 }
886 if traits.is_anycast == Some(true) {
887 risk_indicators.push("anycast");
888 }
889
890 if !risk_indicators.is_empty() {
891 location_parts
892 .push(format!("threats:{}", risk_indicators.join(",")));
893 }
894 }
895
896 Some(location_parts.join("|"))
897 }
898 Ok(None) => {
899 log::debug!("MaxMind lookup returned no data for {}", ip);
900 None
901 }
902 Err(e) => {
903 log::debug!("MaxMind lookup failed for {}: {}", ip, e);
904 None
905 }
906 }
907 }
908 Err(e) => {
909 log::warn!("Failed to open MaxMind database: {}", e);
910 None
911 }
912 }
913 }
914}
915
916pub struct RiskCalculator;
918
919impl Default for RiskCalculator {
920 fn default() -> Self {
921 Self::new()
922 }
923}
924
925impl RiskCalculator {
926 pub fn new() -> Self {
927 Self
928 }
929
930 pub fn calculate_risk(
932 &self,
933 device_info: &DeviceInfo,
934 metadata: &RequestMetadata,
935 _session_history: &[Session],
936 threat_intel_manager: Option<&ThreatFeedManager>,
937 ) -> u8 {
938 let mut risk_score = 0u8;
939
940 if !device_info.is_trusted {
942 risk_score += 20;
943 }
944
945 if let Some(ip) = &metadata.ip_address
947 && self.is_suspicious_ip(ip, threat_intel_manager)
948 {
949 risk_score += 30;
950 }
951
952 if let Some(location) = &metadata.geolocation {
954 let mut geo_risk = 0;
955
956 if let Some(country) = &location.country {
958 let country_lower = country.to_lowercase();
959
960 let high_risk_countries =
962 ["tor", "anonymous", "vpn", "proxy", "hosting", "datacenter"];
963
964 for indicator in &high_risk_countries {
965 if country_lower.contains(indicator) {
966 geo_risk += 30;
967 break;
968 }
969 }
970
971 geo_risk += self.assess_country_threat_level(&country_lower) as u8;
973
974 let elevated_risk_patterns = ["cloud", "aws", "azure", "gcp"];
976 for pattern in &elevated_risk_patterns {
977 if country_lower.contains(pattern) {
978 geo_risk += 20;
979 break;
980 }
981 }
982 }
983
984 if let (Some(lat), Some(lon)) = (location.latitude, location.longitude) {
986 if !(-90.0..=90.0).contains(&lat) || !(-180.0..=180.0).contains(&lon) {
988 geo_risk += 25; }
990
991 if (lat * 100.0).fract().abs() < 0.01 && (lon * 100.0).fract().abs() < 0.01 {
993 geo_risk += 15; }
995 }
996
997 if let Some(region) = &location.region {
999 let region_lower = region.to_lowercase();
1000 if region_lower.contains("hosting") || region_lower.contains("datacenter") {
1001 geo_risk += 20;
1002 }
1003 }
1004
1005 if let Some(city) = &location.city {
1007 let city_lower = city.to_lowercase();
1008 if city_lower.contains("server") || city_lower.contains("datacenter") {
1009 geo_risk += 15;
1010 }
1011 }
1012
1013 risk_score += geo_risk;
1014 }
1015
1016 risk_score.min(100)
1022 }
1023
1024 fn is_suspicious_ip(&self, ip: &str, threat_intel_manager: Option<&ThreatFeedManager>) -> bool {
1026 use std::net::IpAddr;
1027 use std::str::FromStr;
1028
1029 if let Ok(ip_addr) = IpAddr::from_str(ip) {
1031 match ip_addr {
1032 IpAddr::V4(ipv4) => {
1033 let octets = ipv4.octets();
1034
1035 if self.check_malicious_ip_feeds(&ipv4, threat_intel_manager) {
1037 return true;
1038 }
1039
1040 let suspicious_ranges = [
1042 (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), ];
1050
1051 for (net, _, _, _, _) in &suspicious_ranges {
1052 if octets[0] == *net {
1053 return true;
1054 }
1055 }
1056
1057 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)
1063 {
1064 return true;
1066 }
1067
1068 if octets[3] == 0 || octets[3] == 255 {
1070 return true;
1071 }
1072
1073 if self.check_proxy_vpn_databases(&ipv4) {
1075 return true;
1076 }
1077
1078 let proxy_ports_in_ip = [
1080 80, 443, 8080, 3128, 1080, 8000, 8888, 9050, ];
1082
1083 for &port in &proxy_ports_in_ip {
1084 if octets[2] == (port / 256) as u8 && octets[3] == (port % 256) as u8 {
1085 return true;
1086 }
1087 }
1088
1089 false
1090 }
1091 IpAddr::V6(ipv6) => {
1092 let segments = ipv6.segments();
1093
1094 if self.check_tor_exit_nodes(&ipv6) {
1096 return true;
1097 }
1098
1099 if segments[0] == 0x2001 && segments[1] == 0x67c {
1101 return true;
1103 }
1104
1105 let mut sequential_count = 0;
1107 for i in 1..segments.len() {
1108 if segments[i] == segments[i - 1] + 1 {
1109 sequential_count += 1;
1110 }
1111 }
1112 if sequential_count >= 4 {
1113 return true;
1115 }
1116
1117 if segments[0] == 0x2001 && segments[1] == 0x470 {
1119 return true;
1121 }
1122
1123 false
1124 }
1125 }
1126 } else {
1127 true }
1129 }
1130
1131 fn assess_country_threat_level(&self, country: &str) -> u32 {
1133 use std::path::Path;
1134
1135 let threat_db_path = std::env::var("COUNTRY_THREAT_DB_PATH")
1137 .unwrap_or_else(|_| "country-threats.csv".to_string());
1138
1139 if Path::new(&threat_db_path).exists() {
1140 if let Ok(contents) = std::fs::read_to_string(&threat_db_path) {
1142 let mut csv_reader = csv::Reader::from_reader(contents.as_bytes());
1143
1144 for result in csv_reader.records() {
1145 if let Ok(record) = result
1146 && record.len() >= 2
1147 {
1148 let threat_country = record[0].to_lowercase();
1149 if let Ok(risk_score) = record[1].parse::<u32>()
1150 && country.contains(&threat_country)
1151 {
1152 log::debug!("Country threat match: {} -> risk {}", country, risk_score);
1153 return risk_score;
1154 }
1155 }
1156 }
1157 }
1158 }
1159
1160 let high_risk_indicators = [
1162 ("botnet", 40),
1163 ("malware", 35),
1164 ("ransomware", 45),
1165 ("cybercrime", 30),
1166 ("hacking", 25),
1167 ("fraud", 20),
1168 ];
1169
1170 for (indicator, risk) in &high_risk_indicators {
1171 if country.contains(indicator) {
1172 return *risk;
1173 }
1174 }
1175
1176 let hosting_risk_patterns = [
1178 ("hosting", 15),
1179 ("datacenter", 15),
1180 ("cloud", 10),
1181 ("server", 12),
1182 ("vps", 18),
1183 ("dedicated", 10),
1184 ];
1185
1186 for (pattern, risk) in &hosting_risk_patterns {
1187 if country.contains(pattern) {
1188 return *risk;
1189 }
1190 }
1191
1192 0 }
1194
1195 fn check_malicious_ip_feeds(
1197 &self,
1198 ip: &std::net::Ipv4Addr,
1199 threat_intel_manager: Option<&ThreatFeedManager>,
1200 ) -> bool {
1201 if let Some(threat_manager) = threat_intel_manager {
1203 return threat_manager.is_malicious_ip(&std::net::IpAddr::V4(*ip));
1204 }
1205
1206 use std::path::Path;
1208
1209 let feed_paths = [
1211 std::env::var("MALICIOUS_IPS_DB_PATH")
1212 .unwrap_or_else(|_| "malicious-ips.txt".to_string()),
1213 std::env::var("BOTNET_IPS_DB_PATH").unwrap_or_else(|_| "botnet-ips.txt".to_string()),
1214 std::env::var("TOR_EXIT_NODES_DB_PATH").unwrap_or_else(|_| "tor-exits.txt".to_string()),
1215 ];
1216
1217 for feed_path in &feed_paths {
1218 if Path::new(feed_path).exists()
1219 && let Ok(contents) = std::fs::read_to_string(feed_path)
1220 {
1221 for line in contents.lines() {
1222 let line = line.trim();
1223 if line.is_empty() || line.starts_with('#') {
1224 continue;
1225 }
1226
1227 if line == ip.to_string() {
1229 log::warn!("Malicious IP detected: {} (source: {})", ip, feed_path);
1230 return true;
1231 }
1232
1233 if line.contains('/')
1235 && let Ok(network) = line.parse::<ipnetwork::Ipv4Network>()
1236 && network.contains(*ip)
1237 {
1238 log::warn!(
1239 "Malicious network detected: {} in {} (source: {})",
1240 ip,
1241 network,
1242 feed_path
1243 );
1244 return true;
1245 }
1246 }
1247 }
1248 }
1249
1250 false
1251 }
1252
1253 fn check_proxy_vpn_databases(&self, ip: &std::net::Ipv4Addr) -> bool {
1255 use std::path::Path;
1256
1257 let db_sources = [
1259 ("VPN_DATABASE_PATH", "vpn-ranges.txt"),
1260 ("PROXY_DATABASE_PATH", "proxy-ips.txt"),
1261 ("DATACENTER_DATABASE_PATH", "datacenter-ranges.txt"),
1262 ("HOSTING_DATABASE_PATH", "hosting-providers.txt"),
1263 ];
1264
1265 for (env_var, default_file) in &db_sources {
1266 let db_path = std::env::var(env_var).unwrap_or_else(|_| default_file.to_string());
1267
1268 if Path::new(&db_path).exists()
1269 && let Ok(contents) = std::fs::read_to_string(&db_path)
1270 {
1271 for line in contents.lines() {
1272 let line = line.trim();
1273 if line.is_empty() || line.starts_with('#') {
1274 continue;
1275 }
1276
1277 if line.contains('/') {
1279 if let Ok(network) = line.parse::<ipnetwork::Ipv4Network>()
1281 && network.contains(*ip)
1282 {
1283 log::info!(
1284 "Proxy/VPN detected: {} in {} (source: {})",
1285 ip,
1286 network,
1287 db_path
1288 );
1289 return true;
1290 }
1291 } else if line.contains('-') {
1292 let parts: Vec<&str> = line.split('-').collect();
1294 if parts.len() == 2
1295 && let (Ok(start_ip), Ok(end_ip)) = (
1296 parts[0].trim().parse::<std::net::Ipv4Addr>(),
1297 parts[1].trim().parse::<std::net::Ipv4Addr>(),
1298 )
1299 {
1300 let ip_u32 = u32::from(*ip);
1301 let start_u32 = u32::from(start_ip);
1302 let end_u32 = u32::from(end_ip);
1303
1304 if ip_u32 >= start_u32 && ip_u32 <= end_u32 {
1305 log::info!(
1306 "Proxy/VPN range detected: {} in {}-{} (source: {})",
1307 ip,
1308 start_ip,
1309 end_ip,
1310 db_path
1311 );
1312 return true;
1313 }
1314 }
1315 } else if line == ip.to_string() {
1316 log::info!("Proxy/VPN exact match: {} (source: {})", ip, db_path);
1318 return true;
1319 }
1320 }
1321 }
1322 }
1323
1324 false
1325 }
1326
1327 fn check_tor_exit_nodes(&self, ip: &std::net::Ipv6Addr) -> bool {
1329 use std::path::Path;
1330
1331 let tor_db_path = std::env::var("TOR_EXIT_NODES_IPV6_PATH")
1333 .unwrap_or_else(|_| "tor-exits-ipv6.txt".to_string());
1334
1335 if Path::new(&tor_db_path).exists()
1336 && let Ok(contents) = std::fs::read_to_string(&tor_db_path)
1337 {
1338 for line in contents.lines() {
1339 let line = line.trim();
1340 if line.is_empty() || line.starts_with('#') {
1341 continue;
1342 }
1343
1344 if let Ok(tor_ip) = line.parse::<std::net::Ipv6Addr>()
1346 && tor_ip == *ip
1347 {
1348 log::warn!("Tor exit node detected: {}", ip);
1349 return true;
1350 }
1351
1352 if line.contains('/')
1354 && let Ok(network) = line.parse::<ipnetwork::Ipv6Network>()
1355 && network.contains(*ip)
1356 {
1357 log::warn!("Tor exit network detected: {} in {}", ip, network);
1358 return true;
1359 }
1360 }
1361 }
1362
1363 if let Some(ipv4) = ip.to_ipv4() {
1365 let tor_v4_path = std::env::var("TOR_EXIT_NODES_IPV4_PATH")
1366 .unwrap_or_else(|_| "tor-exits-ipv4.txt".to_string());
1367
1368 if Path::new(&tor_v4_path).exists()
1369 && let Ok(contents) = std::fs::read_to_string(&tor_v4_path)
1370 {
1371 for line in contents.lines() {
1372 let line = line.trim();
1373 if line == ipv4.to_string() {
1374 log::warn!("Tor exit node detected (IPv4-mapped): {}", ip);
1375 return true;
1376 }
1377 }
1378 }
1379 }
1380
1381 false
1382 }
1383}
1384
1385impl Default for SessionConfig {
1386 fn default() -> Self {
1387 Self {
1388 default_duration: Duration::from_secs(3600), max_duration: Duration::from_secs(86400), idle_timeout: Duration::from_secs(1800), rotate_on_privilege_escalation: true,
1392 rotate_periodically: true,
1393 rotation_interval: Duration::from_secs(1800), max_concurrent_sessions: Some(5),
1395 track_device_fingerprints: true,
1396 enforce_geographic_restrictions: false,
1397 allowed_countries: vec![],
1398 security_policy: SessionSecurityPolicy::default(),
1399 }
1400 }
1401}
1402
1403impl Default for SessionSecurityPolicy {
1404 fn default() -> Self {
1405 Self {
1406 require_mfa_for_new_devices: true,
1407 require_reauth_for_sensitive_ops: true,
1408 reauth_timeout: Duration::from_secs(300), max_risk_score: 70,
1410 auto_suspend_suspicious: true,
1411 verify_location_changes: true,
1412 limit_concurrent_sessions: true,
1413 }
1414 }
1415}
1416
1417#[cfg(test)]
1418mod tests {
1419 use super::*;
1420
1421 #[test]
1422 fn test_device_fingerprint_generation() {
1423 let generator = DeviceFingerprintGenerator::new();
1424 let metadata = RequestMetadata {
1425 user_agent: Some("Mozilla/5.0".to_string()),
1426 ..Default::default()
1427 };
1428
1429 let fp1 = generator.generate_fingerprint(&metadata);
1430 let fp2 = generator.generate_fingerprint(&metadata);
1431
1432 assert_eq!(fp1, fp2);
1433 assert!(fp1.starts_with("fp_"));
1434 }
1435
1436 #[test]
1437 fn test_risk_calculation() {
1438 let calculator = RiskCalculator::new();
1439 let device_info = DeviceInfo {
1440 fingerprint: "test".to_string(),
1441 device_type: "desktop".to_string(),
1442 operating_system: None,
1443 browser: None,
1444 screen_resolution: None,
1445 timezone: None,
1446 language: None,
1447 is_trusted: false,
1448 device_name: None,
1449 };
1450
1451 let metadata = RequestMetadata::default();
1452 let history = vec![];
1453
1454 let risk = calculator.calculate_risk(&device_info, &metadata, &history, None);
1455 assert!(risk >= 20); }
1457}