use serde::{Serialize, de::DeserializeOwned}; use std::convert::TryInto; use crate::user_management::{User, Result}; use crate::user_management::user::{RegisteredUser, NotSignedInUser, PartialUser}; #[derive(Debug, Clone)] /// A struct containing information for managing users. /// /// Wraps a [`sled::Db`] pub struct UserManager { /// Allows access to the [`sled::Db`] used by the UserManager to store user data. /// /// Do not try to use this database to access user information, and instead prefer /// methods such as [`lookup_user()`] and [`lookup_certificate()`]. /// /// However, you're welcome to use the database to store you own data without needing /// to run parallel sled databases. It's recommended that any trees you open be /// namespaced like `tld.yourdomain.projectname.treename` in order to prevent /// conflict. /// /// [`lookup_user()`]: Self::lookup_user /// [`lookup_certificate()`]: Self::lookup_certificate pub db: sled::Db, pub (crate) users: sled::Tree, // user_id:u64 maps to data:PartialUser pub (crate) certificates: sled::Tree, // fingerprint:[u8; 32] maps to uid:u64 pub (crate) usernames: sled::Tree, // username:String maps to uid:u64 } impl UserManager { /// Create or open a new UserManager /// /// The `dir` argument is the path to a data directory, to be populated using sled. /// This will be created if it does not exist. pub fn new(db: sled::Db) -> Result { Ok(Self { users: db.open_tree("gay.emii.kochab.users")?, certificates: db.open_tree("gay.emii.kochab.certificates")?, usernames: db.open_tree("gay.emii.kochab.usernames")?, db, }) } /// Lookup the owner of a certificate based on it's fingerprint /// /// # Errors /// An error is thrown if there is an error reading from the database or if data /// recieved from the database is corrupt /// /// [`None`] can be returned if their is no user with this certificate. pub fn lookup_certificate( &self, cert: [u8; 32] ) -> Result>> { if let Some(bytes) = self.certificates.get(cert)? { let id = u64::from_le_bytes(bytes.as_ref().try_into()?); Ok(Some( self.lookup_user(id)? .ok_or(super::DeserializeError::InvalidReference(id))? )) } else { Ok(None) } } /// Get the user with the specified username /// /// # Errors /// An error is thrown if there is an error reading from the database or if data /// recieved from the database is corrupt /// /// [`None`] can be returned if their is no user with this username. pub fn lookup_username( &self, username: impl AsRef ) -> Result>> { if let Some(bytes) = self.usernames.get(username.as_ref())? { let id = u64::from_le_bytes(bytes.as_ref().try_into()?); Ok(Some( self.lookup_user(id)? .ok_or(super::DeserializeError::InvalidReference(id))? )) } else { Ok(None) } } /// Lookup a user by uid /// /// # Errors /// An error is thrown if there is an error reading from the database or if data /// recieved from the database is corrupt pub fn lookup_user( &self, uid: u64, ) -> Result>> where UserData: Serialize + DeserializeOwned { if let Some(bytes) = self.users.get(uid.to_le_bytes())? { let inner: PartialUser = bincode::deserialize_from(bytes.as_ref())?; Ok(Some(RegisteredUser::new(uid, None, self.clone(), inner))) } else { Ok(None) } } /// Produce a list of all users in the database /// /// # Panics /// An panics if there is an error reading from the database or if data recieved from /// the database is corrupt pub fn all_users( &self, ) -> impl Iterator>> where UserData: Serialize + DeserializeOwned { let this = self.clone(); self.users.iter() .map(move|result| { let (uid, bytes) = result?; let inner: PartialUser = bincode::deserialize_from(bytes.as_ref())?; let uid = u64::from_le_bytes(uid.as_ref().try_into()?); Ok(RegisteredUser::new(uid, None, this.clone(), inner)) }) } /// Attempt to determine the user who sent a request based on the certificate. /// /// # Errors /// An error is thrown if there is an error reading from the database or if data /// recieved from the database is corrupt /// /// # Panics /// Pancis if the database is corrupt pub fn get_user_by_cert( &self, cert: Option<&[u8; 32]> ) -> Result> where UserData: Serialize + DeserializeOwned { if let Some(certificate) = cert { if let Some(user_inner) = self.lookup_certificate(*certificate)? { Ok(User::SignedIn(user_inner.with_cert(*certificate))) } else { Ok(User::NotSignedIn(NotSignedInUser { certificate: *certificate, manager: self.clone(), })) } } else { Ok(User::Unauthenticated) } } }