diff --git a/.woodpecker.yml b/.woodpecker.yml index 489634d..dfa4d02 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -34,6 +34,7 @@ steps: - pip install virtualenv && virtualenv venv - make env - make test.env + - mv .env.local .env_sample test: image: rust @@ -42,4 +43,4 @@ steps: - PASSWORD=password - INSTANCE_URL=http://mcaptcha:7000 commands: - - . .env.local && echo $TOKEN $SITEKY + - . .env_sample && make test diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..089f9af --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "mcaptcha-api-rs" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +reqwest = { version = "0.11.23", features = ["json", "gzip", "native-tls"] } +serde = { version = "1.0.194", features = ["derive"] } +url = "2.5.0" + +[dev-dependencies] +actix-rt = "2.9.0" diff --git a/Makefile b/Makefile index c96df4f..2bcba49 100644 --- a/Makefile +++ b/Makefile @@ -16,10 +16,9 @@ test.env: test: - . env.local && . && \ - SITEKEY=$(SITEKEY) SECRET=$(SECRET) \ + SITEKEY=$(SITEKEY) SECRET=$(SECRET) \ INSTANCE_URL=$(INSTANCE_URL) \ - TOKEN=$(TOKEN) pytest + TOKEN=$(TOKEN) cargo test lint: black src/ scripts/ diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..9dab94e --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,83 @@ +use reqwest::Client; +use serde::{Deserialize, Serialize}; +use url::Url; + +pub struct MCaptcha { + client: Client, + sitekey: String, + account_secret: String, + verify_path: Url, +} + +impl MCaptcha { + 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(), + } + } + + 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 + .unwrap() + .json() + .await + .unwrap(); + + 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 super::*; + use std::env; + + #[actix_rt::test] + async fn verify_works() { + 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"); + + 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()); + } +}