use rustls::Certificate; use serde::{Deserialize, Serialize, de::DeserializeOwned}; use std::path::Path; use crate::user_management::{User, Result}; use crate::user_management::user::{SignedInUser, NotSignedInUser, PartialUser}; #[derive(Debug, Clone, Deserialize, Serialize)] /// Data stored in the certificate tree about a certain certificate pub struct CertificateData { #[serde(with = "CertificateDef")] /// The certificate in question pub certificate: Certificate, /// The username of the user to which this certificate is registered pub owner_username: String, } #[derive(Serialize, Deserialize)] #[serde(remote = "Certificate")] struct CertificateDef(Vec); #[derive(Debug, Clone)] /// A struct containing information for managing users. /// /// Wraps a [`sled::Db`] pub struct UserManager { db: sled::Db, pub (crate) users: sled::Tree, // user_id:String maps to data:UserData pub (crate) certificates: sled::Tree, // certificate:u64 maps to data:CertificateData } 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(dir: impl AsRef) -> Result { let db = sled::open(dir)?; Ok(Self { users: db.open_tree("users")?, certificates: db.open_tree("certificates")?, db, }) } /// Produce a u32 hash from a certificate, used for [`lookup_certificate()`](Self::lookup_certificate()) pub fn hash_certificate(cert: &Certificate) -> u32 { let mut hasher = crc32fast::Hasher::new(); hasher.update(cert.0.as_ref()); hasher.finalize() } /// Lookup information about a certificate based on it's u32 hash /// /// # 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_certificate(&self, cert: u32) -> Result> { if let Some(bytes) = self.certificates.get(cert.to_le_bytes())? { Ok(Some(bincode::deserialize(&bytes)?)) } else { Ok(None) } } /// Lookup information about a user by username /// /// # 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, username: impl AsRef ) -> Result>> where UserData: Serialize + DeserializeOwned { if let Some(bytes) = self.users.get(username.as_ref())? { let inner: PartialUser = bincode::deserialize_from(bytes.as_ref())?; Ok(Some(SignedInUser::new(username.as_ref().to_owned(), None, self.clone(), inner))) } else { Ok(None) } } /// 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( &self, cert: Option<&Certificate> ) -> Result> where UserData: Serialize + DeserializeOwned { if let Some(certificate) = cert { let cert_hash = Self::hash_certificate(certificate); if let Some(certificate_data) = self.lookup_certificate(cert_hash)? { let user_inner = self.lookup_user(&certificate_data.owner_username)? .expect("Database corruption: Certificate data refers to non-existant user"); Ok(User::SignedIn(user_inner.with_cert(certificate_data.certificate))) } else { Ok(User::NotSignedIn(NotSignedInUser { certificate: certificate.clone(), manager: self.clone(), })) } } else { Ok(User::Unauthenticated) } } }