pub mod err;
use std::{
collections::HashMap,
ops::DerefMut,
path::Path,
sync::RwLock,
time::{Duration, SystemTime},
};
use async_mutex::Mutex as AsyncMutex;
use base64::{prelude::BASE64_STANDARD, Engine};
use sqlx::{query, sqlite::SqliteConnectOptions, Connection, Row, SqliteConnection};
fn gen_salt() -> [u8; 64] {
let mut buf = [0u8; 64];
getrandom::fill(&mut buf).unwrap();
buf
}
pub struct SafeBox {
conn: AsyncMutex<SqliteConnection>,
argon2: argon2::Config<'static>,
token: RwLock<HashMap<String, (String, SystemTime)>>,
}
pub use err::SafeBoxError as Error;
const Q_INIT: &str = "CREATE TABLE IF NOT EXISTS main (user TEXT PRIMARY KEY, phc TEXT);";
impl SafeBox {
pub async fn new(p: impl AsRef<Path>) -> Result<Self, Error> {
let opt = SqliteConnectOptions::default()
.filename(p)
.create_if_missing(true);
let mut conn = SqliteConnection::connect_with(&opt).await?;
query(Q_INIT).execute(&mut conn).await?;
Ok(Self {
conn: AsyncMutex::new(conn),
argon2: argon2::Config::default(),
token: RwLock::new(HashMap::new()),
})
}
pub fn issue_token(&self, user: &str) -> String {
let mut buf = [0u8; 64];
getrandom::fill(&mut buf).unwrap();
let token = BASE64_STANDARD.encode(buf);
self.token
.write()
.unwrap()
.insert(token.clone(), (user.to_owned(), SystemTime::now()));
return token;
}
pub fn invalidate_token(&self, token: &str) {
self.token.write().unwrap().remove(token);
}
pub fn invalidate_user_token(&self, user: &str) {
self.token.write().unwrap().retain(|_, (u, _)| u != user);
}
pub fn expire_token(&self, duration: Duration) {
self.token.write().unwrap().retain(|_, (_, time)| {
SystemTime::now()
.duration_since(*time)
.is_ok_and(|d| d < duration)
});
}
pub async fn user_cnt(&self) -> Result<usize, Error> {
let cnt: u64 = query("SELECT COUNT(*) FROM main")
.fetch_one(self.conn.lock().await.deref_mut())
.await?
.get(0);
Ok(cnt as usize)
}
pub async fn create(&self, user: &str, pass: &str) -> Result<(), Error> {
let q = query("SELECT NULL FROM main WHERE user = ?").bind(user);
let v = q.fetch_all(self.conn.lock().await.deref_mut()).await?;
if v.len() > 0 {
return Err(Error::UserAlreadyExist(user.to_owned()));
}
let hashed = argon2::hash_encoded(pass.as_bytes(), &gen_salt(), &self.argon2)?;
let query = query("INSERT INTO main (user, phc) VALUES (?, ?)")
.bind(user)
.bind(hashed);
query.execute(self.conn.lock().await.deref_mut()).await?;
Ok(())
}
pub async fn verify(&self, user: &str, pass: &str) -> Result<bool, Error> {
let query = query("SELECT phc FROM main WHERE user = ?").bind(user);
let mut conn = self.conn.lock().await;
let v = query.fetch_all(conn.deref_mut()).await?;
match v.len() {
0 => return Err(Error::UserNotExist(user.to_owned())),
2.. => return Err(Error::InvalidData(format!("duplicate user '{user}'"))),
_ => (),
};
let p = v[0].try_get("phc")?;
let res = argon2::verify_encoded(p, pass.as_bytes())?;
Ok(res)
}
pub fn verify_token(&self, token: &str) -> Option<String> {
let map = self.token.read().unwrap();
map.get(token).map(|(user, _)| user.clone())
}
pub async fn update(&self, user: &str, new_pass: &str) -> Result<(), Error> {
self.invalidate_user_token(user);
let hashed = argon2::hash_encoded(new_pass.as_bytes(), &gen_salt(), &self.argon2)?;
let query = query("UPDATE main SET phc = ? WHERE user = ?")
.bind(hashed)
.bind(user);
query.execute(self.conn.lock().await.deref_mut()).await?;
Ok(())
}
pub async fn delete(&self, user: &str) -> Result<(), Error> {
let query = query("DELETE FROM main WHERE user = ?").bind(user);
query.execute(self.conn.lock().await.deref_mut()).await?;
Ok(())
}
}