use askama::Template;
use axum::extract::State;
use axum::http::{Request, StatusCode};
use axum::middleware::Next;
use axum::response::{Html, IntoResponse, Redirect, Response};
use axum_extra::extract::cookie::{Cookie, CookieJar};
use crate::state::AppState;
const COOKIE_NAME: &str = "perspt_session";
#[derive(askama::Template)]
#[template(path = "login.html")]
struct LoginTemplate {
error: Option<String>,
}
pub async fn login_page() -> impl IntoResponse {
let tmpl = LoginTemplate { error: None };
Html(tmpl.render().unwrap_or_default())
}
pub async fn login_handler(
State(state): State<AppState>,
jar: CookieJar,
axum::Form(form): axum::Form<LoginForm>,
) -> impl IntoResponse {
let Some(ref expected) = state.password else {
return (jar, Redirect::to("/")).into_response();
};
if form.password != *expected {
let tmpl = LoginTemplate {
error: Some("Invalid password".to_string()),
};
return (
StatusCode::UNAUTHORIZED,
Html(tmpl.render().unwrap_or_default()),
)
.into_response();
}
let token: String = {
use rand::Rng;
let mut rng = rand::thread_rng();
(0..32)
.map(|_| rng.sample(rand::distributions::Alphanumeric) as char)
.collect()
};
*state.session_token.lock().await = Some(token.clone());
let cookie = Cookie::build((COOKIE_NAME, token))
.path("/")
.http_only(true)
.same_site(axum_extra::extract::cookie::SameSite::Lax)
.secure(!state.is_localhost);
(jar.add(cookie), Redirect::to("/")).into_response()
}
#[derive(serde::Deserialize)]
pub struct LoginForm {
password: String,
}
pub async fn auth_middleware(
State(state): State<AppState>,
jar: CookieJar,
request: Request<axum::body::Body>,
next: Next,
) -> Response {
if state.password.is_none() {
return next.run(request).await;
}
if let Some(cookie) = jar.get(COOKIE_NAME) {
let stored = state.session_token.lock().await;
if let Some(ref token) = *stored {
if cookie.value() == token {
return next.run(request).await;
}
}
}
Redirect::to("/login").into_response()
}