Use thiserror instead of converting errors manually

This commit is contained in:
Yash Karandikar 2022-08-14 12:34:09 -05:00
parent c16707c173
commit 72a15eed07
4 changed files with 48 additions and 29 deletions

1
Cargo.lock generated
View file

@ -1778,6 +1778,7 @@ dependencies = [
"serde",
"sqlx",
"tera",
"thiserror",
"tokio",
"tower",
]

View file

@ -13,6 +13,7 @@ pbkdf2 = "0.11.0"
serde = { version = "1.0.143", features = ["derive"] }
sqlx = { version = "0.6.1", features = ["runtime-tokio-rustls", "postgres"] }
tera = "1.16.0"
thiserror = "1.0.32"
tokio = { version = "1.20.1", features = ["full"] }
tower = "0.4.13"

View file

@ -14,10 +14,40 @@ use axum_sessions::{async_session::MemoryStore, SessionLayer};
use sqlx::postgres::PgPoolOptions;
use std::sync::Arc;
use tera::Tera;
use thiserror::Error as ThisError;
use tower::ServiceBuilder;
#[derive(ThisError, Debug)]
pub enum Error {
Database(#[from] sqlx::Error),
Tera(#[from] tera::Error),
Tokio(#[from] tokio::task::JoinError),
Pbkdf2(pbkdf2::password_hash::Error),
Session(#[from] axum_sessions::async_session::serde_json::Error),
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:#?}", self)
}
}
impl IntoResponse for Error {
fn into_response(self) -> Response {
self.to_string().into_response()
}
}
// We need to implement this manually because pbkdf2's Error type does not meet the bounds required
// for `thiserror`
impl From<pbkdf2::password_hash::Error> for Error {
fn from(e: pbkdf2::password_hash::Error) -> Self {
Self::Pbkdf2(e)
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
async fn main() -> Result<(), Error> {
let pool = Arc::new(
PgPoolOptions::new()
.connect("postgresql://tmtd@192.168.1.133/tmtd")
@ -65,7 +95,7 @@ async fn homepage(
if let Some(_username) = session.get::<String>("logged_in_as") {
let rendered = tera
.render("home.html", &tera::Context::default())
.map_err(|e| e.to_string().into_response())?;
.map_err(|e| Error::from(e).into_response())?;
return Ok(Html(rendered));
}

View file

@ -1,3 +1,4 @@
use crate::Error;
use axum::extract::Extension;
use axum::extract::Form;
use axum::response::Html;
@ -22,24 +23,21 @@ pub struct RawUser {
pub async fn create_user(
Form(data): Form<RawUser>,
Extension(pool): Extension<Arc<Pool<Postgres>>>,
) -> Result<Redirect, String> {
) -> Result<Redirect, Error> {
let handle = tokio::task::spawn_blocking(move || {
let salt = SaltString::generate(&mut OsRng);
let password_hash = Pbkdf2.hash_password(data.password.as_bytes(), &salt);
password_hash
.map(|p| p.to_string())
.map_err(|e| e.to_string())
password_hash.map(|p| p.to_string())
});
let hash = handle.await.map_err(|e| e.to_string())??;
let hash = handle.await??;
sqlx::query("INSERT INTO users (username, password_hash) VALUES ($1, $2)")
.bind(data.username)
.bind(hash)
.execute(&*pool)
.await
.map_err(|e| e.to_string())?;
.await?;
Ok(Redirect::to("/login"))
}
@ -48,47 +46,36 @@ pub async fn login_backend(
Form(data): Form<RawUser>,
Extension(pool): Extension<Arc<Pool<Postgres>>>,
mut session: WritableSession,
) -> Result<Redirect, String> {
) -> Result<Redirect, Error> {
let (hash,): (String,) = sqlx::query_as("SELECT password_hash FROM users WHERE username=$1")
.bind(&data.username)
.fetch_one(&*pool)
.await
.map_err(|e| e.to_string())?;
.await?;
let handle = tokio::task::spawn_blocking(move || {
let parsed_hash = PasswordHash::new(&hash).map_err(|e| e.to_string())?;
let parsed_hash = PasswordHash::new(&hash)?;
Pbkdf2
.verify_password(data.password.as_bytes(), &parsed_hash)
.map(|_| true)
.map_err(|e| e.to_string())
});
handle
.await
.map_err(|e| e.to_string())
.and_then(std::convert::identity)?;
handle.await??;
// If we've gotten to this point, then the password was valid
session
.insert("logged_in_as", &data.username)
.map_err(|e| e.to_string())?;
session.insert("logged_in_as", &data.username)?;
Ok(Redirect::to("/"))
}
pub async fn register_form(Extension(tera): Extension<Arc<Tera>>) -> Result<Html<String>, String> {
let rendered = tera
.render("users/register.html", &Context::new())
.map_err(|e| e.to_string())?;
pub async fn register_form(Extension(tera): Extension<Arc<Tera>>) -> Result<Html<String>, Error> {
let rendered = tera.render("users/register.html", &Context::new())?;
Ok(Html(rendered))
}
pub async fn login_form(Extension(tera): Extension<Arc<Tera>>) -> Result<Html<String>, String> {
let rendered = tera
.render("users/login.html", &Context::new())
.map_err(|e| e.to_string())?;
pub async fn login_form(Extension(tera): Extension<Arc<Tera>>) -> Result<Html<String>, Error> {
let rendered = tera.render("users/login.html", &Context::new())?;
Ok(Html(rendered))
}