use sqlx::sqlite::SqlitePool; use super::repo::auth::Provider as _; use crate::{ clock::DateTime, error::BoxedError, password::StoredHash, repo::{ login::{Login, Provider as _}, token::Provider as _, }, }; pub struct Logins<'a> { db: &'a SqlitePool, } impl<'a> Logins<'a> { pub const fn new(db: &'a SqlitePool) -> Self { Self { db } } pub async fn login( &self, name: &str, password: &str, login_at: DateTime, ) -> Result, BoxedError> { let mut tx = self.db.begin().await?; let login = if let Some((login, stored_hash)) = tx.auth().for_name(name).await? { if stored_hash.verify(password)? { // Password verified; use the login. Some(login) } else { // Password NOT verified. None } } else { let password_hash = StoredHash::new(password)?; Some(tx.logins().create(name, &password_hash).await?) }; // If `login` is Some, then we have an identity and can issue a token. // If `login` is None, then neither creating a new login nor // authenticating an existing one succeeded, and we must reject the // login attempt. let token = if let Some(login) = login { Some(tx.tokens().issue(&login, login_at).await?) } else { None }; tx.commit().await?; Ok(token) } pub async fn validate( &self, secret: &str, used_at: DateTime, ) -> Result, BoxedError> { let mut tx = self.db.begin().await?; tx.tokens().expire(used_at).await?; let login = tx.tokens().validate(secret, used_at).await?; tx.commit().await?; Ok(login) } pub async fn logout(&self, secret: &str) -> Result<(), BoxedError> { let mut tx = self.db.begin().await?; tx.tokens().revoke(secret).await?; tx.commit().await?; Ok(()) } }