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 async_mutex::Mutex as AsyncMutex;
12use base64::{prelude::BASE64_STANDARD, Engine};
13use sqlx::{query, sqlite::SqliteConnectOptions, Connection, Row, SqliteConnection};
14
15fn gen_salt() -> [u8; 64] {
16 let mut buf = [0u8; 64];
17 getrandom::fill(&mut buf).unwrap();
18 buf
19}
20
21pub struct SafeBox {
23 conn: AsyncMutex<SqliteConnection>,
24 argon2: argon2::Config<'static>,
25 token: RwLock<HashMap<String, (String, SystemTime)>>,
26}
27
28pub use err::SafeBoxError as Error;
29
30const Q_INIT: &str = "CREATE TABLE IF NOT EXISTS main (user TEXT PRIMARY KEY, phc TEXT);";
32
33impl SafeBox {
34 pub async fn new(p: impl AsRef<Path>) -> Result<Self, Error> {
42 let opt = SqliteConnectOptions::default()
43 .filename(p)
44 .create_if_missing(true);
45 let mut conn = SqliteConnection::connect_with(&opt).await?;
46 query(Q_INIT).execute(&mut conn).await?;
47 Ok(Self {
48 conn: AsyncMutex::new(conn),
49 argon2: argon2::Config::default(),
50 token: RwLock::new(HashMap::new()),
51 })
52 }
53
54 pub fn issue_token(&self, user: &str) -> String {
56 let mut buf = [0u8; 64];
57 getrandom::fill(&mut buf).unwrap();
58 let token = BASE64_STANDARD.encode(buf);
59 self.token
60 .write()
61 .unwrap()
62 .insert(token.clone(), (user.to_owned(), SystemTime::now()));
63 return token;
64 }
65
66 pub fn invalidate_token(&self, token: &str) {
68 self.token.write().unwrap().remove(token);
69 }
70
71 pub fn invalidate_user_token(&self, user: &str) {
73 self.token.write().unwrap().retain(|_, (u, _)| u != user);
74 }
75
76 pub fn expire_token(&self, duration: Duration) {
78 self.token.write().unwrap().retain(|_, (_, time)| {
79 SystemTime::now()
80 .duration_since(*time)
81 .is_ok_and(|d| d < duration)
82 });
83 }
84
85 pub async fn user_cnt(&self) -> Result<usize, Error> {
87 let cnt: u64 = query("SELECT COUNT(*) FROM main")
88 .fetch_one(self.conn.lock().await.deref_mut())
89 .await?
90 .get(0);
91 Ok(cnt as usize)
92 }
93
94 pub async fn create(&self, user: &str, pass: &str) -> Result<(), Error> {
96 let q = query("SELECT NULL FROM main WHERE user = ?").bind(user);
97 let v = q.fetch_all(self.conn.lock().await.deref_mut()).await?;
98 if v.len() > 0 {
99 return Err(Error::UserAlreadyExist(user.to_owned()));
100 }
101 let hashed = argon2::hash_encoded(pass.as_bytes(), &gen_salt(), &self.argon2)?;
102 let query = query("INSERT INTO main (user, phc) VALUES (?, ?)")
103 .bind(user)
104 .bind(hashed);
105 query.execute(self.conn.lock().await.deref_mut()).await?;
106 Ok(())
107 }
108
109 pub async fn verify(&self, user: &str, pass: &str) -> Result<bool, Error> {
112 let query = query("SELECT phc FROM main WHERE user = ?").bind(user);
113 let mut conn = self.conn.lock().await;
114 let v = query.fetch_all(conn.deref_mut()).await?;
115 match v.len() {
116 0 => return Err(Error::UserNotExist(user.to_owned())),
117 2.. => return Err(Error::InvalidData(format!("duplicate user '{user}'"))),
118 _ => (),
119 };
120 let p = v[0].try_get("phc")?;
121 let res = argon2::verify_encoded(p, pass.as_bytes())?;
122 Ok(res)
123 }
124
125 pub fn verify_token(&self, token: &str) -> Option<String> {
128 let map = self.token.read().unwrap();
129 map.get(token).map(|(user, _)| user.clone())
130 }
131
132 pub async fn update(&self, user: &str, new_pass: &str) -> Result<(), Error> {
134 self.invalidate_user_token(user);
135 let hashed = argon2::hash_encoded(new_pass.as_bytes(), &gen_salt(), &self.argon2)?;
136 let query = query("UPDATE main SET phc = ? WHERE user = ?")
137 .bind(hashed)
138 .bind(user);
139 query.execute(self.conn.lock().await.deref_mut()).await?;
140 Ok(())
141 }
142
143 pub async fn delete(&self, user: &str) -> Result<(), Error> {
145 let query = query("DELETE FROM main WHERE user = ?").bind(user);
146 query.execute(self.conn.lock().await.deref_mut()).await?;
147 Ok(())
148 }
149}