use sqlx::{SqliteConnection, Transaction, sqlite::Sqlite}; use crate::{ clock::DateTime, db::NotFound, login::{self, Login}, name::{self, Name}, token::{Id, Secret, Token}, }; pub trait Provider { fn tokens(&mut self) -> Tokens<'_>; } impl Provider for Transaction<'_, Sqlite> { fn tokens(&mut self) -> Tokens<'_> { Tokens(self) } } pub struct Tokens<'t>(&'t mut SqliteConnection); impl Tokens<'_> { pub async fn create(&mut self, token: &Token, secret: &Secret) -> Result<(), sqlx::Error> { sqlx::query!( r#" insert into token (id, secret, login, issued_at, last_used_at) values ($1, $2, $3, $4, $5) "#, token.id, secret, token.login, token.issued_at, token.last_used_at, ) .execute(&mut *self.0) .await?; Ok(()) } pub async fn require(&mut self, token: &Id) -> Result<(), sqlx::Error> { sqlx::query_scalar!( r#" select id as "id: Id" from token where id = $1 "#, token, ) .fetch_one(&mut *self.0) .await?; Ok(()) } pub async fn revoke(&mut self, token: &Token) -> Result<(), sqlx::Error> { sqlx::query!( r#" delete from token where id = $1 "#, token.id, ) .execute(&mut *self.0) .await?; Ok(()) } // Revoke tokens for a login pub async fn revoke_all(&mut self, login: &Login) -> Result, sqlx::Error> { let tokens = sqlx::query_scalar!( r#" delete from token where login = $1 returning id as "id: Id" "#, login.id, ) .fetch_all(&mut *self.0) .await?; Ok(tokens) } // Expire and delete all tokens that haven't been used more recently than // `expire_at`. pub async fn expire(&mut self, expire_at: &DateTime) -> Result, sqlx::Error> { let tokens = sqlx::query_scalar!( r#" delete from token where last_used_at < $1 returning id as "id: Id" "#, expire_at, ) .fetch_all(&mut *self.0) .await?; Ok(tokens) } pub async fn validate( &mut self, secret: &Secret, used_at: &DateTime, ) -> 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 // queries is fine, but it feels untidy. let token = sqlx::query!( r#" update token set last_used_at = $1 where secret = $2 returning id as "id: Id", login as "login: login::Id", issued_at as "issued_at: DateTime", last_used_at as "last_used_at: DateTime" "#, used_at, secret, ) .map(|row| Token { id: row.id, login: row.login, issued_at: row.issued_at, last_used_at: row.last_used_at, }) .fetch_one(&mut *self.0) .await?; let user = sqlx::query!( r#" select id as "id: login::Id", display_name, canonical_name from login where id = $1 "#, token.login, ) .map(|row| { Ok::<_, name::Error>(Login { id: row.id, name: Name::new(row.display_name, row.canonical_name)?, }) }) .fetch_one(&mut *self.0) .await??; Ok((token, user)) } } #[derive(Debug, thiserror::Error)] #[error(transparent)] pub enum LoadError { Database(#[from] sqlx::Error), Name(#[from] name::Error), } impl NotFound for Result { type Ok = T; type Error = LoadError; fn optional(self) -> Result, LoadError> { match self { Ok(value) => Ok(Some(value)), Err(LoadError::Database(sqlx::Error::RowNotFound)) => Ok(None), Err(other) => Err(other), } } }