salvo-captcha/examples/simple_login.rs

173 lines
5.7 KiB
Rust
Raw Permalink Normal View History

2024-01-29 21:24:35 +01:00
// Example of using the `salvo_captcha`, this example is a simple login page with a captcha.
// The page will be in <http://127.0.0.1:5800>
// 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;
2024-01-29 21:24:35 +01:00
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,
);
const SIMPLE_GENERATOR: SimpleGenerator =
SimpleGenerator::new(CaptchaName::Normal, CaptchaDifficulty::Medium);
2024-01-29 21:24:35 +01:00
#[handler]
async fn index(res: &mut Response, depot: &mut Depot) {
2024-01-29 21:24:35 +01:00
// Get the captcha from the depot
let captcha_storage = depot.obtain::<Arc<MemoryStorage>>().unwrap();
2024-01-29 21:24:35 +01:00
// Create a new captcha
let Ok((token, image)) = captcha_storage.new_captcha(SIMPLE_GENERATOR).await else {
res.status_code(StatusCode::INTERNAL_SERVER_ERROR);
res.render(Text::Html(
"<html><body><h1>Server Error 500</h1></body></html>",
));
return;
};
2024-01-29 21:24:35 +01:00
// 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();
2024-01-29 21:24:35 +01:00
// Not important, just for demo
let Some(username) = req.form::<String>("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_storage = Arc::new(MemoryStorage::new());
let captcha_middleware =
CaptchaBuilder::new(Arc::clone(&captcha_storage), CaptchaFormFinder::new())
// Skip the captcha if the request path is /skipped
.skipper(|req: &mut Request, _: &Depot| req.uri().path() == "/skipped")
.case_insensitive()
.build();
2024-01-29 21:24:35 +01:00
let router = Router::new()
.hoop(affix::inject(captcha_storage))
2024-01-29 21:24:35 +01:00
.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;
Server::new(acceptor).serve(router).await;
}
fn index_page(captcha_image: String, captcha_token: String) -> String {
format!(
r#"
<html>
<head>
<title>Salvo Captcha Example</title>
</head>
<style>
body {{
text-align: center;
}}
.captcha-img {{
width: 220px;
height: 110px;
border: 5px solid black;
padding: 5px;
margin: 5px;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
border-radius: 5px;
}}
input {{
margin: 5px;
padding: 5px;
border: 1px solid black;
border-radius: 3px;
}}
</style>
<body>
<h1>Salvo Captcha Example</h1>
<h2>Sign In</h2>
<img class="captcha-img" src="data:image/png;base64,{captcha_image}" />
<form action="/auth" method="post">
<input type="hidden" name="captcha_token" value="{captcha_token}" />
<input type="text" name="username" placeholder="Username" />
<br/>
<input type="password" name="password" placeholder="Password" />
<br/>
<input type="text" name="captcha_answer" placeholder="Captcha Answer" />
<br/>
<input type="submit" value="Submit" />
</form>
<srong>Or you can skip the captcha</strong>
<form action="/skipped" method="post">
<input type="text" name="username" placeholder="Username" />
<br/>
<input type="password" name="password" placeholder="Password" />
<br/>
<input type="submit" value="Skip Captcha" />
</form>
<a href="https://git.4rs.nl/awiteb/salvo-captcha">Source Code</a>
2024-01-29 21:24:35 +01:00
</body>
</html>
"#
)
}
fn captcha_result_page(captcha_result: String) -> String {
format!(
r#"
<html>
<head>
<title>Salvo Captcha Example</title>
</head>
<style>
body {{
text-align: center;
}}
</style>
<body>
<h1>Salvo Captcha Example</h1>
<h2>Result page</h2>
2024-01-29 21:24:35 +01:00
<strong>{captcha_result}</strong>
<br/>
<a href="/">Go Back</a>
</body>
</html>
"#
)
}