use super::Error;
use axum::extract::Request;
use axum::extract::State;
use axum::middleware::Next;
use axum::response::Response;
use scopeguard::defer;
use std::time::Duration;
use tibba_cache::RedisCache;
use tibba_state::CTX;
use tokio::time::sleep;
use tracing::debug;
type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Debug, Clone, Default)]
pub struct WaitParams {
pub wait: Duration,
pub only_error_occurred: bool,
}
impl WaitParams {
pub fn new(ms: u64) -> Self {
Self {
wait: Duration::from_millis(ms),
..Default::default()
}
}
}
pub async fn wait(State(params): State<WaitParams>, req: Request, next: Next) -> Response {
debug!(category = "middleware", "--> wait");
defer!(debug!(category = "middleware", "<-- wait"););
let res = next.run(req).await;
if params.only_error_occurred && res.status().as_u16() < 400 {
return res;
}
let elapsed = CTX.get().elapsed();
let remaining_wait = params.wait.saturating_sub(elapsed);
if remaining_wait.as_millis() >= 10 {
sleep(remaining_wait).await
}
res
}
pub async fn validate_captcha(
State((magic_code, cache)): State<(String, &'static RedisCache)>,
req: Request,
next: Next,
) -> Result<Response, tibba_error::Error> {
let category = "captcha";
let value = req
.headers()
.get("X-Captcha")
.ok_or(Error::Common {
message: "captcha is required".to_string(),
category: category.to_string(),
})?
.to_str()
.map_err(|err| Error::Common {
message: err.to_string(),
category: category.to_string(),
})?;
let (key, user_code) = value.split_once(':').ok_or_else(|| Error::Common {
message: "captcha parameter is invalid, expect 'key:code'".to_string(),
category: category.to_string(),
})?;
let is_mock = !magic_code.is_empty() && user_code == magic_code;
if !is_mock {
let code: Option<String> = cache.get_del(key).await?;
let Some(code) = code else {
return Err(Error::Common {
message: "captcha is expired".to_string(),
category: category.to_string(),
}
.into());
};
if code != user_code {
return Err(Error::Common {
message: "captcha is invalid".to_string(),
category: category.to_string(),
}
.into());
}
}
let res = next.run(req).await;
Ok(res)
}