2025-11-27 13:45:21 -06:00
|
|
|
use axum::extract::State;
|
|
|
|
|
use axum::http::HeaderMap;
|
|
|
|
|
use axum::{
|
|
|
|
|
Form, Router,
|
|
|
|
|
extract::Query,
|
|
|
|
|
response::{IntoResponse, Redirect},
|
|
|
|
|
routing::{get, post},
|
|
|
|
|
};
|
2025-12-01 15:23:56 -06:00
|
|
|
use cache_bust::asset;
|
2025-11-27 13:45:21 -06:00
|
|
|
use maud::{DOCTYPE, html};
|
|
|
|
|
use serde::Deserialize;
|
|
|
|
|
|
|
|
|
|
use crate::models::user::{AuthSession, Credentials};
|
|
|
|
|
use crate::{AppError, AppState};
|
|
|
|
|
|
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
|
|
|
struct NextUrl {
|
|
|
|
|
next: Option<String>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn router() -> Router<AppState> {
|
|
|
|
|
Router::new()
|
|
|
|
|
.route("/login", post(self::post::login))
|
|
|
|
|
.route("/login", get(self::get::login))
|
|
|
|
|
.route("/logout", get(self::get::logout))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mod post {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[axum::debug_handler]
|
|
|
|
|
pub async fn login(
|
|
|
|
|
mut auth_session: AuthSession,
|
|
|
|
|
State(mut state): State<AppState>,
|
|
|
|
|
Query(NextUrl { next }): Query<NextUrl>,
|
|
|
|
|
Form(creds): Form<Credentials>,
|
|
|
|
|
) -> Result<impl IntoResponse, AppError> {
|
|
|
|
|
let mut headers = HeaderMap::new();
|
|
|
|
|
|
|
|
|
|
let user = match auth_session.authenticate(creds.clone()).await {
|
|
|
|
|
Ok(Some(user)) => user,
|
|
|
|
|
Ok(None) => {
|
|
|
|
|
return Err(AppError(anyhow::Error::msg(
|
|
|
|
|
"Username and password do not match",
|
|
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
Err(_) => return Err(AppError(anyhow::Error::msg("Internal server error"))),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if auth_session.login(&user).await.is_err() {
|
|
|
|
|
return Err(AppError(anyhow::Error::msg("Server error during login")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state.init(&user).await?;
|
|
|
|
|
|
|
|
|
|
if let Some(url) = next {
|
|
|
|
|
headers.insert("HX-Redirect", url.parse()?);
|
|
|
|
|
} else {
|
|
|
|
|
headers.insert("HX-Redirect", "/".parse()?);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok((headers, "ok"))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mod get {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
pub async fn login(Query(NextUrl { next }): Query<NextUrl>) -> impl IntoResponse {
|
|
|
|
|
let post_url = format!(
|
|
|
|
|
"/login{}",
|
|
|
|
|
next.map_or("".to_string(), |n| format!("?next={}", n))
|
|
|
|
|
);
|
|
|
|
|
html! {
|
|
|
|
|
(DOCTYPE)
|
|
|
|
|
html {
|
|
|
|
|
head {
|
|
|
|
|
meta name="viewport" content="width=device-width";
|
|
|
|
|
script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.7/dist/htmx.min.js" {}
|
|
|
|
|
script src="https://cdn.jsdelivr.net/npm/htmx-ext-response-targets@2.0.4" integrity="sha384-T41oglUPvXLGBVyRdZsVRxNWnOOqCynaPubjUVjxhsjFTKrFJGEMm3/0KGmNQ+Pg" crossorigin="anonymous" {}
|
|
|
|
|
script src="https://cdn.jsdelivr.net/npm/alpinejs@3.15.0/dist/cdn.min.js" defer {}
|
2025-12-01 15:23:56 -06:00
|
|
|
link rel="stylesheet" type="text/css" href=(format!("/static/{}", asset!("index.css")));
|
|
|
|
|
link rel="stylesheet" type="text/css" href=(format!("/static/{}", asset!("login.css")));
|
2025-11-27 13:45:21 -06:00
|
|
|
title { "Mascarpone" }
|
|
|
|
|
}
|
|
|
|
|
body hx-ext="response-targets" {
|
|
|
|
|
h1 { "Mascarpone" }
|
|
|
|
|
form hx-post=(post_url) hx-target-error="#error" x-data="{ user: '', pass: '' }" {
|
|
|
|
|
label for="username" { "Username" }
|
|
|
|
|
input name="username" #username autofocus x-model="user";
|
|
|
|
|
label for="password" { "Password" }
|
|
|
|
|
input name="password" #password type="password" x-model="pass";
|
|
|
|
|
|
|
|
|
|
input type="submit" value="login" x-bind:disabled="!(user.length && pass.length)";
|
|
|
|
|
#error {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn logout(
|
|
|
|
|
mut auth_session: AuthSession,
|
|
|
|
|
State(mut state): State<AppState>,
|
|
|
|
|
) -> Result<impl IntoResponse, AppError> {
|
|
|
|
|
let user = auth_session.user.clone();
|
|
|
|
|
auth_session.logout().await?;
|
|
|
|
|
|
|
|
|
|
if let Some(user) = user {
|
|
|
|
|
state.remove(&user);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(Redirect::to("/login").into_response())
|
|
|
|
|
}
|
|
|
|
|
}
|