2020-11-19 18:20:14 +00:00
|
|
|
//! 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 the current user. 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.
|
|
|
|
//!
|
|
|
|
//! [`PartialUser`] is the main way of modifying data stored for users who aren't
|
|
|
|
//! currently connecting. These are mainly obtained through the
|
|
|
|
//! [`UserManager::lookup_user()`] method. Unlinke with [`SignedInUser`], these are not
|
|
|
|
//! committed on drop, and [`PartialUser::store()`] must be manually called
|
2020-11-16 06:13:16 +00:00
|
|
|
use rustls::Certificate;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use sled::Transactional;
|
|
|
|
|
|
|
|
use crate::user_management::UserManager;
|
|
|
|
use crate::user_management::Result;
|
|
|
|
use crate::user_management::manager::CertificateData;
|
|
|
|
|
|
|
|
/// An struct corresponding to the data stored in the user tree
|
2020-11-16 14:21:24 +00:00
|
|
|
///
|
|
|
|
/// In order to generate a full user obj, you need to perform a lookup with a specific
|
|
|
|
/// certificate.
|
2020-11-16 06:13:16 +00:00
|
|
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
2020-11-16 14:21:24 +00:00
|
|
|
pub struct PartialUser<UserData> {
|
|
|
|
pub data: UserData,
|
|
|
|
pub certificates: Vec<u32>,
|
|
|
|
pub pass_hash: Option<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<UserData> PartialUser<UserData> {
|
|
|
|
|
|
|
|
/// A list of certificate hashes registered to this user
|
|
|
|
///
|
2020-11-19 18:20:14 +00:00
|
|
|
/// Can be looked up using [`UserManager::lookup_certificate()`] to get full information
|
2020-11-16 14:21:24 +00:00
|
|
|
pub fn certificates(&self) -> &Vec<u32> {
|
|
|
|
&self.certificates
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The bcrypt hash of the user's password
|
|
|
|
pub fn pass_hash(&self) -> Option<&str> {
|
|
|
|
self.pass_hash.as_ref().map(|s| s.as_str())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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
|
|
|
|
pub fn store(&self, tree: &sled::Tree, username: impl AsRef<[u8]>) -> Result<()>
|
|
|
|
where
|
|
|
|
UserData: Serialize
|
|
|
|
{
|
|
|
|
tree.insert(
|
|
|
|
&username,
|
|
|
|
bincode::serialize(&self)?,
|
|
|
|
)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<UserData> AsRef<UserData> for PartialUser<UserData> {
|
|
|
|
/// Access any data the application has stored for the user.
|
|
|
|
fn as_ref(&self) -> &UserData {
|
|
|
|
&self.data
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<UserData> AsMut<UserData> for PartialUser<UserData> {
|
|
|
|
/// Modify the data stored for a user
|
|
|
|
///
|
|
|
|
/// IMPORTANT: Changes will not be written to the database until
|
|
|
|
/// [`PartialUser::store()`] is called
|
|
|
|
fn as_mut(&mut self) -> &mut UserData {
|
|
|
|
&mut self.data
|
|
|
|
}
|
2020-11-16 06:13:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Any information about the connecting user
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub enum User<UserData> {
|
|
|
|
/// 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<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: Certificate,
|
|
|
|
pub (crate) manager: UserManager,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl NotSignedInUser {
|
2020-11-19 18:20:14 +00:00
|
|
|
/// 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.
|
2020-11-16 06:13:16 +00:00
|
|
|
///
|
|
|
|
/// # 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>(
|
|
|
|
self,
|
|
|
|
username: String,
|
|
|
|
) -> Result<SignedInUser<UserData>>
|
|
|
|
where
|
|
|
|
UserData: Serialize + Default
|
|
|
|
{
|
|
|
|
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 {
|
2020-11-16 14:21:24 +00:00
|
|
|
inner: PartialUser {
|
2020-11-16 06:13:16 +00:00
|
|
|
data: UserData::default(),
|
|
|
|
certificates: vec![cert_hash],
|
|
|
|
pass_hash: None,
|
|
|
|
},
|
|
|
|
username: username.clone(),
|
|
|
|
active_certificate: self.certificate.clone(),
|
|
|
|
manager: self.manager,
|
|
|
|
};
|
|
|
|
|
|
|
|
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
|
|
|
|
///
|
2020-11-19 18:20:14 +00:00
|
|
|
/// 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.
|
2020-11-16 06:13:16 +00:00
|
|
|
///
|
|
|
|
/// # 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<'de, UserData>(
|
|
|
|
username: impl AsRef<[u8]>,
|
|
|
|
password: impl AsRef<[u8]>,
|
|
|
|
) -> Result<SignedInUser<UserData>>
|
|
|
|
where
|
|
|
|
UserData: Serialize + Deserialize<'de>
|
|
|
|
{
|
|
|
|
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<UserData> {
|
|
|
|
pub (crate) username: String,
|
|
|
|
pub (crate) active_certificate: Certificate,
|
|
|
|
pub (crate) manager: UserManager,
|
2020-11-16 14:21:24 +00:00
|
|
|
pub (crate) inner: PartialUser<UserData>,
|
2020-11-16 06:13:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<UserData> SignedInUser<UserData> {
|
|
|
|
/// Get the [`Certificate`] that the user is currently using to connect.
|
|
|
|
pub const fn active_certificate(&self) -> &Certificate {
|
|
|
|
&self.active_certificate
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Produce a list of all [`Certificate`]s registered to this account
|
|
|
|
pub fn all_certificates(&self) -> Vec<Certificate> {
|
|
|
|
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 const 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<bool> {
|
|
|
|
if let Some(hash) = &self.inner.pass_hash {
|
|
|
|
Ok(bcrypt::verify(try_password, hash.as_str())?)
|
|
|
|
} else {
|
|
|
|
Err(super::UserManagerError::PasswordNotSet)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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(&self) -> Result<()>
|
|
|
|
where
|
|
|
|
UserData: Serialize
|
|
|
|
{
|
2020-11-16 14:21:24 +00:00
|
|
|
self.inner.store(&self.manager.users, &self.username)
|
2020-11-16 06:13:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<UserData> AsRef<UserData> for SignedInUser<UserData> {
|
|
|
|
fn as_ref(&self) -> &UserData {
|
|
|
|
&self.inner.data
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<UserData> AsMut<UserData> for SignedInUser<UserData> {
|
|
|
|
/// NOTE: Changes made to the user data won't be persisted until SignedInUser::save
|
|
|
|
/// is called
|
|
|
|
fn as_mut(&mut self) -> &mut UserData {
|
|
|
|
&mut self.inner.data
|
|
|
|
}
|
|
|
|
}
|