// Lprs - A local CLI vaults manager. For human and machine use // Copyright (C) 2024 Awiteb // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . use std::num::NonZeroUsize; use clap::Args; use either::Either; use crate::{ clap_parsers::{either_parser, kv_parser}, utils, vault::{cipher, Vaults}, LprsCommand, LprsError, LprsResult, }; #[derive(Debug, Args)] /// Edit command, used to edit the vault content pub struct Edit { /// The vault to edit, index or name #[arg(name = "INDEX-or-NAME", value_parser = either_parser::)] location: Either, #[arg(short, long)] /// The new vault name name: Option, #[arg(short, long)] /// The new vault username, make it empty string to delete it username: Option, #[arg(short, long)] /// The new password, make it empty string to delete it /// /// If there is no value for it you will prompt it #[allow(clippy::option_option)] password: Option>, #[arg(short, long)] /// The new vault service, make it empty string to delete it service: Option, #[arg(short = 'o', long)] /// The new vault note note: Option, /// The TOTP secret, make it empty string to delete it /// /// If there is no value you will prompt it #[arg(short, long)] #[allow(clippy::option_option)] totp_secret: Option>, /// The custom field, make it empty string to delete it /// /// If the custom field not exist will created it, if it's will update it, /// if there is no value, you will enter it through a prompt (e.g `-c key`) #[arg(name = "KEY=VALUE", short = 'c', long = "custom")] #[arg(value_parser = kv_parser)] custom_fields: Vec<(String, Option)>, /// Force edit, will not return error if there is a problem with the args. /// /// For example, duplication in the custom fields and try to editing nothing #[arg(short, long)] force: bool, } impl LprsCommand for Edit { fn run(self, mut vault_manager: Vaults) -> LprsResult<()> { let vault = match utils::vault_by_index_or_name(&self.location, &mut vault_manager.vaults) { Ok((_, v)) => v, Err(err) => { if self.force { return Ok(()); } return Err(err); } }; log::info!("Applying the new values to the vault"); if let Some(new_name) = self.name { vault.name = new_name; } if let Some(ref new_password) = self.password { if new_password.as_deref().is_some_and(|s| s.is_empty()) { vault.password = None; } else { vault.password = utils::user_secret(self.password, "New vault password:", false)?; } } if let Some(totp_secret) = utils::user_secret(self.totp_secret, "TOTP Secret:", false)? { if totp_secret.is_empty() { vault.totp_secret = None; } else { cipher::base32_decode(&totp_secret).map_err(|_| { LprsError::Base32("Invalid TOTP secret, must be valid base32 string".to_owned()) })?; vault.totp_secret = Some(totp_secret); } } if let Some(new_username) = self.username { if new_username.is_empty() { vault.username = None; } else { vault.username = Some(new_username); } } if let Some(new_service) = self.service { if new_service.is_empty() { vault.service = None; } else { vault.service = Some(new_service); } } if let Some(new_note) = self.note { if new_note.is_empty() { vault.note = None; } else { vault.note = Some(new_note); } } utils::apply_custom_fields( &mut vault.custom_fields, utils::prompt_custom(self.custom_fields)?, ); vault_manager.try_export() } fn validate_args(&self) -> LprsResult<()> { if !self.force && self.name.is_none() && self.username.is_none() && self.password.is_none() && self.service.is_none() && self.note.is_none() && self.totp_secret.is_none() && self.custom_fields.is_empty() { return Err(LprsError::Other( "You must edit one option at least".to_owned(), )); } if let Some(duplicated_key) = utils::get_duplicated_field(&self.custom_fields) { if !self.force { return Err(LprsError::Other(format!( "Duplication error: The custom key `{duplicated_key}` is duplicate" ))); } } if self .custom_fields .iter() .any(|(k, _)| k.starts_with(crate::RESERVED_FIELD_PREFIX)) { return Err(LprsError::ReservedPrefix(crate::RESERVED_FIELD_PREFIX)); } Ok(()) } }