diff options
| author | Tom Smeding <tom.smeding@gmail.com> | 2020-09-17 23:57:46 +0200 | 
|---|---|---|
| committer | Tom Smeding <tom.smeding@gmail.com> | 2021-01-28 22:20:13 +0100 | 
| commit | 653c5d682fb23a57cb39255da0164636ddefb7ab (patch) | |
| tree | 6e8fd948bfb9579a8215c0892b3c12c77bdf007b /rust/src/auth.rs | |
| parent | b453b8f7d510940da2fb3b5c1c88f8ba2caa2012 (diff) | |
rust: Proper authentication data generation
Diffstat (limited to 'rust/src/auth.rs')
| -rw-r--r-- | rust/src/auth.rs | 84 | 
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() +} | 
