-
-
Notifications
You must be signed in to change notification settings - Fork 886
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
320 additions
and
71 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
use crate::{captcha_as_wav_base64, Perform}; | ||
use actix_web::web::Data; | ||
use captcha::{gen, Difficulty}; | ||
use chrono::Duration; | ||
use lemmy_api_common::{ | ||
context::LemmyContext, | ||
person::{CaptchaResponse, GetCaptcha, GetCaptchaResponse}, | ||
}; | ||
use lemmy_db_schema::{ | ||
source::{local_site::LocalSite, user_captcha::UserCaptcha}, | ||
utils::naive_now, | ||
}; | ||
use lemmy_utils::error::LemmyError; | ||
|
||
#[async_trait::async_trait(?Send)] | ||
impl Perform for GetCaptcha { | ||
type Response = GetCaptchaResponse; | ||
|
||
#[tracing::instrument(skip(context))] | ||
async fn perform(&self, context: &Data<LemmyContext>) -> Result<Self::Response, LemmyError> { | ||
let local_site = LocalSite::read(context.pool()).await?; | ||
|
||
if !local_site.captcha_enabled { | ||
return Ok(GetCaptchaResponse { ok: None }); | ||
} | ||
|
||
let captcha = gen(match local_site.captcha_difficulty.as_str() { | ||
"easy" => Difficulty::Easy, | ||
"hard" => Difficulty::Hard, | ||
_ => Difficulty::Medium, | ||
}); | ||
|
||
let answer = captcha.chars_as_string(); | ||
|
||
let png = captcha.as_base64().expect("failed to generate captcha"); | ||
|
||
let uuid = uuid::Uuid::new_v4().to_string(); | ||
|
||
let wav = captcha_as_wav_base64(&captcha); | ||
|
||
let captcha = UserCaptcha { | ||
answer, | ||
uuid: uuid.clone(), | ||
expires: naive_now() + Duration::minutes(10), // expires in 10 minutes | ||
}; | ||
// Stores the captcha item in the db | ||
UserCaptcha::insert(context.pool(), &captcha).await?; | ||
|
||
Ok(GetCaptchaResponse { | ||
ok: Some(CaptchaResponse { png, wav, uuid }), | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,3 +27,4 @@ pub mod registration_application; | |
pub mod secret; | ||
pub mod site; | ||
pub mod tagline; | ||
pub mod user_captcha; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
use crate::{ | ||
schema::user_captcha, | ||
source::user_captcha::{CheckCaptcha, UserCaptcha}, | ||
utils::{functions::lower, get_conn, naive_now, DbPool}, | ||
}; | ||
use diesel::{delete, insert_into, result::Error, ExpressionMethods, QueryDsl}; | ||
use diesel_async::RunQueryDsl; | ||
|
||
impl UserCaptcha { | ||
pub async fn insert(pool: &DbPool, captcha: &UserCaptcha) -> Result<Self, Error> { | ||
let conn = &mut get_conn(pool).await?; | ||
|
||
insert_into(user_captcha::table) | ||
.values(captcha) | ||
.get_result::<Self>(conn) | ||
.await | ||
} | ||
|
||
pub async fn check_captcha(pool: &DbPool, to_check: CheckCaptcha) -> Result<bool, Error> { | ||
let conn = &mut get_conn(pool).await?; | ||
|
||
// delete any expired captchas | ||
delete(user_captcha::table.filter(user_captcha::expires.lt(&naive_now()))) | ||
.execute(conn) | ||
.await?; | ||
|
||
// fetch requested captcha | ||
let captcha = user_captcha::dsl::user_captcha | ||
.filter((user_captcha::dsl::uuid).eq(to_check.uuid.clone())) | ||
.filter(lower(user_captcha::dsl::answer).eq(to_check.answer.to_lowercase().clone())) | ||
.first::<Self>(conn) | ||
.await; | ||
|
||
let result = captcha.is_ok(); | ||
|
||
// delete checked captcha | ||
delete(user_captcha::table.filter(user_captcha::uuid.eq(to_check.uuid.clone()))) | ||
.execute(conn) | ||
.await?; | ||
|
||
Ok(result) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use crate::{ | ||
source::user_captcha::{CheckCaptcha, UserCaptcha}, | ||
utils::{build_db_pool_for_tests, naive_now}, | ||
}; | ||
use chrono::Duration; | ||
use serial_test::serial; | ||
|
||
#[tokio::test] | ||
#[serial] | ||
async fn test_captcha() { | ||
let pool = &build_db_pool_for_tests().await; | ||
|
||
let captcha_a_id = "a".to_string(); | ||
|
||
let _ = UserCaptcha::insert( | ||
pool, | ||
&UserCaptcha { | ||
uuid: captcha_a_id.clone(), | ||
answer: "XYZ".to_string(), | ||
expires: naive_now() + Duration::minutes(10), | ||
}, | ||
) | ||
.await; | ||
|
||
let result = UserCaptcha::check_captcha( | ||
pool, | ||
CheckCaptcha { | ||
uuid: captcha_a_id.clone(), | ||
answer: "xyz".to_string(), | ||
}, | ||
) | ||
.await; | ||
|
||
assert!(result.is_ok()); | ||
assert!(result.unwrap()); | ||
|
||
let result_repeat = UserCaptcha::check_captcha( | ||
pool, | ||
CheckCaptcha { | ||
uuid: captcha_a_id.clone(), | ||
answer: "xyz".to_string(), | ||
}, | ||
) | ||
.await; | ||
|
||
assert!(result_repeat.is_ok()); | ||
assert!(!result_repeat.unwrap()); | ||
|
||
let expired_id = "already_expired".to_string(); | ||
|
||
let _ = UserCaptcha::insert( | ||
pool, | ||
&UserCaptcha { | ||
uuid: expired_id.clone(), | ||
answer: "xyz".to_string(), | ||
expires: naive_now() - Duration::seconds(1), | ||
}, | ||
) | ||
.await; | ||
|
||
let expired_result = UserCaptcha::check_captcha( | ||
pool, | ||
CheckCaptcha { | ||
uuid: expired_id.clone(), | ||
answer: "xyz".to_string(), | ||
}, | ||
) | ||
.await; | ||
|
||
assert!(expired_result.is_ok()); | ||
assert!(!expired_result.unwrap()); | ||
} | ||
} |
Oops, something went wrong.