//! Several structs representing data about users //! //! This module contains any structs needed to store and retrieve data about users. The //! different varieties have different purposes and come from different places. //! //! [`User`] is the most common for of user struct, and typically comes from calling //! [`Request::user()`](crate::types::Request::user()). This is an enum with several //! variants, and can be specialized into a [`NotSignedInUser`] or a [`SignedInUser`] if //! the user has presented a certificate. These two subtypes have more specific //! information, like the user's username and active certificate. //! //! [`SignedInUser`] is particularly signifigant in that this is the struct used to modify //! the data stored for almost all users. This is accomplished through the //! [`as_mut()`](SignedInUser::as_mut) method. Changes made this way must be persisted //! using [`save()`](SignedInUser::save()) or by dropping the user. use rustls::Certificate; use serde::{Deserialize, Serialize, de::DeserializeOwned}; use sled::Transactional; use crate::user_management::UserManager; use crate::user_management::Result; use crate::user_management::manager::CertificateData; const ARGON2_CONFIG: argon2::Config = argon2::Config { ad: &[], hash_length: 32, lanes: 1, mem_cost: 4096, secret: &[], thread_mode: argon2::ThreadMode::Sequential, time_cost: 3, variant: argon2::Variant::Argon2id, version: argon2::Version::Version13, }; lazy_static::lazy_static! { static ref RANDOM: ring::rand::SystemRandom = ring::rand::SystemRandom::new(); } /// An struct corresponding to the data stored in the user tree /// /// In order to generate a full user obj, you need to perform a lookup with a specific /// certificate. #[derive(Clone, Debug, Deserialize, Serialize)] pub (crate) struct PartialUser { pub data: UserData, pub certificates: Vec, pub pass_hash: Option<(Vec, [u8; 32])>, } impl PartialUser { /// Write user data to the database /// /// This MUST be called if the user data is modified using the AsMut trait, or else /// changes will not be written to the database fn store(&self, tree: &sled::Tree, username: impl AsRef<[u8]>) -> Result<()> where UserData: Serialize { tree.insert( &username, bincode::serialize(&self)?, )?; Ok(()) } } /// Any information about the connecting user #[derive(Clone, Debug)] pub enum User { /// A user who is connected without using a client certificate. /// /// This could be a user who has an account but just isn't presenting a certificate at /// the minute, a user whose client does not support client certificates, or a user /// who has not yet created a certificate for the site Unauthenticated, /// A user who is connecting with a certificate that isn't connected to an account /// /// This is typically a new user who hasn't set up an account yet, or a user /// connecting with a new certificate that needs to be added to an existing account. NotSignedIn(NotSignedInUser), /// A user connecting with an identified account SignedIn(SignedInUser), } #[derive(Clone, Debug)] /// Data about a user with a certificate not associated with an account /// /// For more information about the user lifecycle and sign-in stages, see [`User`] pub struct NotSignedInUser { pub (crate) certificate: Certificate, pub (crate) manager: UserManager, } impl NotSignedInUser { /// Register a new user with this certificate. /// /// This creates a new user & user data entry in the database with the given username. /// From now on, when this user logs in with this certificate, they will be /// automatically authenticated, and their user data automatically retrieved. /// /// # Errors /// The provided username must be unique, or else an error will be raised. /// /// Additional errors might occur if there is a problem writing to the database pub fn register( self, username: String, ) -> Result> { if self.manager.users.contains_key(username.as_str())? { Err(super::UserManagerError::UsernameNotUnique) } else { let cert_hash = UserManager::hash_certificate(&self.certificate); let newser = SignedInUser::new( username.clone(), Some(self.certificate.clone()), self.manager, PartialUser { data: UserData::default(), certificates: vec![cert_hash], pass_hash: None, }, ); let cert_info = CertificateData { certificate: self.certificate, owner_username: username, }; let newser_serialized = bincode::serialize(&newser.inner)?; let cert_info_serialized = bincode::serialize(&cert_info)?; (&newser.manager.users, &newser.manager.certificates) .transaction(|(tx_usr, tx_crt)| { tx_usr.insert( newser.username.as_str(), newser_serialized.clone(), )?; tx_crt.insert( cert_hash.to_le_bytes().as_ref(), cert_info_serialized.clone(), )?; Ok(()) })?; //TODO Ok(newser) } } /// Attach this certificate to an existing user /// /// Try to add this certificate to another user using a username and password. If /// successful, the user this certificate is attached to will be able to automatically /// log in with either this certificate or any of the certificates they already had /// registered. /// /// This method returns the new SignedInUser instance representing the now-attached /// user. /// /// # Errors /// This will error if the username and password are incorrect, or if the user has yet /// to set a password. /// /// Additional errors might occur if an error occurs during database lookup and /// deserialization pub fn attach( username: impl AsRef<[u8]>, password: impl AsRef<[u8]>, ) -> Result> { todo!() } } #[derive(Clone, Debug)] /// Data about a logged in user /// /// For more information about the user lifecycle and sign-in stages, see [`User`] pub struct SignedInUser { username: String, active_certificate: Option, manager: UserManager, inner: PartialUser, /// Indicates that [`SignedInUser::as_mut()`] has been called, but [`SignedInUser::save()`] has not has_changed: bool, } impl SignedInUser { /// Create a new user from parts pub (crate) fn new( username: String, active_certificate: Option, manager: UserManager, inner: PartialUser ) -> Self { Self { username, active_certificate, manager, inner, has_changed: false, } } /// Update the active certificate pub (crate) fn with_cert(mut self, cert: Certificate) -> Self { self.active_certificate = Some(cert); self } /// Get the [`Certificate`] that the user is currently using to connect. /// /// If this user was retrieved by a [`UserManager::lookup_user()`], this will be /// [`None`]. In all other cases, this will be [`Some`]. pub fn active_certificate(&self) -> Option<&Certificate> { self.active_certificate.as_ref() } /// Produce a list of all [`Certificate`]s registered to this account pub fn all_certificates(&self) -> Vec { self.inner.certificates .iter() .map( |cid| self.manager.lookup_certificate(*cid) .expect("Database corruption: User refers to non-existant certificate") .expect("Error accessing database") .certificate ) .collect() } /// Get the user's current username. /// /// NOTE: This is not guaranteed not to change. pub fn username(&self) -> &String { &self.username } /// Check a password against the user's password hash /// /// # Errors /// An error is raised if the user has yet to set a password, or if the user's /// password hash is somehow malformed. pub fn check_password( &self, try_password: impl AsRef<[u8]> ) -> Result { if let Some((hash, salt)) = &self.inner.pass_hash { Ok(argon2::verify_raw( try_password.as_ref(), salt, hash.as_ref(), &ARGON2_CONFIG, )?) } else { Err(super::UserManagerError::PasswordNotSet) } } /// Set's the password for this user /// /// By default, users have no password, meaning the cannot add any certificates beyond /// the one they created their account with. However, by setting their password, /// users are able to add more devices to their account, and recover their account if /// it's lost. Note that this will completely overwrite the users old password. /// /// Use [`SignedInUser::check_password()`] and [`NotSignedInUser::attach()`] to check /// the password against another one, or to link a new certificate. /// /// Because this method uses a key derivation algorithm, this should be considered a /// very expensive operation. pub fn set_password( &mut self, password: impl AsRef<[u8]>, ) -> Result<()> { let salt: [u8; 32] = ring::rand::generate(&*RANDOM) .expect("Error generating random salt") .expose(); self.inner.pass_hash = Some(( argon2::hash_raw( password.as_ref(), salt.as_ref(), &ARGON2_CONFIG, )?, salt, )); Ok(()) } /// Write any updates to the user to the database. /// /// Updates caused by calling methods directly on the user do not need to be saved. /// This is only for changes made to the UserData. pub fn save(&mut self) -> Result<()> where UserData: Serialize { self.inner.store(&self.manager.users, &self.username)?; self.has_changed = false; Ok(()) } } impl std::ops::Drop for SignedInUser { fn drop(&mut self) { if self.has_changed { if let Err(e) = self.save() { error!("Failed to save user data to database: {:?}", e); } } } } impl AsRef for SignedInUser { fn as_ref(&self) -> &UserData { &self.inner.data } } impl AsMut for SignedInUser { /// NOTE: Changes made to the user data won't be persisted until SignedInUser::save /// is called fn as_mut(&mut self) -> &mut UserData { self.has_changed = true; &mut self.inner.data } }