kochab/src/user_management/manager.rs

122 lines
4.0 KiB
Rust

use rustls::Certificate;
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use std::path::Path;
use crate::user_management::{User, Result};
use crate::user_management::user::{SignedInUser, NotSignedInUser, UserInner};
#[derive(Debug, Clone, Deserialize, Serialize)]
/// Data stored in the certificate tree about a certain certificate
pub struct CertificateData {
#[serde(with = "CertificateDef")]
pub certificate: Certificate,
pub owner_username: String,
}
#[derive(Serialize, Deserialize)]
#[serde(remote = "Certificate")]
struct CertificateDef(Vec<u8>);
#[derive(Debug, Clone)]
/// A struct containing information for managing users.
///
/// Wraps a [`sled::Db`]
pub struct UserManager {
db: sled::Db,
pub (crate) users: sled::Tree, // user_id:String maps to data:UserData
pub (crate) certificates: sled::Tree, // certificate:u64 maps to data:CertificateData
}
impl UserManager {
/// Create or open a new UserManager
///
/// The `dir` argument is the path to a data directory, to be populated using sled.
/// This will be created if it does not exist.
pub fn new(dir: impl AsRef<Path>) -> Result<Self> {
let db = sled::open(dir)?;
Ok(Self {
users: db.open_tree("users")?,
certificates: db.open_tree("certificates")?,
db,
})
}
/// Produce a u32 hash from a certificate, used for [`lookup_certficate()`]
pub fn hash_certificate(cert: &Certificate) -> u32 {
let mut hasher = crc32fast::Hasher::new();
hasher.update(cert.0.as_ref());
hasher.finalize()
}
/// Lookup information about a certificate based on it's u32 hash
///
/// # Errors
/// An error is thrown if there is an error reading from the database or if data
/// recieved from the database is corrupt
pub fn lookup_certificate(&self, cert: u32) -> Result<Option<CertificateData>> {
if let Some(bytes) = self.certificates.get(cert.to_le_bytes())? {
Ok(Some(bincode::deserialize(&bytes)?))
} else {
Ok(None)
}
}
/// Lookup information about a user by username
///
/// # Errors
/// An error is thrown if there is an error reading from the database or if data
/// recieved from the database is corrupt
pub fn lookup_user<'de, UserData>(
&self,
username: impl AsRef<[u8]>
) -> Result<Option<UserInner<UserData>>>
where
UserData: Serialize + DeserializeOwned
{
if let Some(bytes) = self.users.get(username)? {
Ok(Some(bincode::deserialize_from(bytes.as_ref())?))
} else {
Ok(None)
}
}
/// Attempt to determine the user who sent a request based on the certificate.
///
/// # Errors
/// An error is thrown if there is an error reading from the database or if data
/// recieved from the database is corrupt
///
/// # Panics
/// Pancis if the database is corrupt
pub fn get_user<'de, UserData>(
&self,
cert: Option<&Certificate>
) -> Result<User<UserData>>
where
UserData: Serialize + DeserializeOwned
{
if let Some(certificate) = cert {
let cert_hash = Self::hash_certificate(certificate);
if let Some(certificate_data) = self.lookup_certificate(cert_hash)? {
let user_inner = self.lookup_user(&certificate_data.owner_username)?
.expect("Database corruption: Certificate data refers to non-existant user");
Ok(User::SignedIn(SignedInUser {
username: certificate_data.owner_username,
active_certificate: certificate_data.certificate,
manager: self.clone(),
inner: user_inner,
}))
} else {
Ok(User::NotSignedIn(NotSignedInUser {
certificate: certificate.clone(),
manager: self.clone(),
}))
}
} else {
Ok(User::Unauthenticated)
}
}
}