Score a request based on context signals. Returns (score, decision, triggered_signals).
(
&self,
user_id: &str,
client_ip: &str,
auth_ctx: &super::auth_context::AuthContext,
)
| 83 | /// |
| 84 | /// Returns (score, decision, triggered_signals). |
| 85 | pub fn score( |
| 86 | &self, |
| 87 | user_id: &str, |
| 88 | client_ip: &str, |
| 89 | auth_ctx: &super::auth_context::AuthContext, |
| 90 | ) -> (f64, RiskDecision, Vec<String>) { |
| 91 | let mut total = 0.0_f64; |
| 92 | let mut signals = Vec::new(); |
| 93 | |
| 94 | // Signal: new_ip. |
| 95 | if self.is_new_ip(user_id, client_ip) |
| 96 | && let Some(&w) = self.config.weights.get("new_ip") |
| 97 | { |
| 98 | total += w; |
| 99 | signals.push("new_ip".into()); |
| 100 | } |
| 101 | |
| 102 | // Signal: unusual_time (outside 06:00-22:00 local). |
| 103 | let hour = current_hour(); |
| 104 | if !(6..22).contains(&hour) |
| 105 | && let Some(&w) = self.config.weights.get("unusual_time") |
| 106 | { |
| 107 | total += w; |
| 108 | signals.push("unusual_time".into()); |
| 109 | } |
| 110 | |
| 111 | // Signal: high_privilege (superuser or tenant_admin). |
| 112 | if (auth_ctx.is_superuser() || auth_ctx.roles.iter().any(|r| r == "tenant_admin")) |
| 113 | && let Some(&w) = self.config.weights.get("high_privilege") |
| 114 | { |
| 115 | total += w; |
| 116 | signals.push("high_privilege".into()); |
| 117 | } |
| 118 | |
| 119 | // Signal: device_not_trusted. |
| 120 | if auth_ctx |
| 121 | .metadata |
| 122 | .get("device_trusted") |
| 123 | .is_none_or(|v| v != "true") |
| 124 | && let Some(&w) = self.config.weights.get("device_not_trusted") |
| 125 | { |
| 126 | total += w; |
| 127 | signals.push("device_not_trusted".into()); |
| 128 | } |
| 129 | |
| 130 | // Record this IP as known for future requests. |
| 131 | self.record_ip(user_id, client_ip); |
| 132 | |
| 133 | let decision = if total <= self.config.allow_threshold { |
| 134 | RiskDecision::Allow |
| 135 | } else if total >= self.config.deny_threshold { |
| 136 | RiskDecision::Deny |
| 137 | } else { |
| 138 | RiskDecision::StepUpMfa |
| 139 | }; |
| 140 | |
| 141 | (total, decision, signals) |
| 142 | } |