use std::sync::Arc; use base64::{prelude::BASE64_STANDARD, Engine}; use hmac::{Hmac, Mac}; use md5::Md5; use rand::{ distributions::{Alphanumeric, DistString}, thread_rng, }; use secrecy::{ExposeSecret, SecretVec}; use thiserror::Error; use time::{macros::format_description, OffsetDateTime}; use tokio::sync::Mutex; use tracing::{event, instrument, Level}; use crate::{ db::{/* Database, */ Database, SqliteDatabase}, secrets::{Password, ServerHash, ServerPadlock, UserServerKey, UserToken}, }; #[derive(Debug, Error)] pub enum AuthenticationError { #[error("Invalid username or password")] InvalidUserOrPassword, #[error("Invalid token")] InvalidToken, #[error("Invalid server hash")] InvalidServerHash, #[error("Authentication backend error")] Backend(#[from] sqlx::Error), } #[derive(Debug)] pub struct UserAuthenticator { db: Arc>, } impl UserAuthenticator { const TOKEN_LEN: usize = 30; pub fn new(db: Arc>) -> Self { Self { db } } #[instrument] pub async fn create_user_token( &mut self, username: &str, password: &Password, ) -> Result { // TODO: validate password let new_token = UserToken::from(Alphanumeric.sample_string(&mut thread_rng(), Self::TOKEN_LEN)); let mut db = self.db.lock().await; db.save_token(username, &new_token).await?; Ok(new_token) } #[instrument] pub async fn verify_user_token( &self, username: &str, token: &UserToken, ) -> Result<(), AuthenticationError> { let mut db = self.db.lock().await; match db.get_token(username).await? { Some(_) => Ok(()), None => Err(AuthenticationError::InvalidToken), } } } pub struct ServerPadlockGenerator { secret: SecretVec, } impl ServerPadlockGenerator { pub fn new(/* secret */) -> Self { todo!() } pub fn generate_padlock(&self, server_hash: &ServerHash) -> ServerPadlock { todo!() } } pub struct UserServerKeyGenerator { user_authenticator: Arc, padlock_generator: Arc, } impl UserServerKeyGenerator { pub fn new(/* UserAuthenticator, ServerPadlockGenerator? */) -> Self { todo!() } pub async fn generate_user_server_key( &self, username: &str, token: &UserToken, server_hash: &ServerHash, ) -> Result<(UserServerKey, String), AuthenticationError> { self.user_authenticator .verify_user_token(username, token) .await?; let padlock = self.padlock_generator.generate_padlock(server_hash); let timestamp = OffsetDateTime::now_utc() .format(format_description!( "[year repr:last_two][month][day][hour repr:24][minute][second]" )) .expect("timestamp format should be validated at compile-time"); event!(Level::DEBUG, timestamp, "Generating user_server_key"); let mut mac: Hmac = Hmac::new_from_slice(padlock.0.expose_secret().as_bytes()) .map_err(|_e| AuthenticationError::InvalidServerHash)?; mac.update(format!("{}_{}_{}", username, padlock.0.expose_secret(), timestamp).as_bytes()); let user_server_key = UserServerKey(BASE64_STANDARD.encode(mac.finalize().into_bytes()).into()); Ok((user_server_key, timestamp)) } }