Setup a proper error type for the web application
This commit is contained in:
parent
0782e82d6e
commit
22761ee92e
3 changed files with 53 additions and 40 deletions
46
src/error.rs
46
src/error.rs
|
@ -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())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(®ister.authenticity_token)?;
|
||||||
let v = csrf_token.verify(®ister.authenticity_token);
|
|
||||||
if v.is_err() {
|
|
||||||
return Ok((
|
|
||||||
StatusCode::BAD_REQUEST,
|
|
||||||
Html(html! { "invalid request" }.into_string()),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let new_user: NewUser = (®ister).try_into()?;
|
let new_user: NewUser = (®ister).try_into()?;
|
||||||
|
|
||||||
|
|
34
src/views.rs
34
src/views.rs
|
@ -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)]
|
||||||
|
|
Loading…
Reference in a new issue