diff options
Diffstat (limited to 'src/token')
| -rw-r--r-- | src/token/app.rs | 118 | ||||
| -rw-r--r-- | src/token/extract/identity.rs | 4 | ||||
| -rw-r--r-- | src/token/mod.rs | 12 | ||||
| -rw-r--r-- | src/token/repo/auth.rs | 105 | ||||
| -rw-r--r-- | src/token/repo/mod.rs | 1 | ||||
| -rw-r--r-- | src/token/repo/token.rs | 43 |
6 files changed, 26 insertions, 257 deletions
diff --git a/src/token/app.rs b/src/token/app.rs index a7a843d..fb5d712 100644 --- a/src/token/app.rs +++ b/src/token/app.rs @@ -8,15 +8,9 @@ use sqlx::sqlite::SqlitePool; use super::{ Broadcaster, Event as TokenEvent, Secret, Token, extract::Identity, - repo::{self, Provider as _, auth::Provider as _}, -}; -use crate::{ - clock::DateTime, - db::NotFound as _, - name::{self, Name}, - password::Password, - user::{User, repo::Provider as _}, + repo::{self, Provider as _}, }; +use crate::{clock::DateTime, db::NotFound as _, name}; pub struct Tokens<'a> { db: &'a SqlitePool, @@ -28,100 +22,20 @@ impl<'a> Tokens<'a> { Self { db, token_events } } - pub async fn login( - &self, - name: &Name, - password: &Password, - login_at: &DateTime, - ) -> Result<Secret, LoginError> { - let mut tx = self.db.begin().await?; - let (user, stored_hash) = tx - .auth() - .for_name(name) - .await - .optional()? - .ok_or(LoginError::Rejected)?; - // Split the transaction here to avoid holding the tx open (potentially blocking - // other writes) while we do the fairly expensive task of verifying the - // password. It's okay if the token issuance transaction happens some notional - // amount of time after retrieving the login, as inserting the token will fail - // if the account is deleted during that time. - tx.commit().await?; - - let user = user.as_snapshot().ok_or(LoginError::Rejected)?; - - if stored_hash.verify(password)? { - let mut tx = self.db.begin().await?; - let (token, secret) = Token::generate(&user, login_at); - tx.tokens().create(&token, &secret).await?; - tx.commit().await?; - - Ok(secret) - } else { - Err(LoginError::Rejected) - } - } - - pub async fn change_password( - &self, - user: &User, - password: &Password, - to: &Password, - changed_at: &DateTime, - ) -> Result<Secret, LoginError> { - let mut tx = self.db.begin().await?; - let (user, stored_hash) = tx - .auth() - .for_user(user) - .await - .optional()? - .ok_or(LoginError::Rejected)?; - // Split the transaction here to avoid holding the tx open (potentially blocking - // other writes) while we do the fairly expensive task of verifying the - // password. It's okay if the token issuance transaction happens some notional - // amount of time after retrieving the login, as inserting the token will fail - // if the account is deleted during that time. - tx.commit().await?; - - if !stored_hash.verify(password)? { - return Err(LoginError::Rejected); - } - - let user_snapshot = user.as_snapshot().ok_or(LoginError::Rejected)?; - let to_hash = to.hash()?; - - let mut tx = self.db.begin().await?; - tx.users().set_password(&user, &to_hash).await?; - - let tokens = tx.tokens().revoke_all(&user).await?; - let (token, secret) = Token::generate(&user_snapshot, changed_at); - tx.tokens().create(&token, &secret).await?; - - tx.commit().await?; - - for event in tokens.into_iter().map(TokenEvent::Revoked) { - self.token_events.broadcast(event); - } - - Ok(secret) - } - pub async fn validate( &self, secret: &Secret, used_at: &DateTime, ) -> Result<Identity, ValidateError> { let mut tx = self.db.begin().await?; - let (token, user) = tx + let (token, login) = tx .tokens() .validate(secret, used_at) .await .not_found(|| ValidateError::InvalidToken)?; tx.commit().await?; - let user = user.as_snapshot().ok_or(ValidateError::LoginDeleted)?; - - Ok(Identity { token, user }) + Ok(Identity { token, login }) } pub async fn limit_stream<S, E>( @@ -208,33 +122,9 @@ impl<'a> Tokens<'a> { } #[derive(Debug, thiserror::Error)] -pub enum LoginError { - #[error("invalid login")] - Rejected, - #[error(transparent)] - Database(#[from] sqlx::Error), - #[error(transparent)] - Name(#[from] name::Error), - #[error(transparent)] - PasswordHash(#[from] password_hash::Error), -} - -impl From<repo::auth::LoadError> for LoginError { - fn from(error: repo::auth::LoadError) -> Self { - use repo::auth::LoadError; - match error { - LoadError::Database(error) => error.into(), - LoadError::Name(error) => error.into(), - } - } -} - -#[derive(Debug, thiserror::Error)] pub enum ValidateError { #[error("invalid token")] InvalidToken, - #[error("user deleted")] - LoginDeleted, #[error(transparent)] Database(#[from] sqlx::Error), #[error(transparent)] diff --git a/src/token/extract/identity.rs b/src/token/extract/identity.rs index d01ab53..960fe60 100644 --- a/src/token/extract/identity.rs +++ b/src/token/extract/identity.rs @@ -10,14 +10,14 @@ use crate::{ app::App, clock::RequestedAt, error::{Internal, Unauthorized}, + login::Login, token::{Token, app::ValidateError}, - user::User, }; #[derive(Clone, Debug)] pub struct Identity { pub token: Token, - pub user: User, + pub login: Login, } impl FromRequestParts<App> for Identity { diff --git a/src/token/mod.rs b/src/token/mod.rs index 58ff08b..b2dd6f1 100644 --- a/src/token/mod.rs +++ b/src/token/mod.rs @@ -8,30 +8,26 @@ mod secret; use uuid::Uuid; -use crate::{ - clock::DateTime, - user::{self, User}, -}; - pub use self::{broadcaster::Broadcaster, event::Event, id::Id, secret::Secret}; +use crate::{clock::DateTime, login, login::Login}; #[derive(Clone, Debug)] pub struct Token { pub id: Id, - pub user: user::Id, + pub login: login::Id, pub issued_at: DateTime, pub last_used_at: DateTime, } impl Token { - pub fn generate(user: &User, issued_at: &DateTime) -> (Self, Secret) { + pub fn generate(login: &Login, issued_at: &DateTime) -> (Self, Secret) { let id = Id::generate(); let secret = Uuid::new_v4().to_string().into(); ( Self { id, - user: user.id.clone(), + login: login.id.clone(), issued_at: *issued_at, last_used_at: *issued_at, }, diff --git a/src/token/repo/auth.rs b/src/token/repo/auth.rs deleted file mode 100644 index a42fa1a..0000000 --- a/src/token/repo/auth.rs +++ /dev/null @@ -1,105 +0,0 @@ -use sqlx::{SqliteConnection, Transaction, sqlite::Sqlite}; - -use crate::{ - clock::DateTime, - db::NotFound, - event::{Instant, Sequence}, - name::{self, Name}, - password::StoredHash, - user::{self, History, User}, -}; - -pub trait Provider { - fn auth(&mut self) -> Auth<'_>; -} - -impl Provider for Transaction<'_, Sqlite> { - fn auth(&mut self) -> Auth<'_> { - Auth(self) - } -} - -pub struct Auth<'t>(&'t mut SqliteConnection); - -impl Auth<'_> { - 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: user::Id", - login.display_name as "display_name: String", - login.canonical_name as "canonical_name: String", - user.created_sequence as "created_sequence: Sequence", - user.created_at as "created_at: DateTime", - login.password as "password: StoredHash" - from user - join login using (id) - where login.canonical_name = $1 - "#, - name, - ) - .fetch_one(&mut *self.0) - .await?; - - let login = History { - user: User { - 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)) - } - - pub async fn for_user(&mut self, user: &User) -> Result<(History, StoredHash), LoadError> { - let row = sqlx::query!( - r#" - select - id as "id: user::Id", - login.display_name as "display_name: String", - login.canonical_name as "canonical_name: String", - user.created_sequence as "created_sequence: Sequence", - user.created_at as "created_at: DateTime", - login.password as "password: StoredHash" - from user - join login using (id) - where id = $1 - "#, - user.id, - ) - .fetch_one(&mut *self.0) - .await?; - - let user = History { - user: User { - id: row.id, - name: Name::new(row.display_name, row.canonical_name)?, - }, - created: Instant::new(row.created_at, row.created_sequence), - }; - - Ok((user, row.password)) - } -} - -#[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 d8463eb..9df5bbb 100644 --- a/src/token/repo/mod.rs +++ b/src/token/repo/mod.rs @@ -1,4 +1,3 @@ -pub mod auth; mod token; pub use self::token::{LoadError, Provider}; diff --git a/src/token/repo/token.rs b/src/token/repo/token.rs index afcde53..52a3987 100644 --- a/src/token/repo/token.rs +++ b/src/token/repo/token.rs @@ -3,10 +3,9 @@ use sqlx::{SqliteConnection, Transaction, sqlite::Sqlite}; use crate::{ clock::DateTime, db::NotFound, - event::{Instant, Sequence}, + login::{self, Login}, name::{self, Name}, token::{Id, Secret, Token}, - user::{self, History, User}, }; pub trait Provider { @@ -31,11 +30,11 @@ impl Tokens<'_> { "#, token.id, secret, - token.user, + token.login, token.issued_at, token.last_used_at, ) - .fetch_one(&mut *self.0) + .execute(&mut *self.0) .await?; Ok(()) @@ -71,8 +70,7 @@ impl Tokens<'_> { } // Revoke tokens for a login - pub async fn revoke_all(&mut self, user: &user::History) -> Result<Vec<Id>, sqlx::Error> { - let user = user.id(); + pub async fn revoke_all(&mut self, login: &Login) -> Result<Vec<Id>, sqlx::Error> { let tokens = sqlx::query_scalar!( r#" delete @@ -80,7 +78,7 @@ impl Tokens<'_> { where login = $1 returning id as "id: Id" "#, - user, + login.id, ) .fetch_all(&mut *self.0) .await?; @@ -106,14 +104,11 @@ impl Tokens<'_> { Ok(tokens) } - // Validate a token by its secret, retrieving the associated Login record. - // 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<(Token, History), LoadError> { + ) -> Result<(Token, Login), 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 @@ -125,7 +120,7 @@ impl Tokens<'_> { where secret = $2 returning id as "id: Id", - login as "login: user::Id", + login as "login: login::Id", issued_at as "issued_at: DateTime", last_used_at as "last_used_at: DateTime" "#, @@ -134,7 +129,7 @@ impl Tokens<'_> { ) .map(|row| Token { id: row.id, - user: row.login, + login: row.login, issued_at: row.issued_at, last_used_at: row.last_used_at, }) @@ -144,24 +139,18 @@ impl Tokens<'_> { let user = sqlx::query!( r#" select - id as "id: user::Id", - login.display_name as "display_name: String", - login.canonical_name as "canonical_name: String", - user.created_sequence as "created_sequence: Sequence", - user.created_at as "created_at: DateTime" - from user - join login using (id) + id as "id: login::Id", + display_name, + canonical_name + from login where id = $1 "#, - token.user, + token.login, ) .map(|row| { - Ok::<_, name::Error>(History { - user: User { - id: row.id, - name: Name::new(row.display_name, row.canonical_name)?, - }, - created: Instant::new(row.created_at, row.created_sequence), + Ok::<_, name::Error>(Login { + id: row.id, + name: Name::new(row.display_name, row.canonical_name)?, }) }) .fetch_one(&mut *self.0) |
