use argon2::{ password_hash::{PasswordHash, PasswordVerifier}, Argon2, }; use axum::{ extract::State, http::StatusCode, response::{IntoResponse, Redirect, Response}, Form, }; use sqlx::SqlitePool; use crate::{ templates::{LoginGet, LoginPost, LogoutGet, LogoutPost}, util::form_decode, AuthContext, User, }; //-************************************************************************ // Constants //-************************************************************************ //-************************************************************************ // Login error and success types //-************************************************************************ #[Error] pub struct LoginError(#[from] LoginErrorKind); #[Error] #[non_exhaustive] pub enum LoginErrorKind { Internal, BadPassword, BadUsername, Unknown, } impl IntoResponse for LoginError { fn into_response(self) -> Response { match self.0 { LoginErrorKind::Unknown | LoginErrorKind::Internal => ( StatusCode::INTERNAL_SERVER_ERROR, "An unknown error occurred; you cursed, brah?", ) .into_response(), _ => (StatusCode::OK, format!("{self}")).into_response(), } } } //-************************************************************************ // Login handlers //-************************************************************************ /// Handle login queries #[axum::debug_handler] pub async fn post_login( mut auth: AuthContext, State(pool): State, Form(login): Form, ) -> Result { let username = form_decode(&login.username, LoginErrorKind::BadUsername)?; let username = username.trim(); let pw = form_decode(&login.password, LoginErrorKind::BadPassword)?; let pw = pw.trim(); let user = User::try_get(username, &pool) .await .map_err(|_| LoginErrorKind::Unknown)?; let verifier = Argon2::default(); let hash = PasswordHash::new(&user.pwhash).map_err(|_| LoginErrorKind::Internal)?; match verifier.verify_password(pw.as_bytes(), &hash) { Ok(_) => { // log them in and set a session cookie auth.login(&user) .await .map_err(|_| LoginErrorKind::Internal)?; Ok(Redirect::temporary("/")) } _ => Err(LoginErrorKind::BadPassword.into()), } } pub async fn get_login() -> impl IntoResponse { LoginGet::default() } pub async fn get_logout() -> impl IntoResponse { LogoutGet } pub async fn post_logout(mut auth: AuthContext) -> impl IntoResponse { if auth.current_user.is_some() { auth.logout().await; } LogoutPost }