summaryrefslogtreecommitdiff
path: root/src/token
diff options
context:
space:
mode:
authorOwen Jacobson <owen@grimoire.ca>2024-10-09 00:57:31 -0400
committerOwen Jacobson <owen@grimoire.ca>2024-10-09 11:45:31 -0400
commitba96974bdebd6d4ec345907d49944b5ee644ed47 (patch)
tree8811ef8981a915a8cc17d8a1e576750b31cbdd0b /src/token
parentda1810afc5a627a518131cfb0af0996c5ec60bcf (diff)
Provide a view of logins to clients.
Diffstat (limited to 'src/token')
-rw-r--r--src/token/app.rs49
-rw-r--r--src/token/broadcaster.rs3
-rw-r--r--src/token/event.rs10
-rw-r--r--src/token/mod.rs4
-rw-r--r--src/token/repo/auth.rs28
-rw-r--r--src/token/repo/token.rs9
6 files changed, 65 insertions, 38 deletions
diff --git a/src/token/app.rs b/src/token/app.rs
index 5c4fcd5..b8af637 100644
--- a/src/token/app.rs
+++ b/src/token/app.rs
@@ -7,23 +7,34 @@ use futures::{
use sqlx::sqlite::SqlitePool;
use super::{
- broadcaster::Broadcaster, event, repo::auth::Provider as _, repo::Provider as _, Id, Secret,
+ repo::auth::Provider as _, repo::Provider as _, Broadcaster, Event as TokenEvent, Id, Secret,
};
use crate::{
clock::DateTime,
db::NotFound as _,
+ event::{self, repo::Provider as _, Event as ServiceEvent},
login::{repo::Provider as _, Login, Password},
};
pub struct Tokens<'a> {
db: &'a SqlitePool,
- tokens: &'a Broadcaster,
+ events: &'a event::Broadcaster,
+ token_events: &'a Broadcaster,
}
impl<'a> Tokens<'a> {
- pub const fn new(db: &'a SqlitePool, tokens: &'a Broadcaster) -> Self {
- Self { db, tokens }
+ pub const fn new(
+ db: &'a SqlitePool,
+ events: &'a event::Broadcaster,
+ token_events: &'a Broadcaster,
+ ) -> Self {
+ Self {
+ db,
+ events,
+ token_events,
+ }
}
+
pub async fn login(
&self,
name: &str,
@@ -32,22 +43,30 @@ impl<'a> Tokens<'a> {
) -> Result<Secret, LoginError> {
let mut tx = self.db.begin().await?;
- let login = if let Some((login, stored_hash)) = tx.auth().for_name(name).await? {
+ let (login, created) = if let Some((login, stored_hash)) = tx.auth().for_name(name).await? {
if stored_hash.verify(password)? {
- // Password verified; use the login.
- login
+ // Password verified, proceed with login
+ (login, false)
} else {
// Password NOT verified.
return Err(LoginError::Rejected);
}
} else {
let password_hash = password.hash()?;
- tx.logins().create(name, &password_hash).await?
+ let created = tx.sequence().next(login_at).await?;
+ let login = tx.logins().create(name, &password_hash, &created).await?;
+
+ (login, true)
};
let token = tx.tokens().issue(&login, login_at).await?;
tx.commit().await?;
+ if created {
+ self.events
+ .broadcast(login.events().map(ServiceEvent::from).collect::<Vec<_>>());
+ }
+
Ok(token)
}
@@ -76,7 +95,7 @@ impl<'a> Tokens<'a> {
E: std::fmt::Debug,
{
// Subscribe, first.
- let token_events = self.tokens.subscribe();
+ let token_events = self.token_events.subscribe();
// Check that the token is valid at this point in time, second. If it is, then
// any future revocations will appear in the subscription. If not, bail now.
@@ -102,7 +121,9 @@ impl<'a> Tokens<'a> {
// Then construct the guarded stream. First, project both streams into
// `GuardedEvent`.
let token_events = token_events
- .filter(move |event| future::ready(event.token == token))
+ .filter(move |event| {
+ future::ready(matches!(event, TokenEvent::Revoked(id) if id == &token))
+ })
.map(|_| GuardedEvent::TokenRevoked);
let events = events.map(|event| GuardedEvent::Event(event));
@@ -126,8 +147,8 @@ impl<'a> Tokens<'a> {
let tokens = tx.tokens().expire(&expire_at).await?;
tx.commit().await?;
- for event in tokens.into_iter().map(event::TokenRevoked::from) {
- self.tokens.broadcast(event);
+ for event in tokens.into_iter().map(TokenEvent::Revoked) {
+ self.token_events.broadcast(event);
}
Ok(())
@@ -138,8 +159,8 @@ impl<'a> Tokens<'a> {
tx.tokens().revoke(token).await?;
tx.commit().await?;
- self.tokens
- .broadcast(event::TokenRevoked::from(token.clone()));
+ self.token_events
+ .broadcast(TokenEvent::Revoked(token.clone()));
Ok(())
}
diff --git a/src/token/broadcaster.rs b/src/token/broadcaster.rs
index 8e2e006..de2513a 100644
--- a/src/token/broadcaster.rs
+++ b/src/token/broadcaster.rs
@@ -1,4 +1,3 @@
-use super::event;
use crate::broadcast;
-pub type Broadcaster = broadcast::Broadcaster<event::TokenRevoked>;
+pub type Broadcaster = broadcast::Broadcaster<super::Event>;
diff --git a/src/token/event.rs b/src/token/event.rs
index d53d436..51b74d7 100644
--- a/src/token/event.rs
+++ b/src/token/event.rs
@@ -1,12 +1,6 @@
use crate::token;
#[derive(Clone, Debug)]
-pub struct TokenRevoked {
- pub token: token::Id,
-}
-
-impl From<token::Id> for TokenRevoked {
- fn from(token: token::Id) -> Self {
- Self { token }
- }
+pub enum Event {
+ Revoked(token::Id),
}
diff --git a/src/token/mod.rs b/src/token/mod.rs
index d122611..eccb3cd 100644
--- a/src/token/mod.rs
+++ b/src/token/mod.rs
@@ -1,9 +1,9 @@
pub mod app;
-pub mod broadcaster;
+mod broadcaster;
mod event;
pub mod extract;
mod id;
mod repo;
mod secret;
-pub use self::{id::Id, secret::Secret};
+pub use self::{broadcaster::Broadcaster, event::Event, id::Id, secret::Secret};
diff --git a/src/token/repo/auth.rs b/src/token/repo/auth.rs
index b299697..ddb5136 100644
--- a/src/token/repo/auth.rs
+++ b/src/token/repo/auth.rs
@@ -1,6 +1,10 @@
use sqlx::{sqlite::Sqlite, SqliteConnection, Transaction};
-use crate::login::{self, password::StoredHash, Login};
+use crate::{
+ clock::DateTime,
+ event::{Instant, Sequence},
+ login::{self, password::StoredHash, History, Login},
+};
pub trait Provider {
fn auth(&mut self) -> Auth;
@@ -21,25 +25,33 @@ impl<'t> Auth<'t> {
pub async fn for_name(
&mut self,
name: &str,
- ) -> Result<Option<(Login, StoredHash)>, sqlx::Error> {
+ ) -> Result<Option<(History, StoredHash)>, sqlx::Error> {
let found = sqlx::query!(
r#"
select
id as "id: login::Id",
name,
- password_hash as "password_hash: StoredHash"
+ password_hash as "password_hash: StoredHash",
+ created_sequence as "created_sequence: Sequence",
+ created_at as "created_at: DateTime"
from login
where name = $1
"#,
name,
)
- .map(|rec| {
+ .map(|row| {
(
- Login {
- id: rec.id,
- name: rec.name,
+ History {
+ login: Login {
+ id: row.id,
+ name: row.name,
+ },
+ created: Instant {
+ at: row.created_at,
+ sequence: row.created_sequence,
+ },
},
- rec.password_hash,
+ row.password_hash,
)
})
.fetch_optional(&mut *self.0)
diff --git a/src/token/repo/token.rs b/src/token/repo/token.rs
index 5f64dac..c592dcd 100644
--- a/src/token/repo/token.rs
+++ b/src/token/repo/token.rs
@@ -3,7 +3,7 @@ use uuid::Uuid;
use crate::{
clock::DateTime,
- login::{self, Login},
+ login::{self, History, Login},
token::{Id, Secret},
};
@@ -24,11 +24,12 @@ impl<'c> Tokens<'c> {
// be used to control expiry, until the token is actually used.
pub async fn issue(
&mut self,
- login: &Login,
+ login: &History,
issued_at: &DateTime,
) -> Result<Secret, sqlx::Error> {
let id = Id::generate();
let secret = Uuid::new_v4().to_string();
+ let login = login.id();
let secret = sqlx::query_scalar!(
r#"
@@ -39,7 +40,7 @@ impl<'c> Tokens<'c> {
"#,
id,
secret,
- login.id,
+ login,
issued_at,
)
.fetch_one(&mut *self.0)
@@ -127,7 +128,7 @@ impl<'c> Tokens<'c> {
select
token.id as "token_id: Id",
login.id as "login_id: login::Id",
- name as "login_name"
+ login.name as "login_name"
from login
join token on login.id = token.login
where token.secret = $1