kochab/src/user_management/user.rs

204 lines
6.5 KiB
Rust

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
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct UserInner<UserData> {
data: UserData,
certificates: Vec<u32>,
pass_hash: Option<String>,
}
/// 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 {
/// Register a new user with this certificate
///
/// # 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 {
inner: UserInner {
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
///
/// Try to add this certificate to another user using a username and password
///
/// # 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,
pub (crate) inner: UserInner<UserData>,
}
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
{
self.manager.users.insert(
&self.username,
bincode::serialize(&self.inner)?,
)?;
Ok(())
}
}
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
}
}