diff --git a/Cargo.lock b/Cargo.lock index 9b064ac8d..7e572b427 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1984,6 +1984,15 @@ dependencies = [ "hmac 0.8.1", ] +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "http" version = "0.2.9" @@ -3401,7 +3410,7 @@ dependencies = [ [[package]] name = "rings-core" -version = "0.4.0" +version = "0.4.1" dependencies = [ "arrayref", "async-channel", @@ -3463,7 +3472,7 @@ dependencies = [ [[package]] name = "rings-derive" -version = "0.4.0" +version = "0.4.1" dependencies = [ "proc-macro2", "quote", @@ -3474,7 +3483,7 @@ dependencies = [ [[package]] name = "rings-node" -version = "0.4.0" +version = "0.4.1" dependencies = [ "anyhow", "arrayref", @@ -3495,6 +3504,7 @@ dependencies = [ "form_urlencoded", "futures", "futures-timer", + "home", "http", "hyper", "js-sys", @@ -3532,7 +3542,7 @@ dependencies = [ [[package]] name = "rings-rpc" -version = "0.4.0" +version = "0.4.1" dependencies = [ "base64 0.13.1", "http", @@ -3548,7 +3558,7 @@ dependencies = [ [[package]] name = "rings-transport" -version = "0.4.0" +version = "0.4.1" dependencies = [ "async-trait", "bincode", diff --git a/Cargo.toml b/Cargo.toml index 5d3436e5a..c08e71ad9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "2" members = ["core", "transport", "node", "rpc", "derive"] [workspace.package] -version = "0.4.0" +version = "0.4.1" edition = "2021" license = "GPL-3.0" authors = ["RND "] diff --git a/core/src/session.rs b/core/src/session.rs index 6c4504709..ef5982765 100644 --- a/core/src/session.rs +++ b/core/src/session.rs @@ -27,7 +27,7 @@ //! let account_type = "secp256k1".to_string(); //! let account_entity = user_secret_key_did.to_string(); //! -//! let mut builder = SessionSkBuilder::new(account_entity, account_type); +//! let builder = SessionSkBuilder::new(account_entity, account_type); //! let unsigned_proof = builder.unsigned_proof(); //! //! // Sign the unsigned proof with user's secret key. diff --git a/node/Cargo.toml b/node/Cargo.toml index b9f16bc54..93d09b2cd 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -44,6 +44,7 @@ node = [ "rings-transport/native-webrtc", "wasmer/default", "wasmer-types", + "home", ] browser = [ "backtrace", @@ -99,6 +100,7 @@ axum = { version = "0.6.10", optional = true } backtrace = { version = "0.3.6", optional = true } clap = { version = "4.0.14", features = ["derive", "env"], optional = true } form_urlencoded = { version = "1.0.1", optional = true } +home = { version = "0.5.5", optional = true } hyper = { version = "0.14.25", features = ["full"], optional = true } lazy_static = { version = "1.4.0", optional = true } opentelemetry = { version = "0.18.0", default-features = false, features = ["trace", "rt-tokio"], optional = true } diff --git a/node/bin/rings.rs b/node/bin/rings.rs index 4b01fe30d..c39365228 100644 --- a/node/bin/rings.rs +++ b/node/bin/rings.rs @@ -12,6 +12,8 @@ use futures::pin_mut; use futures::select; use futures::StreamExt; use futures_timer::Delay; +use rings_core::dht::Did; +use rings_core::session::SessionSkBuilder; use rings_node::backend::native::Backend; use rings_node::backend::native::BackendConfig; use rings_node::logging::init_logging; @@ -25,6 +27,8 @@ use rings_node::prelude::PersistenceStorage; use rings_node::processor::Processor; use rings_node::processor::ProcessorBuilder; use rings_node::processor::ProcessorConfig; +use rings_node::util::ensure_parent_dir; +use rings_node::util::expand_home; use tokio::io; use tokio::io::AsyncBufReadExt; @@ -43,6 +47,8 @@ struct Cli { enum Command { #[command(about = "Initializes a node with the given configuration.")] Init(InitCommand), + #[command(about = "Creates a new session secret key.")] + NewSession(NewSessionCommand), #[command(about = "Starts a long-running node daemon.")] Run(RunCommand), #[command(about = "Provides chat room-like functionality on the Rings Network.")] @@ -75,19 +81,21 @@ struct ConfigArgs { #[derive(Args, Debug)] struct InitCommand { + #[command(flatten)] + session_args: SessionArgs, + #[arg( long, default_value = "~/.rings/config.yaml", help = "The location of config file" )] pub location: String, +} - #[arg( - long = "key", - short = 'k', - help = "Your ecdsa_key. If not provided, a new key will be generated" - )] - pub ecdsa_key: Option, +#[derive(Args, Debug)] +struct NewSessionCommand { + #[command(flatten)] + session_args: SessionArgs, } #[derive(Args, Debug)] @@ -102,7 +110,6 @@ struct RunCommand { #[arg( long, - short = 's', help = "ICE server list. If not provided, use ice_servers in config file or stun://stun.l.google.com:19302", env )] @@ -177,6 +184,59 @@ impl ClientArgs { } } +#[derive(Args, Debug)] +struct SessionArgs { + #[arg( + long, + short = 's', + default_value = "~/.rings/session_sk", + help = "The location of session_sk file" + )] + pub session_sk: String, + + #[arg( + long, + short = 'k', + help = "Your ecdsa_key. If not provided, a random key will be used" + )] + pub ecdsa_key: Option, + + #[arg( + long, + default_value = "2592000", + help = "The ttl of session file in seconds" + )] + pub ttl: u64, +} + +impl SessionArgs { + fn new_session_then_write_to_fs(&self) -> anyhow::Result<&std::path::Path> { + let key = self.ecdsa_key.unwrap_or_else(|| { + let rand_key = SecretKey::random(); + println!("Your random ecdsa key is: {}", rand_key.to_string()); + rand_key + }); + let key_did: Did = key.address().into(); + + let ssk_builder = SessionSkBuilder::new(key_did.to_string(), "secp256k1".to_string()) + .set_ttl(self.ttl * 1000); + let unsigned_proof = ssk_builder.unsigned_proof(); + + let sig = key.sign(&unsigned_proof).to_vec(); + let ssk_builder = ssk_builder.set_session_sig(sig); + + let ssk = ssk_builder.build()?; + let ssk_dump = ssk.dump()?; + + let ssk_path = std::path::Path::new(&self.session_sk); + ensure_parent_dir(ssk_path)?; + std::fs::write(expand_home(ssk_path)?, ssk_dump)?; + println!("Your session_sk file has saved to: {}", ssk_path.display()); + + Ok(ssk_path) + } +} + #[derive(Subcommand, Debug)] #[command(rename_all = "kebab-case")] enum ConnectCommand { @@ -553,15 +613,16 @@ async fn main() -> anyhow::Result<()> { Ok(()) } Command::Init(args) => { - let config = if let Some(key) = args.ecdsa_key { - config::Config::new_with_key(key) - } else { - config::Config::default() - }; - let p = config.write_fs(args.location.as_str())?; + let session_sk_path = args.session_args.new_session_then_write_to_fs()?; + let config = config::Config::new(session_sk_path); + let p = config.write_fs(&args.location)?; println!("Your config file has saved to: {}", p); Ok(()) } + Command::NewSession(args) => { + args.session_args.new_session_then_write_to_fs()?; + Ok(()) + } Command::Inspect(args) => { args.client_args .new_client() diff --git a/node/src/error.rs b/node/src/error.rs index 5e3e3e8e5..bcfad1197 100644 --- a/node/src/error.rs +++ b/node/src/error.rs @@ -95,6 +95,10 @@ pub enum Error { OpenFileError(String) = 901, #[error("Acquire lock failed")] Lock = 902, + #[error("Cannot find home directory")] + HomeDirError = 903, + #[error("Cannot find parent directory")] + ParentDirError = 904, #[error("Serde json error: {0}")] SerdeJsonError(#[from] serde_json::Error) = 1000, #[error("Serde yaml error: {0}")] diff --git a/node/src/native/config.rs b/node/src/native/config.rs index 499451122..a51d0ac31 100644 --- a/node/src/native/config.rs +++ b/node/src/native/config.rs @@ -15,6 +15,8 @@ use crate::prelude::rings_core::ecc::SecretKey; use crate::prelude::SessionSk; use crate::processor::ProcessorConfig; use crate::processor::ProcessorConfigSerialized; +use crate::util::ensure_parent_dir; +use crate::util::expand_home; lazy_static::lazy_static! { static ref DEFAULT_DATA_STORAGE_CONFIG: StorageConfig = StorageConfig { @@ -55,6 +57,7 @@ pub struct Config { pub endpoint_url: String, pub ice_servers: String, pub stabilize_timeout: usize, + #[serde(skip_serializing_if = "Option::is_none")] pub external_ip: Option, /// When there is no configuration in the YAML file, /// its deserialization is equivalent to `vec![]` in Rust. @@ -78,12 +81,18 @@ impl TryFrom for ProcessorConfigSerialized { .expect("create session sk failed") .dump() .expect("dump session sk failed") - } else if let Some(dk) = config.session_manager { + } else if let Some(ssk) = config.session_manager { tracing::warn!("Field `session_manager` is deprecated, use `session_sk` instead."); - dk + ssk } else { - config.session_sk.expect("session_sk is not set.") + let ssk_file = config.session_sk.expect("session_sk is not set."); + let ssk_file_expand_home = expand_home(&ssk_file)?; + fs::read_to_string(ssk_file_expand_home).unwrap_or_else(|e| { + tracing::warn!("Read session_sk file failed: {e:?}. Handling it as raw session_sk string. This mode is deprecated. please use a file path."); + ssk_file + }) }; + if let Some(ext_ip) = config.external_ip { Ok(Self::new_with_ext_addr( config.ice_servers, @@ -118,12 +127,9 @@ impl From for BackendConfig { } impl Config { - pub fn new_with_key(key: SecretKey) -> Self { - let session_sk = SessionSk::new_with_seckey(&key) - .expect("create session sk failed") - .dump() - .expect("dump session sk failed"); - + pub fn new

(session_sk: P) -> Self + where P: AsRef { + let session_sk = session_sk.as_ref().to_string_lossy().to_string(); Self { ecdsa_key: None, session_manager: None, @@ -142,21 +148,8 @@ impl Config { pub fn write_fs

(&self, path: P) -> Result where P: AsRef { - let path = match path.as_ref().strip_prefix("~") { - Ok(stripped) => { - let home_dir = env::var_os("HOME").map(PathBuf::from); - home_dir.map(|mut p| { - p.push(stripped); - p - }) - } - Err(_) => Some(path.as_ref().to_owned()), - } - .unwrap(); - let parent = path.parent().expect("no parent directory"); - if !parent.is_dir() { - fs::create_dir_all(parent).map_err(|e| Error::CreateFileError(e.to_string()))?; - }; + let path = expand_home(path)?; + ensure_parent_dir(&path)?; let f = fs::File::create(path.as_path()).map_err(|e| Error::CreateFileError(e.to_string()))?; let f_writer = io::BufWriter::new(f); @@ -166,17 +159,7 @@ impl Config { pub fn read_fs

(path: P) -> Result where P: AsRef { - let path = match path.as_ref().strip_prefix("~") { - Ok(stripped) => { - let home_dir = env::var_os("HOME").map(PathBuf::from); - home_dir.map(|mut p| { - p.push(stripped); - p - }) - } - Err(_) => Some(path.as_ref().to_owned()), - } - .unwrap(); + let path = expand_home(path)?; tracing::debug!("Read config from: {:?}", path); let f = fs::File::open(path).map_err(|e| Error::OpenFileError(e.to_string()))?; let f_rdr = io::BufReader::new(f); @@ -184,13 +167,6 @@ impl Config { } } -impl Default for Config { - fn default() -> Self { - let ecdsa_key = SecretKey::random(); - Self::new_with_key(ecdsa_key) - } -} - #[derive(Debug, Clone, Deserialize, Serialize)] pub struct StorageConfig { pub path: String, diff --git a/node/src/util.rs b/node/src/util.rs index cac790134..32731d96a 100644 --- a/node/src/util.rs +++ b/node/src/util.rs @@ -1,6 +1,9 @@ //! Utilities for configuration and build. #![warn(missing_docs)] +#[cfg(feature = "node")] +use crate::error::Error; + /// build_version of program pub fn build_version() -> String { let mut infos = vec![]; @@ -13,6 +16,35 @@ pub fn build_version() -> String { infos.join("-") } +/// Expand path with "~" to absolute path. +#[cfg(feature = "node")] +pub fn expand_home

(path: P) -> Result +where P: AsRef { + let Ok(stripped) = path.as_ref().strip_prefix("~") else { + return Ok(path.as_ref().to_path_buf()); + }; + + let Some(mut p) = home::home_dir() else { + return Err(Error::HomeDirError); + }; + + p.push(stripped); + + Ok(p) +} + +/// Create parent directory of a path if not exists. +#[cfg(feature = "node")] +pub fn ensure_parent_dir

(path: P) -> Result<(), Error> +where P: AsRef { + let path = expand_home(path)?; + let parent = path.parent().ok_or(Error::ParentDirError)?; + if !parent.is_dir() { + std::fs::create_dir_all(parent).map_err(|e| Error::CreateFileError(e.to_string()))?; + }; + Ok(()) +} + #[cfg(feature = "node")] pub mod loader { //! A module to help user load config from local file or remote url. @@ -50,3 +82,51 @@ pub mod loader { impl ResourceLoader for Seed {} } + +#[cfg(test)] +#[cfg(feature = "node")] +mod tests { + use super::*; + + #[test] + fn test_expand_home_with_tilde() { + let input = "~"; + let mut expected = std::env::var("HOME").unwrap(); + expected.push('/'); + let result = expand_home(input).unwrap(); + assert_eq!(result.to_str(), Some(expected.as_str())); + } + + #[test] + fn test_expand_home_with_relative_path() { + let input = "~/path/to/file.txt"; + let mut expected = std::env::var("HOME").unwrap(); + expected.push_str("/path/to/file.txt"); + let result = expand_home(input).unwrap(); + assert_eq!(result.to_str(), Some(expected.as_str())); + } + + #[test] + fn test_expand_home_with_absolute_path() { + let input = "/absolute/path/to/file.txt"; + let expected = std::path::PathBuf::from(input); + let result = expand_home(input).unwrap(); + assert_eq!(result, expected); + } + + #[test] + fn test_expand_home_with_invalid_path() { + let input = "path/does/not/exist.txt"; + let expected = std::path::PathBuf::from(input); + let result = expand_home(input).unwrap(); + assert_eq!(result, expected); + } + + #[test] + fn test_expand_home_with_empty_path() { + let input = ""; + let expected = std::path::PathBuf::from(""); + let result = expand_home(input).unwrap(); + assert_eq!(result, expected); + } +}