use std::net::SocketAddr; use axum::{ response::Redirect, routing::{get, post}, Router, }; use http::StatusCode; use maud::{html, Markup}; use tower_surf::{Surf, Token}; #[tokio::main] async fn main() { let app = Router::new() .route("/", get(root)) .route("/submit", post(submit)) .route("/login", get(login)) .route("/logout", get(logout)) .layer(Surf::new("secret-key").secure(false)); let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); let listener = tokio::net::TcpListener::bind(&addr).await.unwrap(); axum::serve(listener, app.into_make_service()) .await .unwrap(); } async fn root(token: Token) -> Result { let token = token.get().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; Ok(html! { link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"; script src="https://unpkg.com/htmx.org@2.0.2" {} main class="container" { nav { ul { li { a href="/login" { "Login" } } li { a href="/logout" { "Logout" } } } } p { mark { "Open the Network tab in your dev console." } } p { small { kbd { (token) } } } div class="grid" { div { form hx-post="/submit" hx-swap="none" "hx-on::config-request"={"event.detail.headers['X-CSRF-Token'] = \"" (token) "\""} { label for="hotdogs" { "How do you like your hotdogs?" } select name="hotdogs" value="ketchup" { option value="ketchup" { "Ketchup" } option value="ketchup-again" { "Ketchup" } option value="more-ketchup" { "More ketchup" } } button type="submit" { "Submit with token" } } } div { form hx-post="/submit" { label for="hotdogs" { "How do you like your hotdogs?" } select name="hotdogs" value="ketchup" { option value="ketchup" { "Ketchup" } option value="still-ketchup" { "Still ketchup" } option value="always-ketchup" { "It'll always be ketchup!" } } button type="submit" { "Submit without token" } } } } } }) } async fn submit() -> (StatusCode, &'static str) { (StatusCode::OK, "Success!") } async fn login(token: Token) -> Result { token .set("secret-session-identifier") .map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))?; Ok(Redirect::to("/")) } async fn logout(token: Token) -> Redirect { token.reset(); Redirect::to("/") }