// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers // All rights reserved. // Licensed under the Apache License, Version 2.0 // // or the MIT license , // at your option. All files in the project carrying such // notice may not be copied, modified, or distributed except // according to those terms. //! User account use std::collections::hash_map::DefaultHasher; use std::hash::Hash; use std::hash::Hasher; use std::time::SystemTime; use p2p_net::types::*; use p2p_repo::kcv_store::KCVStore; use p2p_repo::store::*; use p2p_repo::types::Timestamp; use serde_bare::{from_slice, to_vec}; pub struct Account<'a> { /// User ID id: UserId, store: &'a dyn KCVStore, } impl<'a> Account<'a> { const PREFIX: u8 = b"u"[0]; const PREFIX_CLIENT: u8 = b"d"[0]; // propertie's suffixes const CLIENT: u8 = b"c"[0]; const ADMIN: u8 = b"a"[0]; //const OVERLAY: u8 = b"o"[0]; // propertie's client suffixes const INFO: u8 = b"i"[0]; const LAST_SEEN: u8 = b"l"[0]; const ALL_PROPERTIES: [u8; 2] = [Self::CLIENT, Self::ADMIN]; const ALL_CLIENT_PROPERTIES: [u8; 2] = [Self::INFO, Self::LAST_SEEN]; const SUFFIX_FOR_EXIST_CHECK: u8 = Self::ADMIN; pub fn open(id: &UserId, store: &'a dyn KCVStore) -> Result, StorageError> { let opening = Account { id: id.clone(), store, }; if !opening.exists() { return Err(StorageError::NotFound); } Ok(opening) } pub fn create( id: &UserId, admin: bool, store: &'a dyn KCVStore, ) -> Result, StorageError> { let acc = Account { id: id.clone(), store, }; if acc.exists() { return Err(StorageError::AlreadyExists); } store.put( Self::PREFIX, &to_vec(&id)?, Some(Self::ADMIN), to_vec(&admin)?, )?; Ok(acc) } pub fn get_all_users( admins: bool, store: &'a dyn KCVStore, ) -> Result, StorageError> { let size = to_vec(&UserId::nil())?.len(); let mut res: Vec = vec![]; for user in store.get_all_keys_and_values(Self::PREFIX, size, Some(Self::ADMIN))? { let admin: bool = from_slice(&user.1)?; if admin == admins { let id: UserId = from_slice(&user.0[1..user.0.len() - 1])?; res.push(id); } } Ok(res) } pub fn exists(&self) -> bool { self.store .get( Self::PREFIX, &to_vec(&self.id).unwrap(), Some(Self::SUFFIX_FOR_EXIST_CHECK), ) .is_ok() } pub fn id(&self) -> UserId { self.id } pub fn add_client(&self, client: &ClientId, info: &ClientInfo) -> Result<(), StorageError> { if !self.exists() { return Err(StorageError::BackendError); } let mut s = DefaultHasher::new(); info.hash(&mut s); let hash = s.finish(); let client_key = (client.clone(), hash); let client_key_ser = to_vec(&client_key)?; let info_ser = to_vec(info)?; self.store.write_transaction(&|tx| { if tx .has_property_value( Self::PREFIX, &to_vec(&self.id)?, Some(Self::CLIENT), &client_key_ser, ) .is_err() { tx.put( Self::PREFIX, &to_vec(&self.id)?, Some(Self::CLIENT), &client_key_ser, )?; } if tx .has_property_value( Self::PREFIX_CLIENT, &client_key_ser, Some(Self::INFO), &info_ser, ) .is_err() { tx.put( Self::PREFIX_CLIENT, &client_key_ser, Some(Self::INFO), &info_ser, )?; } let now = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap() .as_secs(); tx.replace( Self::PREFIX_CLIENT, &client_key_ser, Some(Self::LAST_SEEN), &to_vec(&now)?, )?; Ok(()) }) } // pub fn has_client(&self, client: &ClientId) -> Result<(), StorageError> { // self.store.has_property_value( // Self::PREFIX, // &to_vec(&self.id)?, // Some(Self::CLIENT), // to_vec(client)?, // ) // } // pub fn add_overlay(&self, overlay: &OverlayId) -> Result<(), StorageError> { // if !self.exists() { // return Err(StorageError::BackendError); // } // self.store.put( // Self::PREFIX, // &to_vec(&self.id)?, // Some(Self::OVERLAY), // to_vec(overlay)?, // ) // } // pub fn remove_overlay(&self, overlay: &OverlayId) -> Result<(), StorageError> { // self.store.del_property_value( // Self::PREFIX, // &to_vec(&self.id)?, // Some(Self::OVERLAY), // to_vec(overlay)?, // ) // } // pub fn has_overlay(&self, overlay: &OverlayId) -> Result<(), StorageError> { // self.store.has_property_value( // Self::PREFIX, // &to_vec(&self.id)?, // Some(Self::OVERLAY), // to_vec(overlay)?, // ) // } pub fn is_admin(&self) -> Result { if self .store .has_property_value( Self::PREFIX, &to_vec(&self.id)?, Some(Self::ADMIN), &to_vec(&true)?, ) .is_ok() { return Ok(true); } Ok(false) } pub fn del(&self) -> Result<(), StorageError> { self.store.write_transaction(&|tx| { if let Ok(clients) = tx.get_all(Self::PREFIX, &to_vec(&self.id)?, Some(Self::CLIENT)) { for client in clients { tx.del_all(Self::PREFIX_CLIENT, &client, &Self::ALL_CLIENT_PROPERTIES)?; } } tx.del_all(Self::PREFIX, &to_vec(&self.id)?, &Self::ALL_PROPERTIES)?; Ok(()) }) } } #[cfg(test)] mod test { use p2p_repo::store::*; use p2p_repo::types::*; use p2p_repo::utils::*; use std::fs; use stores_lmdb::kcv_store::LmdbKCVStore; use tempfile::Builder; use crate::broker_store::account::Account; #[test] pub fn test_account() { let path_str = "test-env"; let root = Builder::new().prefix(path_str).tempdir().unwrap(); let key: [u8; 32] = [0; 32]; fs::create_dir_all(root.path()).unwrap(); println!("{}", root.path().to_str().unwrap()); let mut store = LmdbKCVStore::open(root.path(), key).unwrap(); let user_id = PubKey::Ed25519PubKey([1; 32]); let account = Account::create(&user_id, true, &store).unwrap(); println!("account created {}", account.id()); let account2 = Account::open(&user_id, &store).unwrap(); println!("account opened {}", account2.id()); // let client_id = PubKey::Ed25519PubKey([56; 32]); // let client_id_not_added = PubKey::Ed25519PubKey([57; 32]); // account2.add_client(&client_id).unwrap(); // assert!(account2.is_admin().unwrap()); // account.has_client(&client_id).unwrap(); // assert!(account.has_client(&client_id_not_added).is_err()); } }