From 1e0b9ac1667155137092fb0fe0e6b8ac9a365a57 Mon Sep 17 00:00:00 2001 From: selfhoster1312 Date: Sun, 20 Sep 2026 13:47:46 +0200 Subject: [PATCH] fix: Properly load/save DB and stop creating dummy users --- src/db/filesystem.rs | 41 +++++++++++++++++++++++++++-------------- src/db/interface.rs | 1 - src/db/memory.rs | 1 + src/main.rs | 37 +++++++++---------------------------- 4 files changed, 37 insertions(+), 43 deletions(-) diff --git a/src/db/filesystem.rs b/src/db/filesystem.rs index 9074822..4c805ec 100644 --- a/src/db/filesystem.rs +++ b/src/db/filesystem.rs @@ -1,5 +1,4 @@ use camino::{Utf8Path, Utf8PathBuf}; -use serde::{Deserialize, Serialize}; use serde_json::Error as JsonError; use std::fmt; @@ -35,7 +34,7 @@ impl fmt::Display for FilesystemDatabaseError { impl std::error::Error for FilesystemDatabaseError {} -#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[derive(Clone, Debug, Default)] pub struct FilesystemDatabase { // TODO: Once we have common validation steps in place across DB backends, we can reduce cloning. // For now, we clone the DB on every write operation, and update it when saving to disk @@ -59,10 +58,14 @@ impl FilesystemDatabase { path: path.to_path_buf(), }; - db.save().await?; + tracing::info!("Initializing empty database in file {path}. Checking permissions..."); + db.save_self().await?; + tracing::info!("Database successfully created"); + return Ok(Database::new(db)); } + tracing::info!("Loading database from file {path}"); let s = tokio::fs::read(path) .await .map_err(|e| Box::new(FilesystemDatabaseError::ReadFileIO(path.to_path_buf(), e)))?; @@ -74,13 +77,29 @@ impl FilesystemDatabase { })) } - pub async fn save(&mut self) -> Result<(), BoxedError> { - let s = serde_json::to_string(&self) + /// Attempts to save a new state of the DB, effectively switching to the new state + /// only if the save is successful. + pub async fn save(&mut self, new_db: MemoryDatabase) -> Result<(), BoxedError> { + self.save_inner(&new_db).await?; + self.inner = new_db; + Ok(()) + } + + /// Saves an in-memory DB to disk. Cannot be used directly to observe exclusive access. + async fn save_inner(&self, inner: &MemoryDatabase) -> Result<(), BoxedError> { + let s = serde_json::to_string(inner) .map_err(|e| Box::new(FilesystemDatabaseError::WriteFileJson(self.path.clone(), e)))?; Ok(tokio::fs::write(&self.path, &s) .await .map_err(|e| Box::new(FilesystemDatabaseError::WriteFileIO(self.path.clone(), e)))?) } + + /// Save the database without switching to a new state. + /// + /// Used when initializing the DB, to check for permissions. + pub async fn save_self(&mut self) -> Result<(), BoxedError> { + self.save_inner(&self.inner).await + } } #[async_trait::async_trait] @@ -95,9 +114,7 @@ impl DatabaseInterface for FilesystemDatabase { return Ok(Err(e)); } - self.save().await?; - self.inner = new_db; - + self.save(new_db).await?; Ok(Ok(())) } @@ -119,9 +136,7 @@ impl DatabaseInterface for FilesystemDatabase { return Ok(Err(e)); } - self.save().await?; - self.inner = new_db; - + self.save(new_db).await?; Ok(Ok(())) } @@ -136,9 +151,7 @@ impl DatabaseInterface for FilesystemDatabase { return Ok(Err(e)); } - self.save().await?; - self.inner = new_db; - + self.save(new_db).await?; Ok(Ok(())) } diff --git a/src/db/interface.rs b/src/db/interface.rs index 6115f39..36d6266 100644 --- a/src/db/interface.rs +++ b/src/db/interface.rs @@ -70,7 +70,6 @@ pub trait DatabaseInterface: std::fmt::Debug + Send + Sync + 'static { ) -> Result, BoxedError>; async fn list_all_domains(&self) -> Result, BoxedError>; - #[expect(unused)] async fn list_all_users(&self) -> Result, BoxedError>; /// List users on a specific domain. diff --git a/src/db/memory.rs b/src/db/memory.rs index bae3c8e..76a53be 100644 --- a/src/db/memory.rs +++ b/src/db/memory.rs @@ -14,6 +14,7 @@ pub struct MemoryDatabase { impl MemoryDatabase { #[allow(clippy::new_ret_no_self)] pub fn new() -> Database { + tracing::warn!("Using in-memory database. Data will not be saved across restarts!"); Database::new(Self::default()) } } diff --git a/src/main.rs b/src/main.rs index ac42c60..69487e3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,36 +18,16 @@ use ldap::ldap_handler; use listener::ListenerPath; async fn create_dummy_users(db: &mut Database) { - db.create_user(User { - username: "admin".to_string(), - domain: None, - password: "adminadmin".to_string(), - // TODO: what should we put here? - mail: "admin".to_string(), - role: Role::Admin, - }) - .await - .unwrap() - .unwrap(); - for letter in &["a", "b", "c"] { - let domain = format!("{letter}.localhost"); - db.create_domain(&domain).await.unwrap().unwrap(); + // TODO: customize admin password + if db.list_all_users().await.unwrap().is_empty() { + tracing::info!("Creating admin account with default `adminadmin` password"); db.create_user(User { - username: letter.to_string(), - domain: Some(domain.clone()), + username: "admin".to_string(), + domain: None, password: "adminadmin".to_string(), - mail: format!("{letter}@{domain}"), - role: Role::DomainAdmin(domain.clone()), - }) - .await - .unwrap() - .unwrap(); - db.create_user(User { - username: format!("user{letter}"), - domain: Some(domain.clone()), - password: "adminadmin".to_string(), - mail: format!("user{letter}@{domain}"), - role: Role::User, + // TODO: what should we put here? + mail: "admin".to_string(), + role: Role::Admin, }) .await .unwrap() @@ -74,6 +54,7 @@ async fn main() -> Result<(), GlobalError> { } else { MemoryDatabase::new() }; + tracing::info!("Database loaded successfully"); create_dummy_users(&mut db).await; #[cfg(feature = "http")]