diff options
-rw-r--r-- | rust/Cargo.toml | 3 | ||||
-rw-r--r-- | rust/src/auth.rs | 84 | ||||
-rw-r--r-- | rust/src/bel.rs | 6 | ||||
-rw-r--r-- | rust/src/editor.rs | 5 | ||||
-rw-r--r-- | rust/src/main.rs | 8 |
5 files changed, 101 insertions, 5 deletions
diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 2c95d3c..869baeb 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -11,3 +11,6 @@ tokio = { version = "0.2", features = ["rt-threaded"] } futures = "0.3" once_cell = "1.4" unicode-width = "^0.1.8" +users = "0.10" +dirs = "3.0" +rand = "0.7" diff --git a/rust/src/auth.rs b/rust/src/auth.rs new file mode 100644 index 0000000..51b4820 --- /dev/null +++ b/rust/src/auth.rs @@ -0,0 +1,84 @@ +// use users::get_current_username; +// use dirs::data_dir; +use std::borrow::Borrow; +use std::ops::Deref; +use std::{fs, io}; +use std::path::{Path, PathBuf}; +use rand::{Rng, CryptoRng}; +use crate::error::{IntoIOError, IntoIOResult}; + +pub struct User(String); +pub struct Pass(String); + +impl Borrow<str> for User { fn borrow(&self) -> &str { &self.0 } } +impl Borrow<str> for Pass { fn borrow(&self) -> &str { &self.0 } } + +impl Deref for User { type Target = str; fn deref(&self) -> &Self::Target { &self.0 } } +impl Deref for Pass { type Target = str; fn deref(&self) -> &Self::Target { &self.0 } } + +static PASSWORD_FILE_SUBDIR: &str = "tomsg_client"; // ~/.local/share/PASSWORD_FILE_SUBDIR/... +static PASSWORD_FILE_SUBDIR_HOME: &str = ".tomsg_client"; // ~/PASSWORD_FILE_SUBDIR_HOME/... +static PASSWORD_FILE_NAME: &str = "password.txt"; + +pub fn get_auth_info() -> io::Result<(User, Pass)> { + Ok((get_username()?, obtain_password()?)) +} + +fn get_username() -> io::Result<User> { + match users::get_current_username().map(|u| u.to_str().map(|u| u.to_string())) { + Some(Some(u)) => Ok(User(u)), + Some(None) => Err("Username not valid UTF-8".ioerr()), + None => Err("Cannot obtain username".ioerr()), + } +} + +fn obtain_password() -> io::Result<Pass> { + let dir = storage_directory()?; + if let Ok(pass) = read_password(&dir) { + return Ok(pass); + } + + let pass = generate_password(); + write_password(&dir, &pass) + .map_err(|e| format!("Cannot write password to home directory: {}", e).ioerr())?; + Ok(pass) +} + +fn read_password(data_dir: &Path) -> io::Result<Pass> { + fs::read(data_dir.join(PASSWORD_FILE_NAME)) + .and_then(|mut v| { + // Trim final newline if there is one + if v.last() == Some(&b'\n') { + v.pop(); + } + String::from_utf8(v).map(Pass).iores() + }) +} + +fn write_password(data_dir: &Path, pass: &Pass) -> io::Result<()> { + fs::create_dir_all(data_dir)?; + fs::write(data_dir.join(PASSWORD_FILE_NAME), pass.as_bytes()) +} + +fn storage_directory() -> io::Result<PathBuf> { + dirs::data_dir() + .map(|mut dir| { dir.push(PASSWORD_FILE_SUBDIR); dir }) + .or_else(|| dirs::home_dir() + .map(|mut dir| { + dir.push(PASSWORD_FILE_SUBDIR_HOME); dir + })) + .ok_or("Cannot determine home directory".ioerr()) +} + +fn generate_password() -> Pass { + Pass(secure_rng() + .sample_iter(rand::distributions::Alphanumeric) + .take(32) + .collect::<String>() + ) +} + +// Wrapper for thread_rng() that statically enforces that the generator is CryptoRng. +fn secure_rng() -> impl Rng + CryptoRng { + rand::thread_rng() +} diff --git a/rust/src/bel.rs b/rust/src/bel.rs new file mode 100644 index 0000000..fec5a3c --- /dev/null +++ b/rust/src/bel.rs @@ -0,0 +1,6 @@ +use std::io::Write; + +pub fn bel() { + print!("\x07"); + std::io::stdout().flush().unwrap(); +} diff --git a/rust/src/editor.rs b/rust/src/editor.rs index 7d83904..6284570 100644 --- a/rust/src/editor.rs +++ b/rust/src/editor.rs @@ -1,5 +1,5 @@ -use std::io::Write; use termion::event::Key; +use crate::bel; #[derive(Debug, Default)] pub struct Editor { @@ -56,8 +56,7 @@ impl Editor { } _ => { - print!("\x07"); - std::io::stdout().flush().unwrap(); + bel::bel(); } }; diff --git a/rust/src/main.rs b/rust/src/main.rs index 6e10938..5a50f3b 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -18,6 +18,8 @@ use unicode_width::UnicodeWidthChar; use crate::editor::Editor; use crate::error::IntoIOError; +mod auth; +mod bel; mod editor; mod error; @@ -537,8 +539,10 @@ async fn async_main() -> io::Result<()> { let addr = ("127.0.0.1", 29538); let (conn, pushchan) = Connection::connect(connection::Type::Plain, addr).await?; - let user = Word::try_from(String::from("tom")).unwrap(); - let pass = Line::try_from(String::from("kaas")).unwrap(); + let (user, pass) = auth::get_auth_info()?; + + let user = Word::try_from(user.to_string()).unwrap(); + let pass = Line::try_from(pass.to_string()).unwrap(); send_command(&conn, Command::Register { username: user.clone(), password: pass.clone() }).await?; |