2023-05-29 00:55:16 +00:00
|
|
|
use argon2::{
|
|
|
|
password_hash::{PasswordHash, PasswordVerifier},
|
|
|
|
Argon2,
|
|
|
|
};
|
2023-05-28 20:44:50 +00:00
|
|
|
use axum::{
|
|
|
|
extract::State,
|
|
|
|
http::StatusCode,
|
2023-05-29 00:55:16 +00:00
|
|
|
response::{IntoResponse, Redirect, Response},
|
|
|
|
Form,
|
2023-05-28 20:44:50 +00:00
|
|
|
};
|
|
|
|
use sqlx::SqlitePool;
|
|
|
|
|
2023-05-29 00:55:16 +00:00
|
|
|
use crate::{
|
|
|
|
templates::{LoginGet, LoginPost},
|
|
|
|
util::form_decode,
|
|
|
|
AuthContext, User,
|
|
|
|
};
|
2023-05-28 20:44:50 +00:00
|
|
|
|
2023-05-29 00:55:16 +00:00
|
|
|
//-************************************************************************
|
|
|
|
// Constants
|
|
|
|
//-************************************************************************
|
2023-05-28 20:44:50 +00:00
|
|
|
|
2023-05-29 00:55:16 +00:00
|
|
|
const LAST_SEEN_QUERY: &str = "update witches set last_seen = (select unixepoch()) where id = $1";
|
2023-05-28 20:44:50 +00:00
|
|
|
|
|
|
|
//-************************************************************************
|
|
|
|
// Login error and success types
|
|
|
|
//-************************************************************************
|
|
|
|
|
|
|
|
#[Error]
|
|
|
|
pub struct LoginError(#[from] LoginErrorKind);
|
|
|
|
|
|
|
|
#[Error]
|
|
|
|
#[non_exhaustive]
|
|
|
|
pub enum LoginErrorKind {
|
2023-05-29 00:55:16 +00:00
|
|
|
Internal,
|
2023-05-28 20:44:50 +00:00
|
|
|
BadPassword,
|
2023-05-29 00:55:16 +00:00
|
|
|
BadUsername,
|
2023-05-28 20:44:50 +00:00
|
|
|
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<SqlitePool>,
|
2023-05-29 00:55:16 +00:00
|
|
|
Form(login): Form<LoginPost>,
|
|
|
|
) -> Result<impl IntoResponse, LoginError> {
|
|
|
|
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()),
|
|
|
|
}
|
2023-05-28 20:44:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn get_login() -> impl IntoResponse {
|
|
|
|
LoginGet::default()
|
|
|
|
}
|
2023-05-29 18:13:12 +00:00
|
|
|
|
|
|
|
pub async fn get_logout() -> impl IntoResponse {
|
|
|
|
todo!()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn post_logout() -> impl IntoResponse {
|
|
|
|
todo!()
|
|
|
|
}
|