From f44180a9809020a53bc6ef60b110b5a77f4b2202 Mon Sep 17 00:00:00 2001 From: Paul Makles <paulmakles@gmail.com> Date: Thu, 13 Aug 2020 13:06:06 +0200 Subject: [PATCH] Add hCaptcha support. --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/routes/account.rs | 22 ++++++++++++++++++++++ src/routes/guild.rs | 2 +- src/routes/root.rs | 8 ++++++-- src/util/captcha.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ src/util/mod.rs | 2 ++ 7 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 src/util/captcha.rs diff --git a/Cargo.lock b/Cargo.lock index 04236fb..35b6e3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1907,7 +1907,7 @@ dependencies = [ [[package]] name = "revolt" -version = "0.2.8" +version = "0.2.9" dependencies = [ "bcrypt", "bitfield", diff --git a/Cargo.toml b/Cargo.toml index 207a9c8..a9b2247 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "revolt" -version = "0.2.8" +version = "0.2.9" authors = ["Paul Makles <paulmakles@gmail.com>"] edition = "2018" diff --git a/src/routes/account.rs b/src/routes/account.rs index 124dc5b..f3f213e 100644 --- a/src/routes/account.rs +++ b/src/routes/account.rs @@ -2,6 +2,7 @@ use super::Response; use crate::database; use crate::email; use crate::util::gen_token; +use crate::util::captcha; use bcrypt::{hash, verify}; use chrono::prelude::*; @@ -17,6 +18,7 @@ pub struct Create { username: String, password: String, email: String, + captcha: Option<String>, } /// create a new Revolt account @@ -28,6 +30,12 @@ pub struct Create { /// (3) add user and send email verification #[post("/create", data = "<info>")] pub fn create(info: Json<Create>) -> Response { + if let Err(error) = captcha::verify(&info.captcha) { + return Response::BadRequest( + json!({ "error": error }) + ); + } + let col = database::get_collection("users"); if info.username.len() < 2 || info.username.len() > 32 { @@ -150,6 +158,7 @@ pub fn verify_email(code: String) -> Response { #[derive(Serialize, Deserialize)] pub struct Resend { email: String, + captcha: Option<String>, } /// resend a verification email @@ -158,6 +167,12 @@ pub struct Resend { /// (3) resend the email #[post("/resend", data = "<info>")] pub fn resend_email(info: Json<Resend>) -> Response { + if let Err(error) = captcha::verify(&info.captcha) { + return Response::BadRequest( + json!({ "error": error }) + ); + } + let col = database::get_collection("users"); if let Some(u) = col @@ -218,6 +233,7 @@ pub fn resend_email(info: Json<Resend>) -> Response { pub struct Login { email: String, password: String, + captcha: Option<String>, } /// login to a Revolt account @@ -226,6 +242,12 @@ pub struct Login { /// (3) return access token #[post("/login", data = "<info>")] pub fn login(info: Json<Login>) -> Response { + if let Err(error) = captcha::verify(&info.captcha) { + return Response::BadRequest( + json!({ "error": error }) + ); + } + let col = database::get_collection("users"); if let Some(u) = col diff --git a/src/routes/guild.rs b/src/routes/guild.rs index 8409ec9..516741f 100644 --- a/src/routes/guild.rs +++ b/src/routes/guild.rs @@ -2,7 +2,7 @@ use super::channel::ChannelType; use super::Response; use crate::database::guild::{fetch_member as get_member, get_invite, Guild, MemberKey}; use crate::database::{ - self, channel::fetch_channel, guild::fetch_guilds, guild::serialise_guilds_with_channels, channel::Channel, Permission, PermissionCalculator, user::User + self, channel::fetch_channel, guild::serialise_guilds_with_channels, channel::Channel, Permission, PermissionCalculator, user::User }; use crate::notifications::{ self, diff --git a/src/routes/root.rs b/src/routes/root.rs index 269cb55..29715ac 100644 --- a/src/routes/root.rs +++ b/src/routes/root.rs @@ -1,16 +1,20 @@ use super::Response; use mongodb::bson::doc; +use std::env; /// root #[get("/")] pub fn root() -> Response { Response::Success(json!({ - "revolt": "0.2.8", + "revolt": "0.2.9", "version": { "major": 0, "minor": 2, - "patch": 8 + "patch": 9 + }, + "features": { + "captcha": env::var("HCAPTCHA_KEY").is_ok() } })) } diff --git a/src/util/captcha.rs b/src/util/captcha.rs new file mode 100644 index 0000000..a9f6213 --- /dev/null +++ b/src/util/captcha.rs @@ -0,0 +1,42 @@ +use serde::{Serialize, Deserialize}; +use reqwest::blocking::Client; +use std::collections::HashMap; +use std::env; + +#[derive(Serialize, Deserialize)] +struct CaptchaResponse { + success: bool +} + +pub fn verify(user_token: &Option<String>) -> Result<(), String> { + if let Ok(key) = env::var("HCAPTCHA_KEY") { + if let Some(token) = user_token { + let mut map = HashMap::new(); + map.insert("secret", key); + map.insert("response", token.to_string()); + + let client = Client::new(); + if let Ok(response) = client + .post("https://hcaptcha.com/siteverify") + .json(&map) + .send() + { + let result: CaptchaResponse = response + .json() + .map_err(|_| "Failed to deserialise captcha result.".to_string())?; + + if result.success { + Ok(()) + } else { + Err("Unsuccessful captcha verification".to_string()) + } + } else { + Err("Failed to verify with hCaptcha".to_string()) + } + } else { + Err("Missing hCaptcha token!".to_string()) + } + } else { + Ok(()) + } +} diff --git a/src/util/mod.rs b/src/util/mod.rs index 038b896..c88ae63 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -2,6 +2,8 @@ use hashbrown::HashSet; use rand::{distributions::Alphanumeric, Rng}; use std::iter::FromIterator; +pub mod captcha; + pub fn vec_to_set<T: Clone + Eq + std::hash::Hash>(data: &[T]) -> HashSet<T> { HashSet::from_iter(data.iter().cloned()) } -- GitLab