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}, util::form_decode, AuthContext, User, }; //-************************************************************************ // Constants //-************************************************************************ const LAST_SEEN_QUERY: &str = "update witches set last_seen = (select unixepoch()) where id = $1"; //-************************************************************************ // 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 => ( StatusCode::INTERNAL_SERVER_ERROR, "An unknown error occurred; you cursed, brah?", ) .into_response(), _ => (StatusCode::BAD_REQUEST, 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::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)?; // update last_seen; maybe this is ok to fail? sqlx::query(LAST_SEEN_QUERY) .bind(user.id) .execute(&pool) .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 { todo!() } pub async fn post_logout() -> impl IntoResponse { todo!() }