CLI: Allow editing principal password

This commit is contained in:
Lennart
2025-04-26 10:52:23 +02:00
parent 6b9bd1226f
commit 4071ea4ff4
6 changed files with 68 additions and 22 deletions

View File

@@ -281,14 +281,17 @@ impl<AP: AuthenticationProvider> UserStore for OidcUserStore<AP> {
async fn insert_user(&self, id: &str) -> Result<(), Self::Error> {
self.0
.insert_principal(User {
.insert_principal(
User {
id: id.to_owned(),
displayname: None,
principal_type: Default::default(),
password: None,
app_tokens: vec![],
memberships: vec![],
})
},
false,
)
.await
}
}

View File

@@ -9,7 +9,7 @@ pub trait AuthenticationProvider: 'static {
async fn get_principals(&self) -> Result<Vec<User>, crate::Error>;
async fn get_principal(&self, id: &str) -> Result<Option<User>, 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<Option<User>, Error>;
async fn validate_app_token(&self, user_id: &str, token: &str) -> Result<Option<User>, Error>;

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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 {
.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(())
}

View File

@@ -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")))
}