aboutsummaryrefslogtreecommitdiff
path: root/rust/src/auth.rs
blob: 51b4820ca87c9a7e0aef560e45c189cfa95c7939 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
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()
}