// 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 for User { fn borrow(&self) -> &str { &self.0 } } impl Borrow 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 { 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 { 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 { 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 { 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::() ) } // Wrapper for thread_rng() that statically enforces that the generator is CryptoRng. fn secure_rng() -> impl Rng + CryptoRng { rand::thread_rng() }