aboutsummaryrefslogtreecommitdiff
path: root/rust/src/auth.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rust/src/auth.rs')
-rw-r--r--rust/src/auth.rs84
1 files changed, 84 insertions, 0 deletions
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()
+}