summaryrefslogtreecommitdiff
path: root/src/login
diff options
context:
space:
mode:
authorOwen Jacobson <owen@grimoire.ca>2024-09-18 01:27:47 -0400
committerOwen Jacobson <owen@grimoire.ca>2024-09-18 12:17:46 -0400
commitcce6662d635bb2115f9f2a7bab92cc105166e761 (patch)
tree9d1edfea364a3b72cf40c78d67ce05e3e68c84df /src/login
parent921f38a73e5d58a5a6077477a8b52d2705798f55 (diff)
App methods now return errors that allow not-found cases to be distinguished.
Diffstat (limited to 'src/login')
-rw-r--r--src/login/app.rs54
-rw-r--r--src/login/routes.rs24
2 files changed, 45 insertions, 33 deletions
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<Option<String>, BoxedError> {
+ ) -> Result<String, LoginError> {
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<Option<Login>, BoxedError> {
+ pub async fn validate(&self, secret: &str, used_at: DateTime) -> Result<Login, ValidateError> {
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<App> {
Router::new()
@@ -28,30 +28,28 @@ async fn on_login(
identity: IdentityToken,
Form(form): Form<LoginRequest>,
) -> Result<impl IntoResponse, InternalError> {
- 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(),
}
}
}