diff --git a/Cargo.lock b/Cargo.lock index 1861317..b7c8845 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -145,6 +145,7 @@ name = "mfm_core" version = "0.1.0" dependencies = [ "serde", + "zeroize", ] [[package]] @@ -587,3 +588,9 @@ dependencies = [ "quote", "syn 2.0.39", ] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/mfm_core/Cargo.toml b/mfm_core/Cargo.toml index c8f2b8e..5a78158 100644 --- a/mfm_core/Cargo.toml +++ b/mfm_core/Cargo.toml @@ -7,3 +7,4 @@ edition = "2021" [dependencies] serde = "1.0.193" +zeroize = "1.7.0" diff --git a/mfm_core/src/config/authentication/mod.rs b/mfm_core/src/config/authentication/mod.rs new file mode 100644 index 0000000..d29a03d --- /dev/null +++ b/mfm_core/src/config/authentication/mod.rs @@ -0,0 +1,15 @@ +pub mod wallet; + +use serde::{Deserialize, Serialize}; + +use self::wallet::Wallet; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case", tag = "type")] +pub enum Method { + Wallet(Wallet), + MetaMask, // TODO: the next auth method +} + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct Methods(Vec); diff --git a/mfm_core/src/config/authentication/wallet.rs b/mfm_core/src/config/authentication/wallet.rs new file mode 100644 index 0000000..90bd738 --- /dev/null +++ b/mfm_core/src/config/authentication/wallet.rs @@ -0,0 +1,11 @@ +use serde::{Deserialize, Serialize}; + +use crate::password::{deserialize_safe_password, SafePassword}; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct Wallet { + #[serde(deserialize_with = "deserialize_safe_password")] + pub private_key: SafePassword, + pub not_encrypted: bool, + pub env_password: Option, +} diff --git a/mfm_core/src/config/dexes.rs b/mfm_core/src/config/dexes.rs new file mode 100644 index 0000000..f5a0d23 --- /dev/null +++ b/mfm_core/src/config/dexes.rs @@ -0,0 +1,27 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub enum Kind { + UniswapV2, +} + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct Dex { + pub name: String, + pub kind: Kind, + pub router_address: String, + pub factory_address: String, + pub network_id: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct Dexes(HashMap); +impl Dexes { + pub fn hashmap(&self) -> &HashMap { + &self.0 + } + pub fn get(&self, key: &str) -> Option<&Dex> { + self.0.get(key) + } +} diff --git a/mfm_core/src/config/mod.rs b/mfm_core/src/config/mod.rs index 2c7f786..78f947f 100644 --- a/mfm_core/src/config/mod.rs +++ b/mfm_core/src/config/mod.rs @@ -1,88 +1,20 @@ use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub enum Kind { - EVM, -} - -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct Network { - pub name: String, - pub kind: Kind, - pub symbol: String, - pub decimals: Option, - pub chain_id: u32, - pub node_url: String, - pub node_url_failover: Option, - pub blockexplorer_url: Option, - pub min_balance_coin: f64, - pub wrapped_asset: Option, -} - -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct Networks(HashMap); -impl Networks { - pub fn get(&self, key: &str) -> Option<&Network> { - self.0.get(key) - } - pub fn hashmap(&self) -> &HashMap { - &self.0 - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub struct Dex { - pub name: String, - pub kind: Kind, - pub router_address: String, - pub factory_address: String, - pub network_id: String, -} - -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub struct Dexes(HashMap); -impl Dexes { - pub fn hashmap(&self) -> &HashMap { - &self.0 - } - pub fn get(&self, key: &str) -> Option<&Dex> { - self.0.get(key) - } -} +pub mod authentication; +pub mod dexes; +pub mod network; +pub mod token; -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct TokenNetwork { - pub(crate) name: String, - pub(crate) network_id: String, - pub(crate) address: String, - pub(crate) slippage: f64, - pub(crate) path_asset: String, -} - -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct TokenNetworks(HashMap); +use dexes::Dexes; +use network::Networks; +use token::Tokens; -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct Token { - pub(crate) kind: String, - pub(crate) networks: TokenNetworks, -} +use self::authentication::Methods; #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct Tokens(HashMap); -impl Tokens { - pub fn hashmap(&self) -> &HashMap { - &self.0 - } - - pub fn get(&self, key: &str) -> Option<&Token> { - self.0.get(key) - } -} - pub struct Config { pub networks: Networks, pub dexes: Dexes, pub tokens: Tokens, + pub auth_methods: Methods, } diff --git a/mfm_core/src/config/network.rs b/mfm_core/src/config/network.rs new file mode 100644 index 0000000..0249fe1 --- /dev/null +++ b/mfm_core/src/config/network.rs @@ -0,0 +1,33 @@ +use super::token::Token; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub enum Kind { + EVM, +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct Network { + pub name: String, + pub kind: Kind, + pub symbol: String, + pub decimals: Option, + pub chain_id: u32, + pub node_url: String, + pub node_url_failover: Option, + pub blockexplorer_url: Option, + pub min_balance_coin: f64, + pub wrapped_asset: Option, +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct Networks(HashMap); +impl Networks { + pub fn get(&self, key: &str) -> Option<&Network> { + self.0.get(key) + } + pub fn hashmap(&self) -> &HashMap { + &self.0 + } +} diff --git a/mfm_core/src/config/token.rs b/mfm_core/src/config/token.rs new file mode 100644 index 0000000..45a222a --- /dev/null +++ b/mfm_core/src/config/token.rs @@ -0,0 +1,37 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub enum Kind { + ERC20, +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct TokenNetwork { + pub name: String, + pub network_id: String, + pub address: String, + pub slippage: f64, + pub path_asset: String, +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct TokenNetworks(HashMap); + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct Token { + pub kind: Kind, + pub networks: TokenNetworks, +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct Tokens(HashMap); +impl Tokens { + pub fn hashmap(&self) -> &HashMap { + &self.0 + } + + pub fn get(&self, key: &str) -> Option<&Token> { + self.0.get(key) + } +} diff --git a/mfm_core/src/hidden/mod.rs b/mfm_core/src/hidden/mod.rs new file mode 100644 index 0000000..4add6a6 --- /dev/null +++ b/mfm_core/src/hidden/mod.rs @@ -0,0 +1,72 @@ +/// ! A wrapper to conceal secrets when output into logs or displayed. +// +use serde::{Deserialize, Serialize}; +use std::{fmt, ops::DerefMut}; + +/// A simple struct with a single inner value to wrap content of any type. +#[derive(Copy, Clone, Serialize, Deserialize)] +#[serde(transparent)] +pub struct Hidden { + inner: T, +} + +impl Hidden { + /// Returns ownership of the inner value discarding the wrapper. + pub fn into_inner(self) -> T { + self.inner + } +} + +impl From for Hidden { + fn from(inner: T) -> Self { + Hidden { inner } + } +} + +/// Defines a masked value for the type to output as debug information. Concealing the secrets within. +impl fmt::Debug for Hidden { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Hidden<{}>", std::any::type_name::()) + } +} + +/// Defines a masked value for the type to display. Concealing the secrets within. +impl fmt::Display for Hidden { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Hidden<{}>", std::any::type_name::()) + } +} + +/// Attempts to make the wrapper more transparent by having deref return a reference to the inner value. +impl std::ops::Deref for Hidden { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for Hidden { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl PartialEq for Hidden { + fn eq(&self, other: &Self) -> bool { + self.inner == other.inner + } +} + +impl Eq for Hidden {} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn into_applies_wrapper_deref_removes_it() { + let wrapped: Hidden = 42.into(); + assert_eq!(42, *wrapped) + } +} diff --git a/mfm_core/src/lib.rs b/mfm_core/src/lib.rs index e489ce0..d81e21f 100644 --- a/mfm_core/src/lib.rs +++ b/mfm_core/src/lib.rs @@ -1,4 +1,6 @@ pub mod config; +pub mod hidden; +pub mod password; pub mod states; pub mod tasks; diff --git a/mfm_core/src/password/mod.rs b/mfm_core/src/password/mod.rs new file mode 100644 index 0000000..b521668 --- /dev/null +++ b/mfm_core/src/password/mod.rs @@ -0,0 +1,59 @@ +use crate::hidden::Hidden; +use serde::{Deserialize, Serialize}; +use std::{error::Error, fmt, str::FromStr}; +use zeroize::Zeroize; + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(transparent)] +pub struct SafePassword { + password: Hidden>, +} + +impl From for SafePassword { + fn from(s: String) -> Self { + Self { + password: Hidden::from(s.into_bytes().into_boxed_slice()), + } + } +} + +impl Drop for SafePassword { + fn drop(&mut self) { + self.password.zeroize(); + } +} + +impl SafePassword { + /// Gets a reference to bytes of a passphrase. + pub fn reveal(&self) -> &[u8] { + self.password.as_ref() + } +} + +/// An error for parsing a password from string. +#[derive(Debug)] +pub struct PasswordError; + +impl Error for PasswordError {} + +impl fmt::Display for PasswordError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "PasswordError") + } +} + +impl FromStr for SafePassword { + type Err = PasswordError; + + fn from_str(s: &str) -> Result { + Ok(Self::from(s.to_owned())) + } +} + +pub fn deserialize_safe_password<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + let password: String = Deserialize::deserialize(deserializer)?; + Ok(SafePassword::from(password)) +}