use crate::error::CoreError;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BehavioralBiometrics {
pub user_id: Uuid,
pub session_id: Uuid,
pub typing_pattern: TypingPattern,
pub mouse_dynamics: MouseDynamics,
pub device_fingerprint: DeviceFingerprint,
pub risk_score: Decimal,
pub timestamp: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TypingPattern {
pub avg_keypress_duration: u64,
pub avg_interval_between_keys: u64,
pub typing_speed_wpm: u16,
pub error_rate: Decimal,
pub pattern_signature: Vec<u64>,
}
impl TypingPattern {
pub fn new(intervals: Vec<u64>, errors: usize, total_keys: usize) -> Self {
let avg_interval = if !intervals.is_empty() {
intervals.iter().sum::<u64>() / intervals.len() as u64
} else {
0
};
let typing_speed = if avg_interval > 0 {
(60000 / (avg_interval * 5)).min(200) as u16 } else {
0
};
let error_rate = if total_keys > 0 {
Decimal::from(errors) / Decimal::from(total_keys)
} else {
Decimal::ZERO
};
Self {
avg_keypress_duration: 100, avg_interval_between_keys: avg_interval,
typing_speed_wpm: typing_speed,
error_rate,
pattern_signature: intervals,
}
}
pub fn similarity(&self, other: &TypingPattern) -> Decimal {
let mut similarity_score = Decimal::ZERO;
let mut factors = 0;
let speed_diff = (self.typing_speed_wpm as i32 - other.typing_speed_wpm as i32).abs();
if speed_diff < 20 {
similarity_score += Decimal::from(100 - speed_diff) / Decimal::from(100);
factors += 1;
}
let error_diff = (self.error_rate - other.error_rate).abs();
if error_diff < Decimal::new(1, 1) {
similarity_score += Decimal::ONE - error_diff * Decimal::from(10);
factors += 1;
}
if !self.pattern_signature.is_empty() && !other.pattern_signature.is_empty() {
let min_len = self
.pattern_signature
.len()
.min(other.pattern_signature.len());
let pattern_similarity = self.pattern_signature[..min_len]
.iter()
.zip(&other.pattern_signature[..min_len])
.filter(|(a, b)| {
let diff = (**a).abs_diff(**b);
diff < 50 })
.count();
similarity_score += Decimal::from(pattern_similarity) / Decimal::from(min_len);
factors += 1;
}
if factors > 0 {
similarity_score / Decimal::from(factors)
} else {
Decimal::ZERO
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MouseDynamics {
pub avg_velocity: Decimal,
pub avg_acceleration: Decimal,
pub movement_smoothness: Decimal,
pub click_precision: Decimal,
pub trajectory_points: Vec<(i32, i32)>,
}
impl MouseDynamics {
pub fn new(trajectory: Vec<(i32, i32)>, _click_targets: Vec<(i32, i32)>) -> Self {
let avg_velocity = Self::calculate_velocity(&trajectory);
let smoothness = Self::calculate_smoothness(&trajectory);
Self {
avg_velocity,
avg_acceleration: Decimal::ZERO, movement_smoothness: smoothness,
click_precision: Decimal::new(95, 2), trajectory_points: trajectory,
}
}
fn calculate_velocity(trajectory: &[(i32, i32)]) -> Decimal {
if trajectory.len() < 2 {
return Decimal::ZERO;
}
let total_distance: f64 = trajectory
.windows(2)
.map(|w| {
let dx = (w[1].0 - w[0].0) as f64;
let dy = (w[1].1 - w[0].1) as f64;
(dx * dx + dy * dy).sqrt()
})
.sum();
Decimal::from_f64_retain(total_distance / trajectory.len() as f64).unwrap_or(Decimal::ZERO)
}
fn calculate_smoothness(trajectory: &[(i32, i32)]) -> Decimal {
if trajectory.len() < 3 {
return Decimal::ONE;
}
let direction_changes = trajectory
.windows(3)
.filter(|w| {
let dx1 = w[1].0 - w[0].0;
let dy1 = w[1].1 - w[0].1;
let dx2 = w[2].0 - w[1].0;
let dy2 = w[2].1 - w[1].1;
(dx1 * dx2 + dy1 * dy2) < 0
})
.count();
let smoothness = 1.0 - (direction_changes as f64 / trajectory.len() as f64);
Decimal::from_f64_retain(smoothness).unwrap_or(Decimal::ONE)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeviceFingerprint {
pub fingerprint_hash: String,
pub user_agent: String,
pub screen_resolution: (u32, u32),
pub timezone: String,
pub language: String,
pub platform: String,
pub first_seen: chrono::DateTime<chrono::Utc>,
pub last_seen: chrono::DateTime<chrono::Utc>,
}
impl DeviceFingerprint {
pub fn new(
user_agent: String,
screen_resolution: (u32, u32),
timezone: String,
language: String,
platform: String,
) -> Self {
let fingerprint_data = format!(
"{}_{}x{}_{}_{}_{}",
user_agent, screen_resolution.0, screen_resolution.1, timezone, language, platform
);
let fingerprint_hash = format!("{:x}", md5::compute(fingerprint_data));
let now = chrono::Utc::now();
Self {
fingerprint_hash,
user_agent,
screen_resolution,
timezone,
language,
platform,
first_seen: now,
last_seen: now,
}
}
pub fn matches(&self, other: &DeviceFingerprint) -> bool {
self.fingerprint_hash == other.fingerprint_hash
}
}
#[derive(Debug, Clone)]
pub struct BiometricsAnalyzer {
user_profiles: HashMap<Uuid, Vec<BehavioralBiometrics>>,
threshold_similarity: Decimal,
}
impl BiometricsAnalyzer {
pub fn new(threshold_similarity: Decimal) -> Self {
Self {
user_profiles: HashMap::new(),
threshold_similarity,
}
}
pub fn add_sample(&mut self, biometrics: BehavioralBiometrics) {
self.user_profiles
.entry(biometrics.user_id)
.or_default()
.push(biometrics);
}
pub fn verify_user(
&self,
user_id: Uuid,
current_biometrics: &BehavioralBiometrics,
) -> Result<bool, CoreError> {
let profiles = self
.user_profiles
.get(&user_id)
.ok_or_else(|| CoreError::NotFound("No biometric profile found".to_string()))?;
if profiles.is_empty() {
return Err(CoreError::Validation(
"Insufficient biometric data".to_string(),
));
}
let recent_profiles: Vec<_> = profiles.iter().rev().take(5).collect();
let mut total_similarity = Decimal::ZERO;
for profile in recent_profiles.iter() {
let similarity = current_biometrics
.typing_pattern
.similarity(&profile.typing_pattern);
total_similarity += similarity;
}
let avg_similarity = total_similarity / Decimal::from(recent_profiles.len());
Ok(avg_similarity >= self.threshold_similarity)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FraudDetector {
pub id: Uuid,
pub user_blacklist: Vec<Uuid>,
pub ip_blacklist: Vec<String>,
pub suspicious_patterns: Vec<FraudPattern>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FraudPattern {
pub pattern_type: FraudPatternType,
pub description: String,
pub severity: FraudSeverity,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum FraudPatternType {
RapidTransactions,
UnusualLocation,
DeviceChange,
LargeTransaction,
SuspiciousPattern,
MultipleAccounts,
VelocityAnomaly,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum FraudSeverity {
Low,
Medium,
High,
Critical,
}
impl FraudDetector {
pub fn new() -> Self {
Self {
id: Uuid::new_v4(),
user_blacklist: Vec::new(),
ip_blacklist: Vec::new(),
suspicious_patterns: Vec::new(),
}
}
pub fn blacklist_user(&mut self, user_id: Uuid) {
if !self.user_blacklist.contains(&user_id) {
self.user_blacklist.push(user_id);
}
}
pub fn blacklist_ip(&mut self, ip: String) {
if !self.ip_blacklist.contains(&ip) {
self.ip_blacklist.push(ip);
}
}
pub fn is_user_blacklisted(&self, user_id: &Uuid) -> bool {
self.user_blacklist.contains(user_id)
}
pub fn is_ip_blacklisted(&self, ip: &str) -> bool {
self.ip_blacklist.contains(&ip.to_string())
}
pub fn analyze_transaction(
&self,
user_id: Uuid,
amount: Decimal,
ip_address: &str,
_device: &DeviceFingerprint,
recent_transactions: &[TransactionRecord],
) -> FraudAnalysis {
let mut risk_score = Decimal::ZERO;
let mut detected_patterns = Vec::new();
if self.is_user_blacklisted(&user_id) {
risk_score += Decimal::from(100);
detected_patterns.push(FraudPattern {
pattern_type: FraudPatternType::SuspiciousPattern,
description: "User is blacklisted".to_string(),
severity: FraudSeverity::Critical,
});
}
if self.is_ip_blacklisted(ip_address) {
risk_score += Decimal::from(50);
detected_patterns.push(FraudPattern {
pattern_type: FraudPatternType::SuspiciousPattern,
description: "IP is blacklisted".to_string(),
severity: FraudSeverity::High,
});
}
let recent_count = recent_transactions
.iter()
.filter(|t| {
let age = chrono::Utc::now()
.signed_duration_since(t.timestamp)
.num_minutes();
age < 5
})
.count();
if recent_count > 10 {
risk_score += Decimal::from(30);
detected_patterns.push(FraudPattern {
pattern_type: FraudPatternType::RapidTransactions,
description: format!("{} transactions in 5 minutes", recent_count),
severity: FraudSeverity::High,
});
}
if amount > Decimal::from(10000) {
risk_score += Decimal::from(20);
detected_patterns.push(FraudPattern {
pattern_type: FraudPatternType::LargeTransaction,
description: format!("Large transaction: {}", amount),
severity: FraudSeverity::Medium,
});
}
if recent_count > 5 {
let total_amount: Decimal = recent_transactions
.iter()
.take(recent_count)
.map(|t| t.amount)
.sum();
if total_amount > Decimal::from(50000) {
risk_score += Decimal::from(40);
detected_patterns.push(FraudPattern {
pattern_type: FraudPatternType::VelocityAnomaly,
description: format!("High velocity: {} in short time", total_amount),
severity: FraudSeverity::High,
});
}
}
let severity = if risk_score > Decimal::from(80) {
FraudSeverity::Critical
} else if risk_score > Decimal::from(50) {
FraudSeverity::High
} else if risk_score > Decimal::from(30) {
FraudSeverity::Medium
} else {
FraudSeverity::Low
};
FraudAnalysis {
risk_score,
detected_patterns,
severity,
requires_review: risk_score > Decimal::from(50),
timestamp: chrono::Utc::now(),
}
}
}
impl Default for FraudDetector {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FraudAnalysis {
pub risk_score: Decimal,
pub detected_patterns: Vec<FraudPattern>,
pub severity: FraudSeverity,
pub requires_review: bool,
pub timestamp: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransactionRecord {
pub amount: Decimal,
pub timestamp: chrono::DateTime<chrono::Utc>,
pub ip_address: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IpReputation {
pub ip_address: String,
pub reputation_score: Decimal,
pub country: String,
pub is_vpn: bool,
pub is_proxy: bool,
pub is_tor: bool,
pub abuse_reports: u32,
pub last_updated: chrono::DateTime<chrono::Utc>,
}
impl IpReputation {
pub fn new(ip_address: String, country: String) -> Self {
Self {
ip_address,
reputation_score: Decimal::from(100),
country,
is_vpn: false,
is_proxy: false,
is_tor: false,
abuse_reports: 0,
last_updated: chrono::Utc::now(),
}
}
pub fn report_abuse(&mut self) {
self.abuse_reports += 1;
self.reputation_score = (self.reputation_score - Decimal::from(10)).max(Decimal::ZERO);
self.last_updated = chrono::Utc::now();
}
pub fn is_trusted(&self) -> bool {
self.reputation_score > Decimal::from(70) && !self.is_vpn && !self.is_proxy && !self.is_tor
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccountTakeoverDetector {
pub user_id: Uuid,
pub normal_login_locations: Vec<String>,
pub normal_devices: Vec<String>,
pub last_password_change: chrono::DateTime<chrono::Utc>,
pub failed_login_attempts: u32,
}
impl AccountTakeoverDetector {
pub fn new(user_id: Uuid) -> Self {
Self {
user_id,
normal_login_locations: Vec::new(),
normal_devices: Vec::new(),
last_password_change: chrono::Utc::now(),
failed_login_attempts: 0,
}
}
pub fn check_login(
&mut self,
location: &str,
device_fingerprint: &str,
biometrics: Option<&BehavioralBiometrics>,
) -> TakeoverRisk {
let mut risk_factors = Vec::new();
let mut risk_score = Decimal::ZERO;
if !self.normal_login_locations.contains(&location.to_string()) {
risk_factors.push("Unknown location".to_string());
risk_score += Decimal::from(30);
}
if !self
.normal_devices
.contains(&device_fingerprint.to_string())
{
risk_factors.push("Unknown device".to_string());
risk_score += Decimal::from(25);
}
if self.failed_login_attempts > 3 {
risk_factors.push("Multiple failed login attempts".to_string());
risk_score += Decimal::from(20);
}
if biometrics.is_some() {
risk_score = (risk_score - Decimal::from(15)).max(Decimal::ZERO);
}
TakeoverRisk {
risk_score,
risk_factors,
requires_2fa: risk_score > Decimal::from(40),
requires_verification: risk_score > Decimal::from(60),
}
}
pub fn record_failed_login(&mut self) {
self.failed_login_attempts += 1;
}
pub fn reset_failed_attempts(&mut self) {
self.failed_login_attempts = 0;
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TakeoverRisk {
pub risk_score: Decimal,
pub risk_factors: Vec<String>,
pub requires_2fa: bool,
pub requires_verification: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PrivacyProof {
pub proof_id: Uuid,
pub statement: String,
pub commitment: String,
pub verified: bool,
pub created_at: chrono::DateTime<chrono::Utc>,
}
impl PrivacyProof {
pub fn new(statement: String, secret_value: Decimal) -> Self {
let commitment_data = format!("{}{}", statement, secret_value);
let commitment = format!("{:x}", md5::compute(commitment_data));
Self {
proof_id: Uuid::new_v4(),
statement,
commitment,
verified: false,
created_at: chrono::Utc::now(),
}
}
pub fn verify(&mut self, expected_commitment: &str) -> bool {
self.verified = self.commitment == expected_commitment;
self.verified
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_typing_pattern() {
let intervals = vec![100, 120, 110, 105, 115];
let pattern = TypingPattern::new(intervals.clone(), 1, 50);
assert!(pattern.typing_speed_wpm > 0);
assert!(pattern.error_rate > Decimal::ZERO);
assert_eq!(pattern.pattern_signature, intervals);
}
#[test]
fn test_typing_pattern_similarity() {
let pattern1 = TypingPattern::new(vec![100, 120, 110], 1, 50);
let pattern2 = TypingPattern::new(vec![105, 115, 112], 1, 50);
let similarity = pattern1.similarity(&pattern2);
assert!(similarity > Decimal::ZERO);
}
#[test]
fn test_mouse_dynamics() {
let trajectory = vec![(0, 0), (10, 10), (20, 20), (30, 30)];
let dynamics = MouseDynamics::new(trajectory, vec![]);
assert!(dynamics.avg_velocity > Decimal::ZERO);
assert!(dynamics.movement_smoothness > Decimal::ZERO);
}
#[test]
fn test_device_fingerprint() {
let fp1 = DeviceFingerprint::new(
"Mozilla/5.0".to_string(),
(1920, 1080),
"UTC".to_string(),
"en-US".to_string(),
"Linux".to_string(),
);
let fp2 = DeviceFingerprint::new(
"Mozilla/5.0".to_string(),
(1920, 1080),
"UTC".to_string(),
"en-US".to_string(),
"Linux".to_string(),
);
assert!(fp1.matches(&fp2));
}
#[test]
fn test_fraud_detector() {
let mut detector = FraudDetector::new();
let user_id = Uuid::new_v4();
detector.blacklist_user(user_id);
assert!(detector.is_user_blacklisted(&user_id));
detector.blacklist_ip("192.168.1.1".to_string());
assert!(detector.is_ip_blacklisted("192.168.1.1"));
}
#[test]
fn test_fraud_analysis() {
let detector = FraudDetector::new();
let device = DeviceFingerprint::new(
"Mozilla/5.0".to_string(),
(1920, 1080),
"UTC".to_string(),
"en-US".to_string(),
"Linux".to_string(),
);
let analysis = detector.analyze_transaction(
Uuid::new_v4(),
Decimal::from(100),
"192.168.1.1",
&device,
&[],
);
assert!(analysis.risk_score >= Decimal::ZERO);
}
#[test]
fn test_ip_reputation() {
let mut ip_rep = IpReputation::new("192.168.1.1".to_string(), "US".to_string());
assert_eq!(ip_rep.reputation_score, Decimal::from(100));
assert!(ip_rep.is_trusted());
ip_rep.report_abuse();
assert_eq!(ip_rep.abuse_reports, 1);
assert_eq!(ip_rep.reputation_score, Decimal::from(90));
}
#[test]
fn test_account_takeover_detector() {
let mut detector = AccountTakeoverDetector::new(Uuid::new_v4());
let risk = detector.check_login("US", "device123", None);
assert!(risk.risk_score > Decimal::ZERO);
detector.record_failed_login();
assert_eq!(detector.failed_login_attempts, 1);
detector.reset_failed_attempts();
assert_eq!(detector.failed_login_attempts, 0);
}
#[test]
fn test_privacy_proof() {
let mut proof = PrivacyProof::new("balance > 1000".to_string(), Decimal::from(2000));
let commitment = proof.commitment.clone();
assert!(proof.verify(&commitment));
assert!(proof.verified);
}
#[test]
fn test_biometrics_analyzer() {
let mut analyzer = BiometricsAnalyzer::new(Decimal::new(70, 2));
let user_id = Uuid::new_v4();
let pattern = TypingPattern::new(vec![100, 120, 110], 1, 50);
let dynamics = MouseDynamics::new(vec![(0, 0), (10, 10)], vec![]);
let device = DeviceFingerprint::new(
"Mozilla/5.0".to_string(),
(1920, 1080),
"UTC".to_string(),
"en-US".to_string(),
"Linux".to_string(),
);
let biometrics = BehavioralBiometrics {
user_id,
session_id: Uuid::new_v4(),
typing_pattern: pattern,
mouse_dynamics: dynamics,
device_fingerprint: device,
risk_score: Decimal::ZERO,
timestamp: chrono::Utc::now(),
};
analyzer.add_sample(biometrics.clone());
let result = analyzer.verify_user(user_id, &biometrics);
assert!(result.is_ok());
}
}