From 4071ea4ff4aa5d5a5b467f32bf07922988912c83 Mon Sep 17 00:00:00 2001 From: Lennart <18233294+lennart-k@users.noreply.github.com> Date: Sat, 26 Apr 2025 10:52:23 +0200 Subject: [PATCH] CLI: Allow editing principal password --- crates/frontend/src/lib.rs | 19 ++++---- crates/store/src/auth/mod.rs | 2 +- crates/store/src/auth/toml_user_store.rs | 4 +- src/commands/mod.rs | 2 +- src/commands/principals.rs | 61 ++++++++++++++++++++---- src/main.rs | 2 +- 6 files changed, 68 insertions(+), 22 deletions(-) diff --git a/crates/frontend/src/lib.rs b/crates/frontend/src/lib.rs index 5f98252..6879eac 100644 --- a/crates/frontend/src/lib.rs +++ b/crates/frontend/src/lib.rs @@ -281,14 +281,17 @@ impl UserStore for OidcUserStore { async fn insert_user(&self, id: &str) -> Result<(), Self::Error> { self.0 - .insert_principal(User { - id: id.to_owned(), - displayname: None, - principal_type: Default::default(), - password: None, - app_tokens: vec![], - memberships: vec![], - }) + .insert_principal( + User { + id: id.to_owned(), + displayname: None, + principal_type: Default::default(), + password: None, + app_tokens: vec![], + memberships: vec![], + }, + false, + ) .await } } diff --git a/crates/store/src/auth/mod.rs b/crates/store/src/auth/mod.rs index 3f49c79..64bf302 100644 --- a/crates/store/src/auth/mod.rs +++ b/crates/store/src/auth/mod.rs @@ -9,7 +9,7 @@ pub trait AuthenticationProvider: 'static { async fn get_principals(&self) -> Result, crate::Error>; async fn get_principal(&self, id: &str) -> Result, crate::Error>; async fn remove_principal(&self, id: &str) -> Result<(), crate::Error>; - async fn insert_principal(&self, user: User) -> Result<(), crate::Error>; + async fn insert_principal(&self, user: User, overwrite: bool) -> Result<(), crate::Error>; async fn validate_password(&self, user_id: &str, password: &str) -> Result, Error>; async fn validate_app_token(&self, user_id: &str, token: &str) -> Result, Error>; diff --git a/crates/store/src/auth/toml_user_store.rs b/crates/store/src/auth/toml_user_store.rs index 44edfa3..7fb0cdc 100644 --- a/crates/store/src/auth/toml_user_store.rs +++ b/crates/store/src/auth/toml_user_store.rs @@ -69,9 +69,9 @@ impl AuthenticationProvider for TomlPrincipalStore { Ok(self.principals.read().await.get(id).cloned()) } - async fn insert_principal(&self, user: User) -> Result<(), crate::Error> { + async fn insert_principal(&self, user: User, overwrite: bool) -> Result<(), crate::Error> { let mut principals = self.principals.write().await; - if principals.contains_key(&user.id) { + if !overwrite && principals.contains_key(&user.id) { return Err(Error::AlreadyExists); } principals.insert(user.id.clone(), user); diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 9b703af..245411c 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -31,7 +31,7 @@ pub fn cmd_gen_config(_args: GenConfigArgs) -> anyhow::Result<()> { path: "/etc/rustical/principals.toml".to_owned(), }), data_store: DataStoreConfig::Sqlite(SqliteDataStoreConfig { - db_url: "".to_owned(), + db_url: "/var/lib/rustical/db.sqlite3".to_owned(), }), tracing: TracingConfig::default(), frontend: FrontendConfig { diff --git a/src/commands/principals.rs b/src/commands/principals.rs index d43c993..3c98003 100644 --- a/src/commands/principals.rs +++ b/src/commands/principals.rs @@ -35,16 +35,28 @@ struct RemoveArgs { id: String, } +#[derive(Parser, Debug)] +struct EditArgs { + id: String, + #[arg(long, help = "Ask for password input")] + password: bool, + #[arg( + long, + help = "Remove password (If you only want to use OIDC for example)" + )] + remove_password: bool, +} + #[derive(Debug, Subcommand)] enum Command { List, Create(CreateArgs), Remove(RemoveArgs), + Edit(EditArgs), } pub async fn cmd_principals(args: PrincipalsArgs) -> anyhow::Result<()> { let config: Config = Figment::new() - // TODO: What to do when config file does not exist? .merge(Toml::file(&args.config_file)) .merge(Env::prefixed("RUSTICAL_").split("__")) .extract()?; @@ -85,14 +97,17 @@ pub async fn cmd_principals(args: PrincipalsArgs) -> anyhow::Result<()> { None }; user_store - .insert_principal(User { - id, - displayname: name, - principal_type: principal_type.unwrap_or_default(), - app_tokens: vec![], - password, - memberships: vec![], - }) + .insert_principal( + User { + id, + displayname: name, + principal_type: principal_type.unwrap_or_default(), + app_tokens: vec![], + password, + memberships: vec![], + }, + false, + ) .await?; println!("Principal created"); } @@ -100,6 +115,34 @@ pub async fn cmd_principals(args: PrincipalsArgs) -> anyhow::Result<()> { user_store.remove_principal(&id).await?; println!("Principal {id} removed"); } + Command::Edit(EditArgs { + id, + remove_password, + password, + }) => { + let mut principal = user_store + .get_principal(&id) + .await? + .unwrap_or_else(|| panic!("Principal {id} does not exist")); + + if remove_password { + principal.password = None; + } + if password { + let salt = SaltString::generate(OsRng); + println!("Enter your password:"); + let password = rpassword::read_password()?; + principal.password = Some( + argon2::Argon2::default() + .hash_password(password.as_bytes(), &salt) + .unwrap() + .to_string() + .into(), + ) + } + user_store.insert_principal(principal, true).await?; + println!("Principal {id} updated"); + } } Ok(()) } diff --git a/src/main.rs b/src/main.rs index bd0bd61..5d842e4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -77,7 +77,6 @@ async fn main() -> Result<()> { Some(Command::Principals(principals_args)) => cmd_principals(principals_args).await?, None => { let config: Config = Figment::new() - // TODO: What to do when config file does not exist? .merge(Toml::file(&args.config_file)) .merge(Env::prefixed("RUSTICAL_").split("__")) .extract()?; @@ -196,6 +195,7 @@ mod tests { async fn insert_principal( &self, _user: rustical_store::auth::User, + _overwrite: bool, ) -> Result<(), rustical_store::Error> { Err(rustical_store::Error::Other(anyhow!("Not implemented"))) }