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::{ use axum::{
http::StatusCode, http::StatusCode,
response::{IntoResponse, Response}, 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 { impl IntoResponse for AppError {
fn into_response(self) -> Response { fn into_response(self) -> Response {
( (self.status_code(), self.message()).into_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())
} }
} }

View file

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

View file

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