safe_box/
lib.rs

1pub mod err;
2
3use std::{
4    collections::HashMap,
5    ops::DerefMut,
6    path::Path,
7    sync::RwLock,
8    time::{Duration, SystemTime},
9};
10
11use argon2::{Argon2, Params, PasswordHash};
12use async_mutex::Mutex as AsyncMutex;
13use base64::Engine;
14use crypto::password_hash::SaltString;
15use getrandom::getrandom;
16use rand_core::OsRng;
17use sqlx::{query, sqlite::SqliteConnectOptions, Connection, Row, SqliteConnection};
18
19fn salt() -> SaltString {
20    SaltString::generate(OsRng)
21}
22
23fn gen_token() -> String {
24    let mut buf = [0u8; 32];
25    getrandom(&mut buf).unwrap();
26    base64::engine::general_purpose::STANDARD.encode(buf)
27}
28
29/// Interface to the password database.
30pub struct SafeBox {
31    conn: AsyncMutex<SqliteConnection>,
32    param: Params,
33    token: RwLock<HashMap<String, (String, SystemTime)>>,
34}
35
36pub use err::SafeBoxError as Error;
37
38/// Initialize the database.
39const Q_INIT: &str = "CREATE TABLE IF NOT EXISTS main (user TEXT PRIMARY KEY, phc TEXT);";
40
41impl SafeBox {
42    /// Open an SQLite connection with specified database file and create a `SafeBox`.
43    /// # Example
44    /// ```
45    /// use safe_box::SafeBox;
46    ///
47    /// let safe = SafeBox::new("secure.db").await.unwrap();
48    /// ```
49    pub async fn new(p: impl AsRef<Path>) -> Result<Self, Error> {
50        let opt = SqliteConnectOptions::default()
51            .filename(p)
52            .create_if_missing(true);
53        let mut conn = SqliteConnection::connect_with(&opt).await?;
54        query(Q_INIT).execute(&mut conn).await?;
55        Ok(Self {
56            conn: AsyncMutex::new(conn),
57            param: Params::DEFAULT,
58            token: RwLock::new(HashMap::new()),
59        })
60    }
61
62    /// Instantantiate a hasher with `self.param`.
63    fn hasher(&self) -> Argon2<'static> {
64        Argon2::new(
65            argon2::Algorithm::Argon2id,
66            argon2::Version::V0x13,
67            self.param.clone(),
68        )
69    }
70
71    /// Create new user entry with `user`name and `pass`word.
72    pub async fn create(&self, user: &str, pass: &str) -> Result<(), Error> {
73        let q = query("SELECT NULL FROM main WHERE user = ?").bind(user);
74        let v = q.fetch_all(self.conn.lock().await.deref_mut()).await?;
75        if v.len() > 0 {
76            return Err(Error::UserAlreadyExist(user.to_owned()));
77        }
78        let p = PasswordHash::generate(self.hasher(), pass, &salt())?.to_string();
79        let q = query("INSERT INTO main (user, phc) VALUES (?, ?)")
80            .bind(user)
81            .bind(p);
82        q.execute(self.conn.lock().await.deref_mut()).await?;
83        Ok(())
84    }
85
86    /// Verify the provided `user`name and `pass`word.
87    /// Return a new token if successful.
88    pub async fn verify(&self, user: &str, pass: &str) -> Result<String, Error> {
89        let query = query("SELECT phc FROM main WHERE user = ?").bind(user);
90        let mut conn = self.conn.lock().await;
91        let v = query.fetch_all(conn.deref_mut()).await?;
92        match v.len() {
93            0 => return Err(Error::UserNotExist(user.to_owned())),
94            2.. => return Err(Error::InvalidData(format!("duplicate user '{user}'"))),
95            _ => (),
96        };
97        let p = v[0].try_get("phc")?;
98        let p = PasswordHash::new(p)?;
99        let res = p.verify_password(&[&self.hasher()], pass);
100        if let Err(crypto::password_hash::Error::Password) = res {
101            return Err(Error::BadPass {
102                user: user.to_owned(),
103                pass: pass.to_owned(),
104            });
105        }
106        res?;
107        let token = gen_token();
108        self.token
109            .write()
110            .unwrap()
111            .insert(token.clone(), (user.to_owned(), SystemTime::now()));
112        Ok(token)
113    }
114
115    /// Verify the provided `token`.
116    /// Returns the user it belongs to if valid.
117    pub fn verify_token(&self, token: &str) -> Result<String, Error> {
118        let map = self.token.read().unwrap();
119        if let Some((s, t)) = map.get(token) {
120            let now = SystemTime::now();
121            if let Ok(d) = now.duration_since(*t) {
122                if d < Duration::from_secs(300) {
123                    return Ok(s.to_owned());
124                }
125            }
126        }
127        Err(Error::BadToken(token.to_owned()))
128    }
129
130    /// Update a user's password to `new`.
131    pub async fn update(&self, user: &str, pass: &str, new: &str) -> Result<(), Error> {
132        self.verify(user, pass).await?;
133        let p = PasswordHash::generate(self.hasher(), new, &salt())?.to_string();
134        let q = query("UPDATE main SET phc = ? WHERE user = ?")
135            .bind(p)
136            .bind(user);
137        q.execute(self.conn.lock().await.deref_mut()).await?;
138        Ok(())
139    }
140
141    /// Delate a user entry.
142    pub async fn delete(&self, user: &str, pass: &str) -> Result<(), Error> {
143        self.verify(user, pass).await?;
144        let q = query("DELETE FROM main WHERE user = ?").bind(user);
145        q.execute(self.conn.lock().await.deref_mut()).await?;
146        Ok(())
147    }
148}