// SPDX-FileCopyrightText: 2024 Aravinth Manivannan // // 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. 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 /// /// /// ```rust /// use url::Url; /// use mcaptcha_api_rs::MCaptcha; /// /// let mcaptcha = MCaptcha::new("sitekeyfromdashboard", "secretfromdashboadr", Url::parse("https://mcaptcha.example.com").unwrap()); /// ``` pub fn new(sitekey: String, account_secret: String, instance_url: Url) -> Self { let mut verify_path = instance_url.clone(); verify_path.set_path("/api/v1/pow/siteverify"); Self { sitekey, account_secret, verify_path, client: Client::new(), } } /// Verify authorization token presented by visitor against mCaptcha server /// /// /// ```rust,no_run /// 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()); /// ``` pub async fn verify(&self, token: &str) -> Result { 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()); } }