feat: New CaptchaBuilder
to build Captcha
struct
Signed-off-by: Awiteb <a@4rs.nl>
This commit is contained in:
parent
27d985114a
commit
28425d939f
1 changed files with 89 additions and 20 deletions
109
src/lib.rs
109
src/lib.rs
|
@ -18,21 +18,19 @@ mod captcha_gen;
|
||||||
mod finder;
|
mod finder;
|
||||||
mod storage;
|
mod storage;
|
||||||
|
|
||||||
|
use std::{sync::Arc, time::Duration};
|
||||||
|
|
||||||
use salvo_core::{
|
use salvo_core::{
|
||||||
handler::{none_skipper, Skipper},
|
handler::{none_skipper, Skipper},
|
||||||
Depot, FlowCtrl, Handler, Request, Response,
|
Depot, FlowCtrl, Handler, Request, Response,
|
||||||
};
|
};
|
||||||
pub use {captcha_gen::*, finder::*, storage::*};
|
pub use {captcha_gen::*, finder::*, storage::*};
|
||||||
|
|
||||||
// Exports from other crates
|
|
||||||
pub use captcha::{CaptchaName, Difficulty as CaptchaDifficulty};
|
|
||||||
|
|
||||||
/// Key used to insert the captcha state into the depot
|
/// Key used to insert the captcha state into the depot
|
||||||
pub const CAPTCHA_STATE_KEY: &str = "::salvo_captcha::captcha_state";
|
pub const CAPTCHA_STATE_KEY: &str = "::salvo_captcha::captcha_state";
|
||||||
|
|
||||||
/// Captcha struct, contains the token and answer.
|
/// Captcha struct, contains the token and answer.
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
pub struct Captcha<S, F>
|
pub struct Captcha<S, F>
|
||||||
where
|
where
|
||||||
S: CaptchaStorage,
|
S: CaptchaStorage,
|
||||||
|
@ -41,7 +39,7 @@ where
|
||||||
/// The captcha finder, used to find the captcha token and answer from the request.
|
/// The captcha finder, used to find the captcha token and answer from the request.
|
||||||
finder: F,
|
finder: F,
|
||||||
/// The storage of the captcha, used to store and get the captcha token and answer.
|
/// The storage of the captcha, used to store and get the captcha token and answer.
|
||||||
storage: S,
|
storage: Arc<S>,
|
||||||
/// The skipper of the captcha, used to skip the captcha check.
|
/// The skipper of the captcha, used to skip the captcha check.
|
||||||
skipper: Box<dyn Skipper>,
|
skipper: Box<dyn Skipper>,
|
||||||
}
|
}
|
||||||
|
@ -66,30 +64,101 @@ pub enum CaptchaState {
|
||||||
StorageError,
|
StorageError,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The [`Captcha`] builder
|
||||||
|
pub struct CaptchaBuilder<S, F>
|
||||||
|
where
|
||||||
|
S: CaptchaStorage,
|
||||||
|
F: CaptchaFinder,
|
||||||
|
{
|
||||||
|
storage: S,
|
||||||
|
finder: F,
|
||||||
|
captcha_expired_after: Duration,
|
||||||
|
clean_interval: Duration,
|
||||||
|
skipper: Box<dyn Skipper>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, F> CaptchaBuilder<Arc<S>, F>
|
||||||
|
where
|
||||||
|
S: CaptchaStorage,
|
||||||
|
F: CaptchaFinder,
|
||||||
|
{
|
||||||
|
/// Create a new [`CaptchaBuilder`] with the given storage and finder.
|
||||||
|
pub fn new(storage: Arc<S>, finder: F) -> Self {
|
||||||
|
CaptchaBuilder {
|
||||||
|
storage,
|
||||||
|
finder,
|
||||||
|
captcha_expired_after: Duration::from_secs(60 * 5),
|
||||||
|
clean_interval: Duration::from_secs(60),
|
||||||
|
skipper: Box::new(none_skipper),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the duration after which the captcha will be expired, default is 5 minutes.
|
||||||
|
///
|
||||||
|
/// After the captcha is expired, it will be removed from the storage, and the user needs to get a new captcha.
|
||||||
|
pub fn expired_after(mut self, expired_after: impl Into<Duration>) -> Self {
|
||||||
|
self.captcha_expired_after = expired_after.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the interval to clean the expired captcha, default is 1 minute.
|
||||||
|
///
|
||||||
|
/// The expired captcha will be removed from the storage every interval.
|
||||||
|
pub fn clean_interval(mut self, interval: impl Into<Duration>) -> Self {
|
||||||
|
self.clean_interval = interval.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the skipper of the captcha, default without skipper.
|
||||||
|
///
|
||||||
|
/// The skipper is used to skip the captcha check, for example, you can skip the captcha check for the admin user.
|
||||||
|
pub fn skipper(mut self, skipper: impl Skipper) -> Self {
|
||||||
|
self.skipper = Box::new(skipper);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the [`Captcha`] with the given configuration.
|
||||||
|
pub fn build(self) -> Captcha<S, F> {
|
||||||
|
Captcha::new(
|
||||||
|
self.storage,
|
||||||
|
self.finder,
|
||||||
|
self.captcha_expired_after,
|
||||||
|
self.clean_interval,
|
||||||
|
self.skipper,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<S, F> Captcha<S, F>
|
impl<S, F> Captcha<S, F>
|
||||||
where
|
where
|
||||||
S: CaptchaStorage,
|
S: CaptchaStorage,
|
||||||
F: CaptchaFinder,
|
F: CaptchaFinder,
|
||||||
{
|
{
|
||||||
/// Create a new Captcha
|
/// Create a new Captcha
|
||||||
pub fn new(storage: S, finder: F) -> Self {
|
fn new(
|
||||||
|
storage: Arc<S>,
|
||||||
|
finder: F,
|
||||||
|
captcha_expired_after: Duration,
|
||||||
|
clean_interval: Duration,
|
||||||
|
skipper: Box<dyn Skipper>,
|
||||||
|
) -> Self {
|
||||||
|
let task_storage = Arc::clone(&storage);
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
loop {
|
||||||
|
if let Err(err) = task_storage.clear_expired(captcha_expired_after).await {
|
||||||
|
log::error!("Captcha storage error: {err}")
|
||||||
|
}
|
||||||
|
tokio::time::sleep(clean_interval).await;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
finder,
|
finder,
|
||||||
storage,
|
storage,
|
||||||
skipper: Box::new(none_skipper),
|
skipper,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the captcha storage
|
|
||||||
pub fn storage(&self) -> &S {
|
|
||||||
&self.storage
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the captcha skipper, the skipper will be used to check if the captcha check should be skipped.
|
|
||||||
pub fn skipper(mut self, skipper: impl Skipper) -> Self {
|
|
||||||
self.skipper = Box::new(skipper);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The captcha extension of the depot.
|
/// The captcha extension of the depot.
|
||||||
|
@ -109,7 +178,7 @@ impl CaptchaDepotExt for Depot {
|
||||||
impl<S, F> Handler for Captcha<S, F>
|
impl<S, F> Handler for Captcha<S, F>
|
||||||
where
|
where
|
||||||
S: CaptchaStorage,
|
S: CaptchaStorage,
|
||||||
F: CaptchaFinder + 'static, // why?
|
F: CaptchaFinder,
|
||||||
{
|
{
|
||||||
async fn handle(
|
async fn handle(
|
||||||
&self,
|
&self,
|
||||||
|
@ -118,7 +187,7 @@ where
|
||||||
_: &mut Response,
|
_: &mut Response,
|
||||||
_: &mut FlowCtrl,
|
_: &mut FlowCtrl,
|
||||||
) {
|
) {
|
||||||
if self.skipper.skipped(req, depot) {
|
if self.skipper.as_ref().skipped(req, depot) {
|
||||||
log::info!("Captcha check is skipped");
|
log::info!("Captcha check is skipped");
|
||||||
depot.insert(CAPTCHA_STATE_KEY, CaptchaState::Skipped);
|
depot.insert(CAPTCHA_STATE_KEY, CaptchaState::Skipped);
|
||||||
return;
|
return;
|
||||||
|
|
Loading…
Reference in a new issue