use std::net::IpAddr;
use serde::Deserialize;
use crate::state::AppState;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Verdict {
Passed,
Failed,
Unavailable,
}
#[derive(Deserialize)]
struct SiteVerify {
success: bool,
}
pub async fn verify(state: &AppState, token: Option<&str>, ip: Option<IpAddr>) -> Verdict {
let Some(cfg) = &state.config.turnstile else {
return Verdict::Passed;
};
let Some(token) = token
.map(str::trim)
.filter(|t| !t.is_empty() && t.len() <= 2048)
else {
return Verdict::Failed;
};
let mut form = vec![
("secret", cfg.secret.clone()),
("response", token.to_owned()),
];
if let Some(ip) = ip {
form.push(("remoteip", ip.to_string()));
}
let response = state
.http
.post(cfg.verify_url.clone())
.form(&form)
.send()
.await
.and_then(|r| r.error_for_status());
let body = match response {
Ok(r) => r.json::<SiteVerify>().await,
Err(e) => {
tracing::warn!(error = %e, "turnstile verification unavailable");
return Verdict::Unavailable;
}
};
match body {
Ok(v) if v.success => Verdict::Passed,
Ok(_) => Verdict::Failed,
Err(e) => {
tracing::warn!(error = %e, "turnstile verification returned an unreadable answer");
Verdict::Unavailable
}
}
}