diff options
Diffstat (limited to 'src/token/repo')
| -rw-r--r-- | src/token/repo/auth.rs | 66 | ||||
| -rw-r--r-- | src/token/repo/mod.rs | 2 | ||||
| -rw-r--r-- | src/token/repo/token.rs | 66 |
3 files changed, 91 insertions, 43 deletions
diff --git a/src/token/repo/auth.rs b/src/token/repo/auth.rs index 88d0878..bdc4c33 100644 --- a/src/token/repo/auth.rs +++ b/src/token/repo/auth.rs @@ -2,8 +2,10 @@ use sqlx::{sqlite::Sqlite, SqliteConnection, Transaction}; use crate::{ clock::DateTime, + db::NotFound, event::{Instant, Sequence}, login::{self, password::StoredHash, History, Login}, + name::{self, Name}, }; pub trait Provider { @@ -19,35 +21,53 @@ impl<'c> Provider for Transaction<'c, Sqlite> { pub struct Auth<'t>(&'t mut SqliteConnection); impl<'t> Auth<'t> { - pub async fn for_name(&mut self, name: &str) -> Result<(History, StoredHash), sqlx::Error> { - let found = sqlx::query!( + pub async fn for_name(&mut self, name: &Name) -> Result<(History, StoredHash), LoadError> { + let name = name.canonical(); + let row = sqlx::query!( r#" - select - id as "id: login::Id", - name, - password_hash as "password_hash: StoredHash", + select + id as "id: login::Id", + display_name as "display_name: String", + canonical_name as "canonical_name: String", created_sequence as "created_sequence: Sequence", - created_at as "created_at: DateTime" - from login - where name = $1 - "#, + created_at as "created_at: DateTime", + password_hash as "password_hash: StoredHash" + from login + where canonical_name = $1 + "#, name, ) - .map(|row| { - ( - History { - login: Login { - id: row.id, - name: row.name, - }, - created: Instant::new(row.created_at, row.created_sequence), - }, - row.password_hash, - ) - }) .fetch_one(&mut *self.0) .await?; - Ok(found) + let login = History { + login: Login { + id: row.id, + name: Name::new(row.display_name, row.canonical_name)?, + }, + created: Instant::new(row.created_at, row.created_sequence), + }; + + Ok((login, row.password_hash)) + } +} + +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub enum LoadError { + Database(#[from] sqlx::Error), + Name(#[from] name::Error), +} + +impl<T> NotFound for Result<T, LoadError> { + type Ok = T; + type Error = LoadError; + + fn optional(self) -> Result<Option<T>, LoadError> { + match self { + Ok(value) => Ok(Some(value)), + Err(LoadError::Database(sqlx::Error::RowNotFound)) => Ok(None), + Err(other) => Err(other), + } } } diff --git a/src/token/repo/mod.rs b/src/token/repo/mod.rs index 9169743..d8463eb 100644 --- a/src/token/repo/mod.rs +++ b/src/token/repo/mod.rs @@ -1,4 +1,4 @@ pub mod auth; mod token; -pub use self::token::Provider; +pub use self::token::{LoadError, Provider}; diff --git a/src/token/repo/token.rs b/src/token/repo/token.rs index c592dcd..35ea385 100644 --- a/src/token/repo/token.rs +++ b/src/token/repo/token.rs @@ -3,7 +3,10 @@ use uuid::Uuid; use crate::{ clock::DateTime, + db::NotFound, + event::{Instant, Sequence}, login::{self, History, Login}, + name::{self, Name}, token::{Id, Secret}, }; @@ -100,53 +103,78 @@ impl<'c> Tokens<'c> { } // Validate a token by its secret, retrieving the associated Login record. - // Will return [None] if the token is not valid. The token's last-used - // timestamp will be set to `used_at`. + // Will return an error if the token is not valid. If successful, the + // retrieved token's last-used timestamp will be set to `used_at`. pub async fn validate( &mut self, secret: &Secret, used_at: &DateTime, - ) -> Result<(Id, Login), sqlx::Error> { + ) -> Result<(Id, History), LoadError> { // I would use `update … returning` to do this in one query, but // sqlite3, as of this writing, does not allow an update's `returning` // clause to reference columns from tables joined into the update. Two // queries is fine, but it feels untidy. - sqlx::query!( + let (token, login) = sqlx::query!( r#" update token set last_used_at = $1 where secret = $2 + returning + id as "token: Id", + login as "login: login::Id" "#, used_at, secret, ) - .execute(&mut *self.0) + .map(|row| (row.token, row.login)) + .fetch_one(&mut *self.0) .await?; let login = sqlx::query!( r#" select - token.id as "token_id: Id", - login.id as "login_id: login::Id", - login.name as "login_name" + id as "id: login::Id", + display_name as "display_name: String", + canonical_name as "canonical_name: String", + created_sequence as "created_sequence: Sequence", + created_at as "created_at: DateTime" from login - join token on login.id = token.login - where token.secret = $1 + where id = $1 "#, - secret, + login, ) .map(|row| { - ( - row.token_id, - Login { - id: row.login_id, - name: row.login_name, + Ok::<_, name::Error>(History { + login: Login { + id: row.id, + name: Name::new(row.display_name, row.canonical_name)?, }, - ) + created: Instant::new(row.created_at, row.created_sequence), + }) }) .fetch_one(&mut *self.0) - .await?; + .await??; + + Ok((token, login)) + } +} + +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub enum LoadError { + Database(#[from] sqlx::Error), + Name(#[from] name::Error), +} + +impl<T> NotFound for Result<T, LoadError> { + type Ok = T; + type Error = LoadError; - Ok(login) + fn optional(self) -> Result<Option<T>, LoadError> { + match self { + Ok(value) => Ok(Some(value)), + Err(LoadError::Database(sqlx::Error::RowNotFound)) => Ok(None), + Err(other) => Err(other), + } } } |
