diff --git a/.coderabbit.yml b/.coderabbit.yml index 53c9eb4..9b3d58f 100644 --- a/.coderabbit.yml +++ b/.coderabbit.yml @@ -18,6 +18,7 @@ reviews: path_instructions: - path: "**/*.{rs,toml}" instructions: - "Review the Rust code for conformity with best practices in Rust, - Systems programming. Highlight any deviations." + "Review the Rust code and Python code for conformity with best practices in Rust, + Systems programming and Python. Highlight any deviations. Also highlight if there any + any security issues in the code" diff --git a/tilekit/src/accounts.rs b/tilekit/src/accounts.rs index 99773c7..9ac75b5 100644 --- a/tilekit/src/accounts.rs +++ b/tilekit/src/accounts.rs @@ -1,17 +1,19 @@ -// Handles stuff related to accounts, identity etc.. +//! Handles stuff related to accounts, identity etc.. + use anyhow::Result; use ed25519_dalek::{SigningKey, ed25519::signature::rand_core::OsRng}; use keyring::Entry; use ucan::did::Ed25519Did; -type DID = String; -type Identity = DID; +type Did = String; +type Identity = Did; -/// Creates an `Identity` -/// # Parameters -/// - `app`: The service for which Identity is made (for ex: tiles) +/// Creates an `Identity` for given application /// The keypair generated will be stored in OS secure storage -/// Returns an `Identity` +/// +/// # Arguments +/// +/// - `app`: The service for which Identity is made (for ex: tiles) pub fn create_identity(app: &str) -> Result { let mut csprng = OsRng; let signing_key = SigningKey::generate(&mut csprng); diff --git a/tiles/src/commands/mod.rs b/tiles/src/commands/mod.rs index 64c6725..8013d3f 100644 --- a/tiles/src/commands/mod.rs +++ b/tiles/src/commands/mod.rs @@ -4,7 +4,7 @@ use anyhow::Result; use owo_colors::OwoColorize; use tiles::runtime::Runtime; use tiles::utils::accounts::{ - create_root_account, get_root_user_details, save_root_account, set_nickname, + RootUser, create_root_account, get_root_user_details, save_root_account, set_nickname, }; use tiles::utils::config::{get_or_create_config, set_memory_path}; use tiles::{core::health, runtime::RunArgs}; @@ -40,26 +40,30 @@ pub async fn stop_server(runtime: &Runtime) { let _ = runtime.stop_server_daemon().await; } -//TODO: add docs +/// Runs the account command with the args being passed. pub fn run_account_commands(account_args: AccountArgs) -> Result<()> { let config = get_or_create_config()?; let root_user_details = get_root_user_details(&config)?; match account_args.command { Some(AccountCommands::Create { nickname }) => { - //TODO: could show a message if did is already there - let root_user_config = create_root_account(&config, nickname)?; - let id = root_user_config.get("id").unwrap().as_str().unwrap(); - save_root_account(config, &root_user_config)?; - //TODO: color it... - let _ = println!("Root account has been created with id: {}", id).green(); + if !root_user_details.id.is_empty() { + println!("Root account exists with id: {}", root_user_details.id) + } else { + let root_user_config = RootUser::new(&create_root_account(&config, nickname)?)?; + + save_root_account(config, &root_user_config.to_table())?; + println!( + "{}", + format_args!( + "Root account has been created with id: {}", + root_user_config.id + ) + ) + } } Some(AccountCommands::SetNickname { nickname }) => { - //FIXME: redundant code - if root_user_details.get("id").unwrap().is_empty() { - println!( - "Root account not created yet, use {}", - format!("tiles account create").yellow() - ); + if root_user_details.id.is_empty() { + println!("{}", get_account_not_created_msg()); } else { match set_nickname(&config, nickname) { Ok(root_user_config) => { @@ -75,18 +79,20 @@ pub fn run_account_commands(account_args: AccountArgs) -> Result<()> { } } _ => { - if root_user_details.get("id").unwrap().is_empty() { - println!( - "Root account not created yet, use {}", - format!("tiles account create").yellow() - ); + if root_user_details.id.is_empty() { + println!("{}", get_account_not_created_msg()); } else { - for elem in root_user_details { - println!("{}: {}", elem.0, elem.1) - } + println!("{}", root_user_details); } } } Ok(()) } + +fn get_account_not_created_msg() -> String { + format!( + "Root account not created yet, use {}", + "tiles account create".yellow() + ) +} diff --git a/tiles/src/utils/accounts.rs b/tiles/src/utils/accounts.rs index 5e723c4..8392659 100644 --- a/tiles/src/utils/accounts.rs +++ b/tiles/src/utils/accounts.rs @@ -1,44 +1,99 @@ // Stuff related to account and identity system - -use std::collections::HashMap; - -use anyhow::Result; +use anyhow::{Result, anyhow}; +use std::fmt::Display; use tilekit::accounts::create_identity; use toml::Table; use crate::utils::config::save_config; const ROOT_USER_CONFIG_KEY: &str = "root-user"; -//TODO: add docs -pub fn get_root_user_details(config: &Table) -> Result> { - Ok(get_root_account(&config)) +#[allow(dead_code)] +pub struct RootUser { + pub id: String, + pub nickname: String, } -fn get_root_account(config: &Table) -> HashMap { - let root_user = config.get(ROOT_USER_CONFIG_KEY).unwrap(); - let root_user_table = root_user.as_table().unwrap(); - let mut root_user_map = HashMap::new(); - for ele in root_user_table { - root_user_map.insert(ele.0.to_string(), ele.1.as_str().unwrap().to_owned()); +impl Display for RootUser { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "id: {}\nnickname: {}\n", self.id, self.nickname) + } +} + +impl RootUser { + pub fn new(config: &Table) -> Result { + let id = config + .get("id") + .ok_or_else(|| anyhow!("Missing ID"))? + .as_str() + .ok_or_else(|| anyhow!("ID not a string"))?; + let nickname = config + .get("nickname") + .ok_or_else(|| anyhow!("Missing Nickname"))? + .as_str() + .ok_or_else(|| anyhow!("Nickname not a string"))?; + Ok(RootUser { + id: id.to_owned(), + nickname: nickname.to_owned(), + }) + } + + pub fn to_table(&self) -> Table { + let mut root_user_table = Table::new(); + root_user_table.insert(String::from("id"), toml::Value::String(self.id.clone())); + root_user_table.insert( + String::from("nickname"), + toml::Value::String(self.nickname.clone()), + ); + root_user_table } - root_user_map } -// Lets return main config table only, let the caller do whatever it wants... -// TODO: Needed docs +/// Returns a `RootUser`, which represents a root user +/// +/// # Params +/// +/// - config: A `Table` type of entire config.toml file +pub fn get_root_user_details(config: &Table) -> Result { + let root_user = config + .get(ROOT_USER_CONFIG_KEY) + .ok_or_else(|| anyhow!("root-user key not found"))?; + let root_user_table = root_user + .as_table() + .ok_or_else(|| anyhow!("root user not a table"))?; + RootUser::new(root_user_table) +} + +/// Create a root account +/// Stores the private credentials in OS secure password manager +/// +/// # Params +/// +/// - config: A `Table` type of entire config.toml file +/// - nickname: Nickname for the identity (Optional) +/// +/// Returns the root_user_config as a `Table` type pub fn create_root_account(config: &Table, nickname: Option) -> Result { - let root_user = config.get(ROOT_USER_CONFIG_KEY).unwrap(); - let root_user_table = root_user.as_table().unwrap(); - let did = root_user_table.get("id").unwrap().as_str().unwrap(); + let root_user = config + .get(ROOT_USER_CONFIG_KEY) + .ok_or_else(|| anyhow!("{} doesn't exist", ROOT_USER_CONFIG_KEY))?; + let root_user_table = root_user + .as_table() + .ok_or_else(|| anyhow!("Failed to parse root user info"))?; + let root_user_data = RootUser::new(root_user_table)?; + let did = root_user_data.id; if did.is_empty() { - let root_user_config = create_root_user(root_user_table, nickname)?; - Ok(root_user_config) + Ok(create_root_user(root_user_table, nickname)?) } else { Ok(root_user_table.clone()) } } -//TODO: docs +/// Save the root config in `Table` type to config.toml +/// +/// # Params +/// +/// - config: A `Table` type of entire config.toml file +/// - root_user_config: A `Table` type of root user pub fn save_root_account(mut config: Table, root_user_config: &Table) -> Result<()> { config.insert( String::from(ROOT_USER_CONFIG_KEY), @@ -47,7 +102,14 @@ pub fn save_root_account(mut config: Table, root_user_config: &Table) -> Result< save_config(&config) } -// TODO: add docs +/// Sets nickname for the root account +/// +/// # Params +/// +/// - config: A `Table` type of entire config.toml file +/// - nickname: Nickname for the identity +/// +/// Returns the root_user_config as a `Table` type pub fn set_nickname(config: &Table, nickname: String) -> Result
{ let root_user = config.get(ROOT_USER_CONFIG_KEY).unwrap(); let mut root_user_table = root_user.as_table().unwrap().clone(); @@ -61,18 +123,13 @@ pub fn set_nickname(config: &Table, nickname: String) -> Result
{ } } -// TODO: add docs fn create_root_user(root_user_config: &Table, nickname: Option) -> Result
{ - // get root user details let mut root_user_table = root_user_config.clone(); match create_identity("tiles") { Ok(did) => { root_user_table.insert("id".to_owned(), toml::Value::String(did)); - if nickname.is_some() { - root_user_table.insert( - "nickname".to_owned(), - toml::Value::String(nickname.unwrap()), - ); + if let Some(nickname) = nickname { + root_user_table.insert("nickname".to_owned(), toml::Value::String(nickname)); } Ok(root_user_table) } @@ -81,15 +138,15 @@ fn create_root_user(root_user_config: &Table, nickname: Option) -> Resul } #[cfg(test)] - mod tests { + use anyhow::Result; use keyring::{mock, set_default_credential_builder}; use toml::Table; - use crate::utils::accounts::{create_root_account, get_root_account}; + use crate::utils::accounts::{create_root_account, get_root_user_details}; #[test] - fn test_get_root_user_details_empty_id() { + fn test_get_root_user_details_empty_id() -> Result<()> { let config: Table = toml::from_str( r#" [root-user] @@ -98,12 +155,13 @@ mod tests { "#, ) .unwrap(); - let acc_details = get_root_account(&config); - assert!(acc_details.get("id").unwrap().is_empty()); + let acc_details = get_root_user_details(&config)?; + assert!(acc_details.id.is_empty()); + Ok(()) } #[test] - fn test_get_root_user_details_valid_id() { + fn test_get_root_user_details_valid_id() -> Result<()> { let config: Table = toml::from_str( r#" [root-user] @@ -112,8 +170,9 @@ mod tests { "#, ) .unwrap(); - let acc_details = get_root_account(&config); - assert!(acc_details.get("id").unwrap().contains("did:key")); + let acc_details = get_root_user_details(&config)?; + assert!(acc_details.id.contains("did:key")); + Ok(()) } #[test] diff --git a/tiles/src/utils/config.rs b/tiles/src/utils/config.rs index 2e9d8b7..80f604f 100644 --- a/tiles/src/utils/config.rs +++ b/tiles/src/utils/config.rs @@ -1,7 +1,6 @@ // Configuration related stuff use anyhow::{Context, Result}; -use std::fs::File; use std::path::PathBuf; use std::str::FromStr; use std::{env, fs}; @@ -144,15 +143,17 @@ pub fn get_or_create_config() -> Result
{ } } +/// Saves the root config toml `Table` type pub fn save_config(config: &Table) -> Result<()> { let tiles_config_dir = DefaultProvider.get_config_dir()?; - let config_toml_path = tiles_config_dir.join("config.toml"); - - fs::write(config_toml_path, config.to_string())?; + let config_path = tiles_config_dir.join("config.toml"); + let tmp_path = tiles_config_dir.join("config.tmp.toml"); + fs::write(&tmp_path, config.to_string())?; + fs::copy(&tmp_path, &config_path)?; + fs::remove_file(tmp_path)?; Ok(()) } -// pub fn save_config(config: &Table) //TODO: Add more tests for config.toml #[cfg(test)] mod tests { diff --git a/tiles/tests/config.rs b/tiles/tests/config.rs index 49389ff..ddbd49a 100644 --- a/tiles/tests/config.rs +++ b/tiles/tests/config.rs @@ -63,4 +63,3 @@ impl ConfigProvider for MockProvider { // assert!(default.ends_with("data/memory") || default.ends_with("data\\memory")); // Ok(()) // } -