💾 Archived View for alchemi.dev › en › projects › kochab › files › src › user_management › user.rs captured on 2022-07-16 at 16:43:55.
-=-=-=-=-=-=-
//! 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 [`RegisteredUser`] if //! the user has presented a certificate. These two subtypes have more specific //! information, like the user's username and active certificate. //! //! [`RegisteredUser`] 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()`](RegisteredUser::as_mut) method. Changes made this way must be persisted //! using [`save()`](RegisteredUser::save()) or by dropping the user. use serde::{Deserialize, Serialize, de::DeserializeOwned}; use sled::Transactional; use crate::user_management::UserManager; use crate::user_management::Result; #[cfg(feature = "user_management_advanced")] 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, }; #[cfg(feature = "user_management_advanced")] 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<UserData> { pub data: UserData, pub certificates: Vec<[u8; 32]>, #[cfg(feature = "user_management_advanced")] pub pass_hash: Option<(Vec<u8>, [u8; 32])>, } impl<UserData> PartialUser<UserData> { /// 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<UserData: Serialize + DeserializeOwned> { /// 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(RegisteredUser<UserData>), } #[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: [u8; 32], 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<UserData: Serialize + DeserializeOwned + Default>( self, username: String, ) -> Result<RegisteredUser<UserData>> { if self.manager.users.contains_key(username.as_str())? { Err(super::UserManagerError::UsernameNotUnique) } else { info!("User {} registered!", username); let mut newser = RegisteredUser::new( username, Some(self.certificate), self.manager, PartialUser { data: UserData::default(), certificates: Vec::with_capacity(1), #[cfg(feature = "user_management_advanced")] pass_hash: None, }, ); // As a nice bonus, calling add_certificate with a user not yet in the // database creates the user and adds the certificate in a single transaction. // Because of this, we can delegate here ^^ newser.add_certificate(self.certificate)?; Ok(newser) } } #[cfg(feature = "user_management_advanced")] /// 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 can check the user's password to ensure that they match before /// registering. If you want to skip this verification, perhaps because you've /// already verified that this user owns this account, then you can pass [`None`] as /// the password to skip the password check. /// /// This method returns the new RegisteredUser instance representing the now-attached /// user, or [`None`] if the username and password didn't match. /// /// Because this method both performs a bcrypt verification and a database access, it /// should be considered expensive. /// /// If you already have a [`RegisteredUser`] that you would like to attach a /// certificate to, consider using [`RegisteredUser::add_certificate()`] /// /// # Errors /// This will error 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<UserData: Serialize + DeserializeOwned>( self, username: &str, password: Option<&[u8]>, ) -> Result<Option<RegisteredUser<UserData>>> { if let Some(mut user) = self.manager.lookup_user(username)? { // Perform password check, if caller wants if let Some(password) = password { if !user.check_password(password)? { return Ok(None); } } info!("User {} attached certificate with fingerprint {:x?}", username, &self.certificate[..]); user.add_certificate(self.certificate)?; user.active_certificate = Some(self.certificate); Ok(Some(user)) } else { Ok(None) } } } #[derive(Clone, Debug)] /// Data about a logged in user /// /// For more information about the user lifecycle and sign-in stages, see [`User`] pub struct RegisteredUser<UserData: Serialize + DeserializeOwned> { username: String, active_certificate: Option<[u8; 32]>, manager: UserManager, inner: PartialUser<UserData>, /// Indicates that [`RegisteredUser::as_mut()`] has been called, but [`RegisteredUser::save()`] has not has_changed: bool, } impl<UserData: Serialize + DeserializeOwned> RegisteredUser<UserData> { /// Create a new user from parts pub (crate) fn new( username: String, active_certificate: Option<[u8; 32]>, manager: UserManager, inner: PartialUser<UserData> ) -> Self { Self { username, active_certificate, manager, inner, has_changed: false, } } /// Update the active certificate /// /// This is not to be confused with [`RegisteredUser::add_certificate`], which /// performs the database operations needed to register a new certificate to a user. /// This literally just marks the active certificate. pub (crate) fn with_cert(mut self, cert: [u8; 32]) -> Self { self.active_certificate = Some(cert); self } /// Get the fingerprint of the certificate that the user is currently using. /// /// 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<&[u8; 32]> { self.active_certificate.as_ref() } /// Produce a list of all certificate fingerprints registered to this account pub fn all_certificates(&self) -> &Vec<[u8; 32]> { &self.inner.certificates } /// Get the user's current username. /// /// NOTE: This is not guaranteed not to change. pub fn username(&self) -> &String { &self.username } #[cfg(feature = "user_management_advanced")] /// 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<bool> { 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) } } #[cfg(feature = "user_management_advanced")] /// 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 [`RegisteredUser::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, )); self.has_changed = true; 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(()) } /// Register a new certificate to this user /// /// This adds a new certificate to this user for use in logins. This requires a /// couple database accesses, one in order to link the user to the certificate, and /// one in order to link the certificate to the user. /// /// If you have a [`NotSignedInUser`] and are looking for a way to link them to an /// existing user, consider [`NotSignedInUser::attach()`], which contains facilities for /// password checking and automatically performs the user lookup. pub fn add_certificate(&mut self, certificate: [u8; 32]) -> Result<()> { self.inner.certificates.push(certificate); let inner_serialized = bincode::serialize(&self.inner)?; (&self.manager.users, &self.manager.certificates) .transaction(|(tx_usr, tx_crt)| { tx_usr.insert( self.username.as_str(), inner_serialized.clone(), )?; tx_crt.insert( &certificate, self.username.as_bytes(), )?; Ok(()) })?; Ok(()) } #[cfg(feature = "user_management_advanced")] /// Check if the user has a password set /// /// Since authentication is done using client certificates, users aren't required to /// set a password up front. In some cases, it may be useful to know if a user has or /// has not set a password yet. /// /// This returns `true` if the user has a password set, or `false` otherwise pub fn has_password(&self) -> bool { self.inner.pass_hash.is_some() } /// Get an immutable reference to the data associated with this user pub fn data(&self) -> &UserData { &self.inner.data } /// Get a mutable reference to the data associated with this user /// /// This automatically flags the user data as needing to be saved to the database, /// which automatically performs the action when this user falls out of scope. If /// need be, you can push these changes to the database sooner by calling [`save()`] /// /// [`save()`]: Self::save() pub fn mut_data(&mut self) -> &mut UserData { self.has_changed = true; &mut self.inner.data } } impl<UserData: Serialize + DeserializeOwned> std::ops::Drop for RegisteredUser<UserData> { fn drop(&mut self) { if self.has_changed { if let Err(e) = self.save() { error!("Failed to save user data to database: {:?}", e); } } } } impl<UserData: Serialize + DeserializeOwned> AsRef<UserData> for RegisteredUser<UserData> { fn as_ref(&self) -> &UserData { self.data() } } impl<UserData: Serialize + DeserializeOwned> AsMut<UserData> for RegisteredUser<UserData> { fn as_mut(&mut self) -> &mut UserData { self.mut_data() } }