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
29pub 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
38const Q_INIT: &str = "CREATE TABLE IF NOT EXISTS main (user TEXT PRIMARY KEY, phc TEXT);";
40
41impl SafeBox {
42 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 fn hasher(&self) -> Argon2<'static> {
64 Argon2::new(
65 argon2::Algorithm::Argon2id,
66 argon2::Version::V0x13,
67 self.param.clone(),
68 )
69 }
70
71 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 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 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 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 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}