use crate::AppService; use crate::auth::rsa::RsaResponse; use crate::error::AppError; use session::Session; use utoipa::{IntoParams, ToSchema}; #[derive(serde::Deserialize, serde::Serialize, Clone, Debug, ToSchema, IntoParams)] pub struct CaptchaQuery { pub w: u32, pub h: u32, pub dark: bool, pub rsa: bool, } #[derive(serde::Serialize, ToSchema)] pub struct CaptchaResponse { pub base64: String, pub rsa: Option, pub req: CaptchaQuery, } impl AppService { const CAPTCHA_KEY: &'static str = "captcha"; const CAPTCHA_LENGTH: usize = 4; pub async fn auth_captcha( &self, context: &Session, query: CaptchaQuery, ) -> Result { let CaptchaQuery { w, h, dark, rsa } = query; 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).ok(); 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::(Self::CAPTCHA_KEY) .map_err(|_| AppError::CaptchaError)? .ok_or(AppError::CaptchaError)?; if text.to_lowercase() != captcha.to_lowercase() { context.remove(Self::CAPTCHA_KEY); slog::warn!(self.logs, "Captcha verification failed"; "ip" => context.ip_address()); return Err(AppError::CaptchaError); } context.remove(Self::CAPTCHA_KEY); Ok(()) } }