feat: init
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
use crate::session::Session;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::error::AppError;
|
||||
use crate::service::AuthService;
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug, utoipa::ToSchema)]
|
||||
pub struct CaptchaQuery {
|
||||
pub w: u32,
|
||||
pub h: u32,
|
||||
pub dark: bool,
|
||||
pub rsa: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, utoipa::ToSchema)]
|
||||
pub struct CaptchaResponse {
|
||||
pub base64: String,
|
||||
pub rsa: Option<super::rsa::RsaResponse>,
|
||||
pub req: CaptchaQuery,
|
||||
}
|
||||
|
||||
impl AuthService {
|
||||
const CAPTCHA_KEY: &'static str = "captcha";
|
||||
const CAPTCHA_LENGTH: usize = 4;
|
||||
const CAPTCHA_MIN_WIDTH: u32 = 80;
|
||||
const CAPTCHA_MAX_WIDTH: u32 = 400;
|
||||
const CAPTCHA_MIN_HEIGHT: u32 = 30;
|
||||
const CAPTCHA_MAX_HEIGHT: u32 = 200;
|
||||
|
||||
pub async fn auth_captcha(
|
||||
&self,
|
||||
context: &Session,
|
||||
query: CaptchaQuery,
|
||||
) -> Result<CaptchaResponse, AppError> {
|
||||
let CaptchaQuery { w, h, dark, rsa } = query;
|
||||
if !(Self::CAPTCHA_MIN_WIDTH..=Self::CAPTCHA_MAX_WIDTH).contains(&w)
|
||||
|| !(Self::CAPTCHA_MIN_HEIGHT..=Self::CAPTCHA_MAX_HEIGHT).contains(&h)
|
||||
{
|
||||
return Err(AppError::BadRequest("invalid captcha size".into()));
|
||||
}
|
||||
|
||||
let captcha = captcha_rs::CaptchaBuilder::new()
|
||||
.width(w)
|
||||
.height(h)
|
||||
.dark_mode(dark)
|
||||
.length(Self::CAPTCHA_LENGTH)
|
||||
.build();
|
||||
|
||||
let base64 = captcha.to_base64();
|
||||
let text = captcha.text;
|
||||
context
|
||||
.insert(Self::CAPTCHA_KEY, text)
|
||||
.map_err(|_| AppError::InternalServerError("session insert failed".into()))?;
|
||||
|
||||
Ok(CaptchaResponse {
|
||||
base64,
|
||||
rsa: if rsa {
|
||||
Some(self.auth_rsa(context).await?)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
req: CaptchaQuery { w, h, dark, rsa },
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn auth_check_captcha(
|
||||
&self,
|
||||
context: &Session,
|
||||
captcha: String,
|
||||
) -> Result<(), AppError> {
|
||||
let text = context
|
||||
.get::<String>(Self::CAPTCHA_KEY)
|
||||
.map_err(|_| AppError::CaptchaError)?
|
||||
.ok_or(AppError::CaptchaError)?;
|
||||
if !constant_time_eq(&text.to_lowercase(), &captcha.to_lowercase()) {
|
||||
context.remove(Self::CAPTCHA_KEY);
|
||||
tracing::warn!("Captcha verification failed");
|
||||
return Err(AppError::CaptchaError);
|
||||
}
|
||||
context.remove(Self::CAPTCHA_KEY);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
use crate::service::util::constant_time_eq;
|
||||
Reference in New Issue
Block a user