From cce6662d635bb2115f9f2a7bab92cc105166e761 Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Wed, 18 Sep 2024 01:27:47 -0400 Subject: App methods now return errors that allow not-found cases to be distinguished. --- src/login/app.rs | 54 +++++++++++++++++++++++++++++++++-------------------- src/login/routes.rs | 24 +++++++++++------------- 2 files changed, 45 insertions(+), 33 deletions(-) (limited to 'src/login') diff --git a/src/login/app.rs b/src/login/app.rs index aec072c..f0e0571 100644 --- a/src/login/app.rs +++ b/src/login/app.rs @@ -3,9 +3,9 @@ use sqlx::sqlite::SqlitePool; use super::repo::auth::Provider as _; use crate::{ clock::DateTime, - error::BoxedError, password::StoredHash, repo::{ + error::NotFound as _, login::{Login, Provider as _}, token::Provider as _, }, @@ -25,7 +25,7 @@ impl<'a> Logins<'a> { name: &str, password: &str, login_at: DateTime, - ) -> Result, BoxedError> { + ) -> Result { let mut tx = self.db.begin().await?; let login = if let Some((login, stored_hash)) = tx.auth().for_name(name).await? { @@ -41,39 +41,53 @@ impl<'a> Logins<'a> { Some(tx.logins().create(name, &password_hash).await?) }; - // If `login` is Some, then we have an identity and can issue a token. - // If `login` is None, then neither creating a new login nor - // authenticating an existing one succeeded, and we must reject the - // login attempt. - let token = if let Some(login) = login { - Some(tx.tokens().issue(&login, login_at).await?) - } else { - None - }; - + let login = login.ok_or(LoginError::Rejected)?; + let token = tx.tokens().issue(&login, login_at).await?; tx.commit().await?; Ok(token) } - pub async fn validate( - &self, - secret: &str, - used_at: DateTime, - ) -> Result, BoxedError> { + pub async fn validate(&self, secret: &str, used_at: DateTime) -> Result { let mut tx = self.db.begin().await?; tx.tokens().expire(used_at).await?; - let login = tx.tokens().validate(secret, used_at).await?; + let login = tx + .tokens() + .validate(secret, used_at) + .await + .not_found(|| ValidateError::InvalidToken)?; tx.commit().await?; Ok(login) } - pub async fn logout(&self, secret: &str) -> Result<(), BoxedError> { + pub async fn logout(&self, secret: &str) -> Result<(), ValidateError> { let mut tx = self.db.begin().await?; - tx.tokens().revoke(secret).await?; + tx.tokens() + .revoke(secret) + .await + .not_found(|| ValidateError::InvalidToken)?; + tx.commit().await?; Ok(()) } } + +#[derive(Debug, thiserror::Error)] +pub enum LoginError { + #[error("invalid login")] + Rejected, + #[error("database error: {0}")] + DatabaseError(#[from] sqlx::Error), + #[error("password hash error: {0}")] + PasswordHashError(#[from] password_hash::Error), +} + +#[derive(Debug, thiserror::Error)] +pub enum ValidateError { + #[error("invalid token")] + InvalidToken, + #[error("database error: {0}")] + DatabaseError(#[from] sqlx::Error), +} diff --git a/src/login/routes.rs b/src/login/routes.rs index 816926e..3c58b10 100644 --- a/src/login/routes.rs +++ b/src/login/routes.rs @@ -8,7 +8,7 @@ use axum::{ use crate::{app::App, clock::RequestedAt, error::InternalError}; -use super::extract::IdentityToken; +use super::{app::LoginError, extract::IdentityToken}; pub fn router() -> Router { Router::new() @@ -28,30 +28,28 @@ async fn on_login( identity: IdentityToken, Form(form): Form, ) -> Result { - let token = app.logins().login(&form.name, &form.password, now).await?; - - let resp = if let Some(token) = token { - let identity = identity.set(&token); - (identity, LoginResponse::Successful) - } else { - (identity, LoginResponse::Rejected) - }; - - Ok(resp) + match app.logins().login(&form.name, &form.password, now).await { + Ok(token) => { + let identity = identity.set(&token); + Ok(LoginResponse::Successful(identity)) + } + Err(LoginError::Rejected) => Ok(LoginResponse::Rejected), + Err(other) => Err(other.into()), + } } enum LoginResponse { Rejected, - Successful, + Successful(IdentityToken), } impl IntoResponse for LoginResponse { fn into_response(self) -> Response { match self { + Self::Successful(identity) => (identity, Redirect::to("/")).into_response(), Self::Rejected => { (StatusCode::UNAUTHORIZED, "invalid name or password").into_response() } - Self::Successful => Redirect::to("/").into_response(), } } } -- cgit v1.2.3