124 lines
3.6 KiB
Rust
124 lines
3.6 KiB
Rust
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<Response, LoginError> {
|
|
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<AppState>,
|
|
Form(form): Form<LoginFormData>,
|
|
) -> Result<Response, LoginError> {
|
|
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())
|
|
}
|
|
}
|
|
}
|