Use thiserror
instead of converting errors manually
This commit is contained in:
parent
c16707c173
commit
72a15eed07
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1778,6 +1778,7 @@ dependencies = [
|
|||
"serde",
|
||||
"sqlx",
|
||||
"tera",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tower",
|
||||
]
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
34
src/main.rs
34
src/main.rs
|
@ -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));
|
||||
}
|
||||
|
|
41
src/users.rs
41
src/users.rs
|
@ -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))
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue