//! Handlers triggered by clicking email links, plus the forms that initiate them. mod account; mod links; mod password; use axum::{routing::get, Router}; use tower_governor::GovernorLayer; use crate::{constants, helpers::rate_limiter_ms, AppState}; /// Register email action routes. /// /// Every route here is unauthenticated (they're reached from email links or the /// pre-login forms that trigger them), so the whole router carries one per-IP /// auth rate limiter applied via `.layer`. Previously only `/forgot-password` /// was capped; `/reset-password`, `/login-link`, `/verify-email`, /// `/confirm-delete`, and `/unsubscribe` were uncapped, leaving a /// DoS/email-amplification surface (Run #11 Security MINOR). The tokens are /// 256-bit CSPRNG / HMAC so this was never a brute-force risk — the cap closes /// the abuse/amplification angle. Burst 5 + 500ms replenish comfortably covers /// the legitimate forgot -> reset -> login click sequence (~3 requests). pub fn email_action_routes() -> Router { let auth_rate_limit = rate_limiter_ms(constants::AUTH_RATE_LIMIT_MS, constants::AUTH_RATE_LIMIT_BURST); Router::new() .route( "/forgot-password", get(password::forgot_password_page).post(password::forgot_password_handler), ) .route( "/reset-password", get(password::reset_password_page).post(password::reset_password_handler), ) .route("/verify-email", get(links::verify_email_handler)) .route("/login-link", get(links::login_link_handler)) .route( "/confirm-delete", get(account::confirm_delete_page).post(account::confirm_delete_handler), ) .route( "/unsubscribe", get(account::unsubscribe_page).post(account::unsubscribe_handler), ) .layer(GovernorLayer { config: auth_rate_limit, }) }