use crate::{ authentication::{AuthError, Credentials, validate_credentials}, routes::error_chain_fmt, session_state::TypedSession, startup::AppState, templates::MessageTemplate, }; use askama::Template; use axum::{ Form, Json, extract::State, http::HeaderMap, response::{Html, IntoResponse, Response}, }; use axum::{http::StatusCode, response::Redirect}; use secrecy::SecretString; #[derive(thiserror::Error)] pub enum LoginError { #[error("Something went wrong.")] UnexpectedError(#[from] anyhow::Error), #[error("Authentication failed.")] AuthError(#[source] anyhow::Error), } impl std::fmt::Debug for LoginError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { error_chain_fmt(self, f) } } impl IntoResponse for LoginError { fn into_response(self) -> Response { #[derive(serde::Serialize)] struct ErrorResponse<'a> { message: &'a str, } tracing::error!("{:?}", self); match &self { LoginError::UnexpectedError(_) => ( StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { message: "An internal server error occured.", }), ) .into_response(), LoginError::AuthError(e) => { let template = MessageTemplate::Error { message: e.to_string(), }; Html(template.render().unwrap()).into_response() } } } } #[derive(Template)] #[template(path = "../templates/login.html")] struct LoginTemplate; #[derive(serde::Deserialize)] pub struct LoginFormData { username: String, password: SecretString, } pub async fn get_login(session: TypedSession) -> Result { if session .get_user_id() .await .map_err(|e| LoginError::UnexpectedError(e.into()))? .is_some() { Ok(Redirect::to("/admin/dashboard").into_response()) } else { Ok(Html(LoginTemplate.render().unwrap()).into_response()) } } pub async fn post_login( session: TypedSession, State(AppState { connection_pool, .. }): State, Form(form): Form, ) -> Result { let credentials = Credentials { username: form.username.clone(), password: form.password, }; tracing::Span::current().record("username", tracing::field::display(&credentials.username)); match validate_credentials(credentials, &connection_pool).await { Err(e) => { let e = match e { AuthError::UnexpectedError(_) => LoginError::UnexpectedError(e.into()), AuthError::InvalidCredentials(_) => LoginError::AuthError(e.into()), AuthError::NotAuthenticated => unreachable!(), }; Err(e) } Ok(user_id) => { tracing::Span::current().record("user_id", tracing::field::display(&user_id)); session .renew() .await .map_err(|e| LoginError::UnexpectedError(e.into()))?; session .insert_user_id(user_id) .await .map_err(|e| LoginError::UnexpectedError(e.into()))?; session .insert_username(form.username) .await .map_err(|e| LoginError::UnexpectedError(e.into()))?; let mut headers = HeaderMap::new(); headers.insert("HX-Redirect", "/admin/dashboard".parse().unwrap()); Ok((StatusCode::OK, headers).into_response()) } } }