summaryrefslogtreecommitdiff
path: root/src/token
diff options
context:
space:
mode:
Diffstat (limited to 'src/token')
-rw-r--r--src/token/app.rs118
-rw-r--r--src/token/extract/identity.rs4
-rw-r--r--src/token/mod.rs12
-rw-r--r--src/token/repo/auth.rs105
-rw-r--r--src/token/repo/mod.rs1
-rw-r--r--src/token/repo/token.rs43
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)