Setup a proper error type for the web application

This commit is contained in:
Daniel Flanagan 2023-11-14 14:07:47 -06:00
parent 0782e82d6e
commit 22761ee92e
Signed by: lytedev
GPG key ID: 5B2020A0F9921EF4
3 changed files with 53 additions and 40 deletions

View file

@ -1,25 +1,41 @@
use core::fmt;
use axum::{
http::StatusCode,
response::{IntoResponse, Response},
};
use thiserror::Error;
pub struct AppError(anyhow::Error);
#[derive(Error, Debug)]
pub enum AppError {
InvalidCsrf(#[from] axum_csrf::CsrfError),
Other(#[from] anyhow::Error),
}
impl AppError {
fn status_code(&self) -> StatusCode {
match self {
AppError::Other(_) => StatusCode::INTERNAL_SERVER_ERROR,
AppError::InvalidCsrf(_) => StatusCode::BAD_REQUEST,
}
}
fn message(&self) -> String {
match self {
AppError::Other(e) => format!("something went wrong: {}", e),
AppError::InvalidCsrf(e) => format!("unable to verify csrf: {}", e),
}
}
}
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message())
}
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Something went wrong: {}", self.0),
)
.into_response()
}
}
impl<E> From<E> for AppError
where
E: Into<anyhow::Error>,
{
fn from(err: E) -> Self {
Self(err.into())
(self.status_code(), self.message()).into_response()
}
}

View file

@ -66,7 +66,7 @@ struct Register {
}
impl<'a> TryInto<NewUser<'a>> for &'a Register {
type Error = argon2::password_hash::Error;
type Error = anyhow::Error;
fn try_into(self: &'a Register) -> Result<NewUser<'a>, Self::Error> {
let salt = SaltString::generate(&mut OsRng);
@ -86,18 +86,11 @@ impl<'a> TryInto<NewUser<'a>> for &'a Register {
}
async fn register(
csrf_token: CsrfToken,
c: CsrfToken,
Form(register): Form<Register>,
) -> Result<impl IntoResponse, AppError> {
// TODO: https://docs.rs/axum_csrf/latest/axum_csrf/#prevent-post-replay-attacks-with-csrf
let v = csrf_token.verify(&register.authenticity_token);
if v.is_err() {
return Ok((
StatusCode::BAD_REQUEST,
Html(html! { "invalid request" }.into_string()),
));
}
c.verify(&register.authenticity_token)?;
let new_user: NewUser = (&register).try_into()?;

View file

@ -12,6 +12,14 @@ use crate::{
partials::{footer, header},
};
pub async fn csrf<F>(csrf: CsrfToken, cb: F) -> impl IntoResponse
where
F: Fn(&str) -> Html<String>,
{
let token = csrf.authenticity_token().unwrap();
(csrf, cb(&token))
}
#[instrument]
pub async fn index() -> Html<String> {
Html(
@ -39,10 +47,8 @@ pub async fn index() -> Html<String> {
)
}
pub async fn register(csrf: CsrfToken) -> impl IntoResponse {
let token = csrf.authenticity_token().unwrap();
(
csrf,
pub async fn register(t: CsrfToken) -> impl IntoResponse {
csrf(t, |token| {
Html(
html! {
(header())
@ -64,22 +70,20 @@ pub async fn register(csrf: CsrfToken) -> impl IntoResponse {
(footer())
}
.into_string(),
),
)
.into_response()
)
})
.await
}
pub async fn login(csrf: CsrfToken) -> impl IntoResponse {
let token = csrf.authenticity_token().unwrap();
(
csrf,
pub async fn login(t: CsrfToken) -> impl IntoResponse {
csrf(t, |token| {
Html(
html! {
(header())
main class="prose" {
h1 { "Login" }
form method="post" {
input type="hidden" name="authenticity_token" value=(token) {}
input type="hidden" name="authenticity_token" value=(token) {}
label {
input {}
}
@ -91,9 +95,9 @@ pub async fn login(csrf: CsrfToken) -> impl IntoResponse {
(footer())
}
.into_string(),
),
)
.into_response()
)
})
.await
}
#[allow(unreachable_code)]