// Example of using the `salvo_captcha`, this example is a simple login page with a captcha. // The page will be in // You can see a video of this example here: // // Run the example with `cargo run --example simple_login --features cacache-storage` use std::{sync::Arc, time::Duration}; use base64::{engine::GeneralPurpose, Engine}; use salvo::prelude::*; use salvo_captcha::*; // To convert the image to base64, to show it in the browser const BASE_64_ENGINE: GeneralPurpose = GeneralPurpose::new( &base64::alphabet::STANDARD, base64::engine::general_purpose::PAD, ); #[handler] async fn index(res: &mut Response, depot: &mut Depot) { // Get the captcha from the depot let captcha_storage = depot.obtain::>().unwrap(); // Create a new captcha let (token, image) = captcha_storage .as_ref() .new_captcha(CaptchaName::Mila, CaptchaDifficulty::Medium) .await .expect("Failed to save captcha") .expect("Failed to create captcha"); // Convert the image to base64 let image = BASE_64_ENGINE.encode(image); // Set the response content res.render(Text::Html(index_page(image, token))) } #[handler] async fn auth(req: &mut Request, res: &mut Response, depot: &mut Depot) { // Get the captcha state from the depot, where we can know if the captcha is passed let captcha_state = depot.get_captcha_state(); // Not important, just for demo let Some(username) = req.form::("username").await else { res.status_code(StatusCode::BAD_REQUEST); return res.render(Text::Html("Invalid form submission")); }; // Handle the captcha state, that's all let content = match captcha_state { CaptchaState::Passed => { format!("Welcome, {username}!") } CaptchaState::AnswerNotFound => "Captcha answer not found".to_string(), CaptchaState::TokenNotFound => "Captcha token not found".to_string(), CaptchaState::WrongAnswer => "Wrong captcha answer".to_string(), CaptchaState::WrongToken => "Wrong captcha token".to_string(), CaptchaState::Skipped => "Captcha skipped".to_string(), CaptchaState::StorageError => "Captcha storage error".to_string(), }; res.render(Text::Html(captcha_result_page(content))) } #[tokio::main] async fn main() { let captcha_middleware = Captcha::new( CacacheStorage::new("./captcha-cache"), CaptchaFormFinder::new(), ) .skipper(|req: &mut Request, _: &Depot| { // Skip the captcha if the request path is /skipped req.uri().path() == "/skipped" }); let captcha_storage = Arc::new(captcha_middleware.storage().clone()); // Set up a task to clean the expired captcha, just call the `clear_expired` function from the storage let captcha_cleaner = tokio::spawn({ let cleanner_storage = captcha_storage.clone(); async move { let captcha_expired_after = Duration::from_secs(60 * 5); let clean_interval = Duration::from_secs(60); // Just this loop, to clean the expired captcha every 60 seconds, each captcha will be expired after 5 minutes loop { cleanner_storage .clear_expired(captcha_expired_after) .await .ok(); // You should log this error tokio::time::sleep(clean_interval).await; } } }); let router = Router::new() .hoop(affix::inject(captcha_storage.clone())) .push(Router::with_path("/").get(index)) .push( Router::new() .hoop(captcha_middleware) .push(Router::with_path("/auth").post(auth)) .push(Router::with_path("/skipped").post(auth)), ); let acceptor = TcpListener::new(("127.0.0.1", 5800)).bind().await; println!("Starting server on http://127.0.0.1:5800"); Server::new(acceptor).serve(router).await; captcha_cleaner.await.ok(); } fn index_page(captcha_image: String, captcha_token: String) -> String { format!( r#" Salvo Captcha Example

Salvo Captcha Example

Sign In




Or you can skip the captcha


Source Code "# ) } fn captcha_result_page(captcha_result: String) -> String { format!( r#" Salvo Captcha Example

Salvo Captcha Example

Result page

{captcha_result}
Go Back "# ) }