use once_cell::sync::OnceCell; use sled::{Db, Tree}; static USER_TREE: OnceCell = OnceCell::new(); fn user_tree(db: &Db) -> &'static Tree { USER_TREE.get_or_init(|| db.open_tree("users").unwrap()) } pub(crate) fn login(db: &Db, username: String, password: String) -> Result<(), anyhow::Error> { let tree = user_tree(db); let hash = tree .get(username.as_bytes())? .ok_or_else(|| anyhow::anyhow!("Missing user {}", username))?; let password_hash = String::from_utf8_lossy(&hash); if bcrypt::verify(password, &password_hash)? { return Ok(()); } Err(anyhow::anyhow!("Invalid password")) } async fn add_user(db: &Db, username: String, password: String) -> Result<(), anyhow::Error> { let tree = user_tree(db); let hash = bcrypt::hash(&password, bcrypt::DEFAULT_COST)?; if tree .compare_and_swap(username, None as Option<&[u8]>, Some(hash.as_bytes()))? .is_err() { return Err(anyhow::anyhow!("User already exists")); } tree.flush_async().await?; Ok(()) } pub(crate) async fn update_password( db: &Db, username: String, new_password: String, ) -> Result<(), anyhow::Error> { let tree = user_tree(db); let hash = bcrypt::hash(&new_password, bcrypt::DEFAULT_COST)?; let updated = tree .update_and_fetch(username.as_bytes(), move |opt| { if opt.is_some() { Some(hash.clone().into_bytes()) } else { None } })? .is_some(); if updated { return Ok(()); } Err(anyhow::anyhow!("User doesn't exist")) } pub(crate) async fn verify(db: &Db, username: &str, password: String) -> Result<(), anyhow::Error> { let tree = user_tree(db); let hash = tree .get(username.as_bytes())? .ok_or_else(|| anyhow::anyhow!("Missing user {}", username))?; let password_hash = String::from_utf8_lossy(&hash); if bcrypt::verify(password, &password_hash)? { return Ok(()); } Err(anyhow::anyhow!("Invalid password")) } pub(crate) async fn create_admin(db: &Db) -> Result<(), anyhow::Error> { use rand::Rng; let password = rand::thread_rng() .sample_iter(rand::distributions::Alphanumeric) .take(16) .map(char::from) .collect::(); if add_user(db, String::from("admin"), password.clone()) .await .is_ok() { println!("Admin password is '{}'", password); } Ok(()) }