diff --git a/src/user_management/user.rs b/src/user_management/user.rs index 049c0cc..06617c1 100644 --- a/src/user_management/user.rs +++ b/src/user_management/user.rs @@ -113,39 +113,21 @@ impl NotSignedInUser { if self.manager.users.contains_key(username.as_str())? { Err(super::UserManagerError::UsernameNotUnique) } else { - let cert_hash = UserManager::hash_certificate(&self.certificate); - - let newser = RegisteredUser::new( - username.clone(), + let mut newser = RegisteredUser::new( + username, Some(self.certificate.clone()), self.manager, PartialUser { data: UserData::default(), - certificates: vec![cert_hash], + certificates: Vec::with_capacity(1), 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 + // 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) } @@ -158,8 +140,19 @@ impl NotSignedInUser { /// 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. + /// 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 username and password are incorrect, or if the user has yet @@ -168,10 +161,23 @@ impl NotSignedInUser { /// 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!() + self, + username: impl AsRef, + password: Option>, + ) -> Result>> { + 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); + } + } + + user.add_certificate(self.certificate)?; + Ok(Some(user)) + } else { + Ok(None) + } } } @@ -207,6 +213,10 @@ impl RegisteredUser { } /// 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: Certificate) -> Self { self.active_certificate = Some(cert); self @@ -303,6 +313,44 @@ impl RegisteredUser { 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: Certificate) -> Result<()> { + let cert_hash = UserManager::hash_certificate(&certificate); + + self.inner.certificates.push(cert_hash); + + let cert_info = CertificateData { + certificate, + owner_username: self.username.clone(), + }; + + let inner_serialized = bincode::serialize(&self.inner)?; + let cert_info_serialized = bincode::serialize(&cert_info)?; + + (&self.manager.users, &self.manager.certificates) + .transaction(|(tx_usr, tx_crt)| { + tx_usr.insert( + self.username.as_str(), + inner_serialized.clone(), + )?; + tx_crt.insert( + cert_hash.to_le_bytes().as_ref(), + cert_info_serialized.clone(), + )?; + Ok(()) + })?; + + Ok(()) + } } impl std::ops::Drop for RegisteredUser {