mod rocksdb; use argon2::{password_hash::SaltString, Argon2, PasswordHash, PasswordHasher, PasswordVerifier}; use axum::async_trait; use rand::rngs::OsRng; use serde::{Deserialize, Serialize}; use uuid::Uuid; /// A user corresponds to an actual end user that can login to the service, /// objects aren't directly stored under users though - users are granted /// access to a set of accounts that objects are stored under. /// /// Each user automatically has a "personal" account created for them. #[derive(Serialize, Deserialize)] pub struct User { pub id: Uuid, pub username: String, password: String, } impl User { /// Builds a new `User` with the given username and password. pub fn new(username: String, password: &str) -> Self { let password = Argon2::default() .hash_password(password.as_bytes(), &SaltString::generate(&mut OsRng)) .unwrap() .to_string(); Self { id: Uuid::new_v4(), username, password, } } /// Verifies if the given password is valid for the user. pub fn verify_password(&self, password: &str) -> bool { let parsed_hash = PasswordHash::new(&self.password).unwrap(); Argon2::default() .verify_password(password.as_bytes(), &parsed_hash) .is_ok() } } #[async_trait] pub trait UserProvider { type Error; async fn increment_seq_number_for_user(&self, user: Uuid) -> Result<(), Self::Error>; async fn fetch_seq_number_for_user(&self, user: Uuid) -> Result; async fn has_any_users(&self) -> Result; async fn create_user(&self, user: User) -> Result<(), Self::Error>; async fn get_by_username(&self, username: &str) -> Result, Self::Error>; } /// An entity which contains many objects, these can be shared among users. #[derive(Serialize, Deserialize, Debug)] pub struct Account { /// ID of the account pub id: Uuid, /// A user-friendly name for the account. pub name: String, /// Whether or not the account is a user's primary account. pub is_personal: bool, /// Whether or not the entire account is read-only. pub is_read_only: bool, } impl Account { pub fn new(name: String, is_personal: bool, is_read_only: bool) -> Self { Self { id: Uuid::new_v4(), name, is_personal, is_read_only, } } } #[async_trait] pub trait AccountProvider { type Error; /// Creates or updates an account in the data store. async fn create_account(&self, account: Account) -> Result<(), Self::Error>; /// Grants a user access to an account. async fn attach_account_to_user( &self, account: Uuid, user: Uuid, access: AccountAccessLevel, ) -> Result<(), Self::Error>; /// Fetches a list of accounts for the given user. async fn get_accounts_for_user(&self, user_id: Uuid) -> Result, Self::Error>; } #[repr(u8)] pub enum AccountAccessLevel { Owner, } #[derive(Deserialize)] #[serde(tag = "type")] pub enum StoreConfig { #[serde(rename = "rocksdb")] RocksDb(rocksdb::Config), } pub enum Store { RocksDb(rocksdb::RocksDb), } impl Store { pub fn from_config(config: StoreConfig) -> Self { match config { StoreConfig::RocksDb(config) => Self::RocksDb(rocksdb::RocksDb::new(config)), } } } #[async_trait] impl AccountProvider for Store { type Error = rocksdb::Error; async fn create_account(&self, account: Account) -> Result<(), Self::Error> { match self { Store::RocksDb(db) => db.create_account(account).await, } } async fn attach_account_to_user( &self, account: Uuid, user: Uuid, access: AccountAccessLevel, ) -> Result<(), Self::Error> { match self { Store::RocksDb(db) => db.attach_account_to_user(account, user, access).await, } } async fn get_accounts_for_user(&self, user_id: Uuid) -> Result, Self::Error> { match self { Store::RocksDb(db) => db.get_accounts_for_user(user_id).await, } } } #[async_trait] impl UserProvider for Store { type Error = rocksdb::Error; async fn increment_seq_number_for_user(&self, user: Uuid) -> Result<(), Self::Error> { match self { Store::RocksDb(db) => db.increment_seq_number_for_user(user).await, } } async fn fetch_seq_number_for_user(&self, user: Uuid) -> Result { match self { Store::RocksDb(db) => db.fetch_seq_number_for_user(user).await, } } /// Checks if any users have been registered to decide whether a root /// account should be created at boot. async fn has_any_users(&self) -> Result { match self { Store::RocksDb(db) => db.has_any_users().await, } } /// Creates or updates a user in the store. async fn create_user(&self, user: User) -> Result<(), Self::Error> { match self { Store::RocksDb(db) => db.create_user(user).await, } } /// Fetches a user by their username. async fn get_by_username(&self, username: &str) -> Result, Self::Error> { match self { Store::RocksDb(db) => db.get_by_username(username).await, } } }