From ced363a37f6f64282ca1a1fb022aa3d030edff79 Mon Sep 17 00:00:00 2001
From: Awiteb
Date: Thu, 16 May 2024 22:41:20 +0200
Subject: [PATCH] feat: Support changing master password (#50)
Reviewed-on: https://git.4rs.nl/awiteb/lprs/pulls/50
Co-authored-by: Awiteb
Co-committed-by: Awiteb
---
src/cli/add_command.rs | 5 +--
src/cli/change_master_password_command.rs | 39 +++++++++++++++++++++++
src/cli/edit_command.rs | 4 +--
src/cli/export_command.rs | 2 +-
src/cli/import_command.rs | 2 +-
src/cli/mod.rs | 6 +++-
src/utils.rs | 13 +++++---
7 files changed, 59 insertions(+), 12 deletions(-)
create mode 100644 src/cli/change_master_password_command.rs
diff --git a/src/cli/add_command.rs b/src/cli/add_command.rs
index 8c3fa8d..07cd536 100644
--- a/src/cli/add_command.rs
+++ b/src/cli/add_command.rs
@@ -55,8 +55,9 @@ impl LprsCommand for Add {
fn run(mut self, mut vault_manager: Vaults) -> LprsResult<()> {
if !self.vault_info.is_empty() {
self.vault_info.name = self.vault_info.name.trim().to_string();
- self.vault_info.password = utils::user_secret(self.password, "Vault password:")?;
- self.vault_info.totp_secret = utils::user_secret(self.totp_secret, "TOTP Secret:")?;
+ self.vault_info.password = utils::user_secret(self.password, "Vault password:", false)?;
+ self.vault_info.totp_secret =
+ utils::user_secret(self.totp_secret, "TOTP Secret:", false)?;
self.vault_info.custom_fields = self.custom_fields.into_iter().collect();
vault_manager.add_vault(self.vault_info);
vault_manager.try_export()?;
diff --git a/src/cli/change_master_password_command.rs b/src/cli/change_master_password_command.rs
new file mode 100644
index 0000000..8fd249d
--- /dev/null
+++ b/src/cli/change_master_password_command.rs
@@ -0,0 +1,39 @@
+// 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 clap::Args;
+use sha2::{Digest, Sha256};
+
+use crate::{utils, vault::Vaults, LprsCommand, LprsResult};
+
+#[derive(Debug, Args)]
+/// Change master password, reencrypt the vaults with new password
+pub struct ChangeMasterPassword {
+ /// The new master password, if there is no value for it you will prompt it
+ #[allow(clippy::option_option)]
+ new_password: Option,
+}
+
+impl LprsCommand for ChangeMasterPassword {
+ fn run(self, mut vault_manager: Vaults) -> LprsResult<()> {
+ vault_manager.master_password =
+ utils::user_secret(Some(self.new_password), "New master password:", true)?
+ .map(|s| Sha256::digest(s).into())
+ .expect("We wrap it in `Some`, so is will return a secret");
+ vault_manager.try_export()?;
+ Ok(())
+ }
+}
diff --git a/src/cli/edit_command.rs b/src/cli/edit_command.rs
index 81e3c88..8cf4a86 100644
--- a/src/cli/edit_command.rs
+++ b/src/cli/edit_command.rs
@@ -77,10 +77,10 @@ impl LprsCommand for Edit {
vault.name = new_name;
}
if self.password.is_some() {
- vault.password = utils::user_secret(self.password, "New vault password:")?;
+ vault.password = utils::user_secret(self.password, "New vault password:", false)?;
}
if self.totp_secret.is_some() {
- vault.totp_secret = utils::user_secret(self.totp_secret, "TOTP Secret:")?;
+ vault.totp_secret = utils::user_secret(self.totp_secret, "TOTP Secret:", false)?;
}
if let Some(new_username) = self.username {
vault.username = Some(new_username);
diff --git a/src/cli/export_command.rs b/src/cli/export_command.rs
index dcebf62..65fbf8f 100644
--- a/src/cli/export_command.rs
+++ b/src/cli/export_command.rs
@@ -55,7 +55,7 @@ impl LprsCommand for Export {
);
let encryption_key: Option<[u8; 32]> =
- utils::user_secret(self.encryption_password, "Encryption Password:")?
+ utils::user_secret(self.encryption_password, "Encryption Password:", false)?
.map(|p| sha2::Sha256::digest(p).into());
let exported_data = match self.format {
diff --git a/src/cli/import_command.rs b/src/cli/import_command.rs
index 4c11e8f..cce9bda 100644
--- a/src/cli/import_command.rs
+++ b/src/cli/import_command.rs
@@ -60,7 +60,7 @@ impl LprsCommand for Import {
);
let decryption_key: Option<[u8; 32]> =
- utils::user_secret(self.decryption_password, "Decryption password:")?
+ utils::user_secret(self.decryption_password, "Decryption password:", false)?
.map(|p| sha2::Sha256::digest(p).into());
let imported_passwords_len = match self.format {
diff --git a/src/cli/mod.rs b/src/cli/mod.rs
index 3cfb68f..0eee915 100644
--- a/src/cli/mod.rs
+++ b/src/cli/mod.rs
@@ -23,6 +23,8 @@ use crate::{impl_commands, utils, vault::Vaults, LprsCommand, LprsResult};
/// Add command, used to add new vault to the vaults file
pub mod add_command;
+/// Change master password, reencrypt the vaults with new password
+pub mod change_master_password_command;
/// Clean command, used to clean the vaults file (remove all vaults)
pub mod clean_command;
/// Generate shell completion
@@ -67,11 +69,13 @@ pub enum Commands {
Export(export_command::Export),
/// Import vaults
Import(import_command::Import),
+ /// Change master password, reencrypt the vaults with new password
+ ChangeMasterPassword(change_master_password_command::ChangeMasterPassword),
/// Generate shell completion
Completion(completion_command::Completion),
}
-impl_commands!(Commands, Add Remove List Clean Edit Gen Get Export Import Completion);
+impl_commands!(Commands, Add Remove List Clean Edit Gen Get Export Import ChangeMasterPassword Completion);
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
diff --git a/src/utils.rs b/src/utils.rs
index 5577a04..61b49ee 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -57,6 +57,7 @@ pub fn local_project_file(filename: &str) -> LprsResult {
pub fn user_secret(
secret: Option