49 lines
1.4 KiB
Rust
49 lines
1.4 KiB
Rust
|
|
use cot::db::{Database, query};
|
||
|
|
|
||
|
|
use crate::models::Setting;
|
||
|
|
|
||
|
|
/// Read `turnstile_site_key` from Settings. Returns empty string if not configured.
|
||
|
|
pub async fn get_site_key(db: &Database) -> cot::Result<String> {
|
||
|
|
let key = "turnstile_site_key".to_string();
|
||
|
|
Ok(query!(Setting, $key == key)
|
||
|
|
.get(db)
|
||
|
|
.await?
|
||
|
|
.map(|s| s.value)
|
||
|
|
.unwrap_or_default())
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Verify a Turnstile token against Cloudflare.
|
||
|
|
/// Returns `true` if verification succeeds, or if no secret key is configured (passthrough).
|
||
|
|
pub async fn verify(db: &Database, token: Option<&str>) -> cot::Result<bool> {
|
||
|
|
let secret_key_name = "turnstile_secret_key".to_string();
|
||
|
|
let secret_key = query!(Setting, $key == secret_key_name)
|
||
|
|
.get(db)
|
||
|
|
.await?
|
||
|
|
.map(|s| s.value)
|
||
|
|
.filter(|s| !s.is_empty());
|
||
|
|
|
||
|
|
let Some(secret) = secret_key else {
|
||
|
|
return Ok(true);
|
||
|
|
};
|
||
|
|
|
||
|
|
let token = token.unwrap_or("");
|
||
|
|
let client = reqwest::Client::new();
|
||
|
|
let resp = client
|
||
|
|
.post("https://challenges.cloudflare.com/turnstile/v0/siteverify")
|
||
|
|
.json(&serde_json::json!({
|
||
|
|
"secret": secret,
|
||
|
|
"response": token
|
||
|
|
}))
|
||
|
|
.send()
|
||
|
|
.await;
|
||
|
|
|
||
|
|
Ok(match resp {
|
||
|
|
Ok(r) => r
|
||
|
|
.json::<serde_json::Value>()
|
||
|
|
.await
|
||
|
|
.map(|v| v["success"].as_bool() == Some(true))
|
||
|
|
.unwrap_or(false),
|
||
|
|
Err(_) => false,
|
||
|
|
})
|
||
|
|
}
|