mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-13 22:52:22 +00:00
CLI: Allow editing principal password
This commit is contained in:
@@ -281,14 +281,17 @@ impl<AP: AuthenticationProvider> UserStore for OidcUserStore<AP> {
|
|||||||
|
|
||||||
async fn insert_user(&self, id: &str) -> Result<(), Self::Error> {
|
async fn insert_user(&self, id: &str) -> Result<(), Self::Error> {
|
||||||
self.0
|
self.0
|
||||||
.insert_principal(User {
|
.insert_principal(
|
||||||
id: id.to_owned(),
|
User {
|
||||||
displayname: None,
|
id: id.to_owned(),
|
||||||
principal_type: Default::default(),
|
displayname: None,
|
||||||
password: None,
|
principal_type: Default::default(),
|
||||||
app_tokens: vec![],
|
password: None,
|
||||||
memberships: vec![],
|
app_tokens: vec![],
|
||||||
})
|
memberships: vec![],
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ pub trait AuthenticationProvider: 'static {
|
|||||||
async fn get_principals(&self) -> Result<Vec<User>, crate::Error>;
|
async fn get_principals(&self) -> Result<Vec<User>, crate::Error>;
|
||||||
async fn get_principal(&self, id: &str) -> Result<Option<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 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)
|
async fn validate_password(&self, user_id: &str, password: &str)
|
||||||
-> Result<Option<User>, Error>;
|
-> Result<Option<User>, Error>;
|
||||||
async fn validate_app_token(&self, user_id: &str, token: &str) -> Result<Option<User>, Error>;
|
async fn validate_app_token(&self, user_id: &str, token: &str) -> Result<Option<User>, Error>;
|
||||||
|
|||||||
@@ -69,9 +69,9 @@ impl AuthenticationProvider for TomlPrincipalStore {
|
|||||||
Ok(self.principals.read().await.get(id).cloned())
|
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;
|
let mut principals = self.principals.write().await;
|
||||||
if principals.contains_key(&user.id) {
|
if !overwrite && principals.contains_key(&user.id) {
|
||||||
return Err(Error::AlreadyExists);
|
return Err(Error::AlreadyExists);
|
||||||
}
|
}
|
||||||
principals.insert(user.id.clone(), user);
|
principals.insert(user.id.clone(), user);
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ pub fn cmd_gen_config(_args: GenConfigArgs) -> anyhow::Result<()> {
|
|||||||
path: "/etc/rustical/principals.toml".to_owned(),
|
path: "/etc/rustical/principals.toml".to_owned(),
|
||||||
}),
|
}),
|
||||||
data_store: DataStoreConfig::Sqlite(SqliteDataStoreConfig {
|
data_store: DataStoreConfig::Sqlite(SqliteDataStoreConfig {
|
||||||
db_url: "".to_owned(),
|
db_url: "/var/lib/rustical/db.sqlite3".to_owned(),
|
||||||
}),
|
}),
|
||||||
tracing: TracingConfig::default(),
|
tracing: TracingConfig::default(),
|
||||||
frontend: FrontendConfig {
|
frontend: FrontendConfig {
|
||||||
|
|||||||
@@ -35,16 +35,28 @@ struct RemoveArgs {
|
|||||||
id: String,
|
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)]
|
#[derive(Debug, Subcommand)]
|
||||||
enum Command {
|
enum Command {
|
||||||
List,
|
List,
|
||||||
Create(CreateArgs),
|
Create(CreateArgs),
|
||||||
Remove(RemoveArgs),
|
Remove(RemoveArgs),
|
||||||
|
Edit(EditArgs),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn cmd_principals(args: PrincipalsArgs) -> anyhow::Result<()> {
|
pub async fn cmd_principals(args: PrincipalsArgs) -> anyhow::Result<()> {
|
||||||
let config: Config = Figment::new()
|
let config: Config = Figment::new()
|
||||||
// TODO: What to do when config file does not exist?
|
|
||||||
.merge(Toml::file(&args.config_file))
|
.merge(Toml::file(&args.config_file))
|
||||||
.merge(Env::prefixed("RUSTICAL_").split("__"))
|
.merge(Env::prefixed("RUSTICAL_").split("__"))
|
||||||
.extract()?;
|
.extract()?;
|
||||||
@@ -85,14 +97,17 @@ pub async fn cmd_principals(args: PrincipalsArgs) -> anyhow::Result<()> {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
user_store
|
user_store
|
||||||
.insert_principal(User {
|
.insert_principal(
|
||||||
id,
|
User {
|
||||||
displayname: name,
|
id,
|
||||||
principal_type: principal_type.unwrap_or_default(),
|
displayname: name,
|
||||||
app_tokens: vec![],
|
principal_type: principal_type.unwrap_or_default(),
|
||||||
password,
|
app_tokens: vec![],
|
||||||
memberships: vec![],
|
password,
|
||||||
})
|
memberships: vec![],
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
println!("Principal created");
|
println!("Principal created");
|
||||||
}
|
}
|
||||||
@@ -100,6 +115,34 @@ pub async fn cmd_principals(args: PrincipalsArgs) -> anyhow::Result<()> {
|
|||||||
user_store.remove_principal(&id).await?;
|
user_store.remove_principal(&id).await?;
|
||||||
println!("Principal {id} removed");
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,7 +77,6 @@ async fn main() -> Result<()> {
|
|||||||
Some(Command::Principals(principals_args)) => cmd_principals(principals_args).await?,
|
Some(Command::Principals(principals_args)) => cmd_principals(principals_args).await?,
|
||||||
None => {
|
None => {
|
||||||
let config: Config = Figment::new()
|
let config: Config = Figment::new()
|
||||||
// TODO: What to do when config file does not exist?
|
|
||||||
.merge(Toml::file(&args.config_file))
|
.merge(Toml::file(&args.config_file))
|
||||||
.merge(Env::prefixed("RUSTICAL_").split("__"))
|
.merge(Env::prefixed("RUSTICAL_").split("__"))
|
||||||
.extract()?;
|
.extract()?;
|
||||||
@@ -196,6 +195,7 @@ mod tests {
|
|||||||
async fn insert_principal(
|
async fn insert_principal(
|
||||||
&self,
|
&self,
|
||||||
_user: rustical_store::auth::User,
|
_user: rustical_store::auth::User,
|
||||||
|
_overwrite: bool,
|
||||||
) -> Result<(), rustical_store::Error> {
|
) -> Result<(), rustical_store::Error> {
|
||||||
Err(rustical_store::Error::Other(anyhow!("Not implemented")))
|
Err(rustical_store::Error::Other(anyhow!("Not implemented")))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user