119 lines
3.7 KiB
Rust
119 lines
3.7 KiB
Rust
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
//! # mcaptcha-api-rs: Library to interact with with mCaptcha API
|
|
//!
|
|
//! This library provides a convenient interface to validate [mCaptcha authorization
|
|
//! tokens](https://mcaptcha.org/docs/webmasters/terminology#authorization-token) presented by
|
|
//! Visitors against your mCaptcha instances. It uses [reqwest](https://crates.io/crates/reqwest),
|
|
//! and `native-tls` under the hood to communicate with the API.
|
|
//! ```rust,ignore
|
|
//! use url::Url;
|
|
//! use mcaptcha_api_rs::MCaptcha;
|
|
//!
|
|
//! let mcaptcha = MCaptcha::new("sitekeyfromdashboard", "secretfromdashboadr", Url::parse("https://mcaptcha.example.com").unwrap());
|
|
//! assert!(mcaptcha.verify("authorizationtokenfromvisitor").await.unwrap());
|
|
//! ```
|
|
|
|
use reqwest::Client;
|
|
use serde::{Deserialize, Serialize};
|
|
use url::Url;
|
|
|
|
/// API Client class
|
|
pub struct MCaptcha {
|
|
client: Client,
|
|
sitekey: String,
|
|
account_secret: String,
|
|
verify_path: Url,
|
|
}
|
|
|
|
impl MCaptcha {
|
|
/// Create new instance of API client
|
|
///
|
|
/// Parameters:
|
|
/// - [`sitekey`](https://mcaptcha.org/docs/webmasters/terminology#sitekey): unique identifier of
|
|
/// captcha configuration. Available on mCaptcha dashboard.
|
|
/// - `account_secret`: used for authentication. Available on the settings page of mCaptcha dashboard
|
|
/// - `instance_url`: the URL of your mCaptcha instance
|
|
///
|
|
pub fn new<T: Into<String>>(sitekey: T, account_secret: T, instance_url: Url) -> Self {
|
|
let mut verify_path = instance_url.clone();
|
|
verify_path.set_path("/api/v1/pow/siteverify");
|
|
Self {
|
|
sitekey: sitekey.into(),
|
|
account_secret: account_secret.into(),
|
|
verify_path,
|
|
client: Client::new(),
|
|
}
|
|
}
|
|
|
|
/// Verify authorization token presented by visitor against mCaptcha server
|
|
pub async fn verify(&self, token: &str) -> Result<bool, reqwest::Error> {
|
|
let payload = CaptchaVerfiyPayload::from_ctx(self, token);
|
|
let res: CaptchaVerifyResp = self
|
|
.client
|
|
.post(self.verify_path.clone())
|
|
.json(&payload)
|
|
.send()
|
|
.await?
|
|
.json()
|
|
.await?;
|
|
|
|
Ok(res.valid)
|
|
}
|
|
}
|
|
|
|
#[derive(Default, Debug, PartialEq, Clone, Serialize, Deserialize)]
|
|
struct CaptchaVerifyResp {
|
|
valid: bool,
|
|
}
|
|
|
|
#[derive(Default, Debug, PartialEq, Clone, Serialize, Deserialize)]
|
|
struct CaptchaVerfiyPayload<'a> {
|
|
token: &'a str,
|
|
key: &'a str,
|
|
secret: &'a str,
|
|
}
|
|
|
|
impl<'a> CaptchaVerfiyPayload<'a> {
|
|
fn from_ctx(m: &'a MCaptcha, token: &'a str) -> Self {
|
|
Self {
|
|
key: &m.sitekey,
|
|
token,
|
|
secret: &m.account_secret,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::env;
|
|
|
|
use super::*;
|
|
|
|
#[actix_rt::test]
|
|
async fn verify_works() {
|
|
dotenv::from_filename(".env.local").ok();
|
|
|
|
let instance_url = env::var("INSTANCE_URL").expect("instance url not set");
|
|
let sitekey = env::var("SITEKEY").expect("sitekey not set");
|
|
let secret = env::var("SECRET").expect("secret not set");
|
|
let token = env::var("TOKEN").expect("token not set");
|
|
|
|
println!("token={token}");
|
|
println!("sitekey={sitekey}");
|
|
println!("secret={secret}");
|
|
println!("instance_url={instance_url}");
|
|
|
|
let mcaptcha = MCaptcha::new(
|
|
sitekey,
|
|
secret,
|
|
Url::parse(&instance_url).expect("instance_url is not URL"),
|
|
);
|
|
assert!(mcaptcha.verify(&token).await.unwrap());
|
|
assert!(!mcaptcha.verify("foo").await.unwrap());
|
|
}
|
|
}
|