chore(DX): Improve the DX #27
16 changed files with 182 additions and 67 deletions
|
@ -15,6 +15,7 @@
|
|||
// along with this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>.
|
||||
|
||||
use clap::Args;
|
||||
use inquire::{Password, PasswordDisplayMode};
|
||||
|
||||
use crate::{
|
||||
vault::{Vault, Vaults},
|
||||
|
@ -23,11 +24,14 @@ use crate::{
|
|||
|
||||
#[derive(Debug, Args)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
/// Add command, used to add new vault to the vaults file
|
||||
pub struct Add {
|
||||
#[command(flatten)]
|
||||
vault_info: Vault,
|
||||
/// The password, if there is no value for it you will prompt it
|
||||
#[arg(short, long)]
|
||||
// FIXME: I think replacing `Option<Option<String>>` with custom type will be better
|
||||
#[allow(clippy::option_option)]
|
||||
password: Option<Option<String>>,
|
||||
}
|
||||
|
||||
|
@ -41,10 +45,10 @@ impl LprsCommand for Add {
|
|||
Some(None) => {
|
||||
log::debug!("User didn't provide a password, prompting it");
|
||||
self.vault_info.password = Some(
|
||||
inquire::Password::new("Vault password:")
|
||||
Password::new("Vault password:")
|
||||
.without_confirmation()
|
||||
.with_formatter(&|p| "*".repeat(p.chars().count()))
|
||||
.with_display_mode(inquire::PasswordDisplayMode::Masked)
|
||||
.with_display_mode(PasswordDisplayMode::Masked)
|
||||
.prompt()?,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -22,7 +22,8 @@ use crate::{vault::Vaults, LprsCommand, LprsError, LprsResult};
|
|||
|
||||
#[derive(Debug, Args)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
pub struct Clean {}
|
||||
/// Clean command, used to clean the vaults file (remove all vaults)
|
||||
pub struct Clean;
|
||||
|
||||
impl LprsCommand for Clean {
|
||||
fn run(self, vault_manager: Vaults) -> LprsResult<()> {
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
use std::num::NonZeroU64;
|
||||
|
||||
use clap::Args;
|
||||
use inquire::{Password, PasswordDisplayMode};
|
||||
|
||||
use crate::{
|
||||
vault::{Vault, Vaults},
|
||||
|
@ -25,6 +26,7 @@ use crate::{
|
|||
|
||||
#[derive(Debug, Args)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
/// Edit command, used to edit the vault content
|
||||
pub struct Edit {
|
||||
/// The password index. Check it from list command
|
||||
index: NonZeroU64,
|
||||
|
@ -37,6 +39,8 @@ pub struct Edit {
|
|||
username: Option<String>,
|
||||
#[arg(short, long)]
|
||||
/// The new password, if there is no value for it you will prompt it
|
||||
// FIXME: I think replacing `Option<Option<String>>` with custom type will be better
|
||||
#[allow(clippy::option_option)]
|
||||
password: Option<Option<String>>,
|
||||
#[arg(short, long)]
|
||||
/// The new vault service
|
||||
|
@ -63,10 +67,10 @@ impl LprsCommand for Edit {
|
|||
let password = match self.password {
|
||||
Some(Some(password)) => Some(password),
|
||||
Some(None) => Some(
|
||||
inquire::Password::new("New vault password:")
|
||||
Password::new("New vault password:")
|
||||
.without_confirmation()
|
||||
.with_formatter(&|p| "*".repeat(p.chars().count()))
|
||||
.with_display_mode(inquire::PasswordDisplayMode::Masked)
|
||||
.with_display_mode(PasswordDisplayMode::Masked)
|
||||
.prompt()?,
|
||||
),
|
||||
None => None,
|
||||
|
|
|
@ -25,6 +25,8 @@ use crate::{
|
|||
|
||||
#[derive(Debug, Args)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
/// Export command, used to export the vaults in `lprs` format or `BitWarden` format.
|
||||
/// The exported file will be a json file.
|
||||
pub struct Export {
|
||||
/// The path to export to
|
||||
path: PathBuf,
|
||||
|
@ -51,24 +53,29 @@ impl LprsCommand for Export {
|
|||
}
|
||||
|
||||
fn validate_args(&self) -> LprsResult<()> {
|
||||
if self
|
||||
if !self
|
||||
.path
|
||||
.extension()
|
||||
.is_some_and(|e| e.to_string_lossy().eq_ignore_ascii_case("json"))
|
||||
{
|
||||
if !self.path.exists() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(LprsError::Io(IoError::new(
|
||||
IoErrorKind::AlreadyExists,
|
||||
format!("file `{}` is already exists", self.path.display()),
|
||||
)))
|
||||
}
|
||||
} else {
|
||||
Err(LprsError::Io(IoError::new(
|
||||
return Err(LprsError::Io(IoError::new(
|
||||
IoErrorKind::InvalidInput,
|
||||
format!("file `{}` is not a json file", self.path.display()),
|
||||
)))
|
||||
}
|
||||
)));
|
||||
}
|
||||
if self.path.exists() {
|
||||
return Err(LprsError::Io(IoError::new(
|
||||
IoErrorKind::AlreadyExists,
|
||||
format!("file `{}` is already exists", self.path.display()),
|
||||
)));
|
||||
}
|
||||
if self.path.is_dir() {
|
||||
return Err(LprsError::Io(IoError::new(
|
||||
IoErrorKind::InvalidInput,
|
||||
format!("file `{}` is a directory", self.path.display()),
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ use crate::{vault::Vaults, LprsCommand, LprsError, LprsResult};
|
|||
|
||||
#[derive(Debug, Args)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
/// Generate command, used to generate a password
|
||||
pub struct Gen {
|
||||
/// The password length
|
||||
#[arg(default_value_t = NonZeroU64::new(18).unwrap())]
|
||||
|
|
|
@ -30,6 +30,7 @@ use crate::{
|
|||
|
||||
#[derive(Debug, Args)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
/// Import command, used to import vaults from the exported files, `lprs` or `BitWarden`
|
||||
pub struct Import {
|
||||
/// The file path to import from
|
||||
path: PathBuf,
|
||||
|
@ -77,24 +78,29 @@ impl LprsCommand for Import {
|
|||
}
|
||||
|
||||
fn validate_args(&self) -> LprsResult<()> {
|
||||
if self.path.exists() {
|
||||
if self
|
||||
.path
|
||||
.extension()
|
||||
.is_some_and(|e| e.to_string_lossy().eq_ignore_ascii_case("json"))
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
Err(LprsError::Io(IoError::new(
|
||||
return Err(LprsError::Io(IoError::new(
|
||||
IoErrorKind::InvalidInput,
|
||||
format!("file `{}` is not a json file", self.path.display()),
|
||||
)))
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
Err(LprsError::Io(IoError::new(
|
||||
if !self.path.exists() {
|
||||
return Err(LprsError::Io(IoError::new(
|
||||
IoErrorKind::NotFound,
|
||||
format!("file `{}` not found", self.path.display()),
|
||||
)))
|
||||
}
|
||||
)));
|
||||
}
|
||||
if self.path.is_dir() {
|
||||
return Err(LprsError::Io(IoError::new(
|
||||
IoErrorKind::InvalidInput,
|
||||
format!("file `{}` is a directory", self.path.display()),
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ use crate::{vault::Vaults, LprsCommand, LprsError, LprsResult};
|
|||
|
||||
#[derive(Debug, Args)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
/// List command, used to list the vaults and search
|
||||
pub struct List {
|
||||
/// Return the password with spesifc index
|
||||
#[arg(short, long, value_name = "INDEX")]
|
||||
|
|
|
@ -20,13 +20,22 @@ use clap::Parser;
|
|||
|
||||
use crate::{impl_commands, utils, vault::Vaults, LprsCommand, LprsResult};
|
||||
|
||||
/// Add command, used to add new vault to the vaults file
|
||||
pub mod add_command;
|
||||
/// Clean command, used to clean the vaults file (remove all vaults)
|
||||
pub mod clean_command;
|
||||
/// Edit command, used to edit the vault content
|
||||
pub mod edit_command;
|
||||
/// Export command, used to export the vaults
|
||||
/// in `lprs` format or `BitWarden` format
|
||||
pub mod export_command;
|
||||
/// Generate command, used to generate a password
|
||||
pub mod gen_command;
|
||||
/// Import command, used to import vaults from the exported files, `lprs` or `BitWarden`
|
||||
pub mod import_command;
|
||||
/// List command, used to list the vaults and search
|
||||
pub mod list_command;
|
||||
/// Remove command, used to remove vault from the vaults file
|
||||
pub mod remove_command;
|
||||
|
||||
/// The lprs commands
|
||||
|
@ -56,6 +65,7 @@ impl_commands!(Commands, Add Remove List Clean Edit Gen Export Import);
|
|||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
/// The lprs cli, manage the CLI arguments and run the commands
|
||||
pub struct Cli {
|
||||
/// The vaults json file
|
||||
#[arg(short = 'f', long)]
|
||||
|
@ -65,11 +75,19 @@ pub struct Cli {
|
|||
pub verbose: bool,
|
||||
|
||||
#[command(subcommand)]
|
||||
/// The provided command to run
|
||||
pub command: Commands,
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
/// Run the cli
|
||||
///
|
||||
/// # Errors
|
||||
/// - If can't get the default vaults file
|
||||
/// - If the vaults file can't be created
|
||||
/// - If the user provide a worng CLI arguments
|
||||
/// - If failed to write in the vaults file
|
||||
/// - (errors from the commands)
|
||||
pub fn run(self) -> LprsResult<()> {
|
||||
let vaults_file = if let Some(path) = self.vaults_file {
|
||||
log::info!("Using the given vaults file");
|
||||
|
@ -83,7 +101,7 @@ impl Cli {
|
|||
path
|
||||
} else {
|
||||
log::info!("Using the default vaults file");
|
||||
crate::utils::vaults_file()?
|
||||
utils::vaults_file()?
|
||||
};
|
||||
log::debug!("Vaults file: {}", vaults_file.display());
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ use crate::{vault::Vaults, LprsCommand, LprsError, LprsResult};
|
|||
|
||||
#[derive(Debug, Args)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
/// Remove command, used to remove a vault from the vaults file
|
||||
pub struct Remove {
|
||||
/// The password index
|
||||
index: NonZeroU64,
|
||||
|
@ -37,14 +38,14 @@ impl LprsCommand for Remove {
|
|||
log::debug!("Removing vault at index: {index}");
|
||||
|
||||
if index > vault_manager.vaults.len() {
|
||||
if !self.force {
|
||||
return Err(LprsError::Other(
|
||||
"The index is greater than the passwords counts".to_owned(),
|
||||
));
|
||||
} else {
|
||||
if self.force {
|
||||
log::error!(
|
||||
"The index is greater than the passwords counts, but the force flag is enabled"
|
||||
);
|
||||
} else {
|
||||
return Err(LprsError::Other(
|
||||
"The index is greater than the passwords counts".to_owned(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
vault_manager.vaults.remove(index);
|
||||
|
|
|
@ -14,11 +14,13 @@
|
|||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>.
|
||||
|
||||
use std::{process::ExitCode, string::FromUtf8Error};
|
||||
use std::{io, process::ExitCode, result, string::FromUtf8Error};
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
/// The result type used in the whole project
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum Error {
|
||||
#[error("Encryption Error: {0}")]
|
||||
Encryption(String),
|
||||
|
@ -50,12 +52,12 @@ pub enum Error {
|
|||
#[error("Project Folder Error: {0}")]
|
||||
ProjectDir(String),
|
||||
#[error("IO Error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
Io(#[from] io::Error),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// Return the exit code of the error
|
||||
pub fn exit_code(&self) -> ExitCode {
|
||||
pub const fn exit_code(&self) -> ExitCode {
|
||||
// TODO: Exit with more specific exit code
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
|
|
|
@ -46,7 +46,6 @@
|
|||
/// }
|
||||
/// }
|
||||
/// }
|
||||
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! impl_commands {
|
||||
|
|
11
src/main.rs
11
src/main.rs
|
@ -13,14 +13,20 @@
|
|||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>.
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
use clap::Parser;
|
||||
use inquire::InquireError;
|
||||
use std::env;
|
||||
use std::process::ExitCode;
|
||||
|
||||
/// The main module of the lprs crate, contains the cli and the commands.
|
||||
pub mod cli;
|
||||
/// The errors module, contains the errors and the result type.
|
||||
pub mod errors;
|
||||
/// The utils module, contains the utility functions of all the modules.
|
||||
pub mod utils;
|
||||
/// The vault module, contains the vault struct and the vaults manager.
|
||||
pub mod vault;
|
||||
|
||||
mod macros;
|
||||
|
@ -30,17 +36,20 @@ pub use base64::engine::general_purpose::STANDARD as BASE64;
|
|||
pub use errors::{Error as LprsError, Result as LprsResult};
|
||||
pub use traits::*;
|
||||
|
||||
/// The default vaults file name. Used to store the vaults.
|
||||
pub const DEFAULT_VAULTS_FILE: &str = "vaults.lprs";
|
||||
|
||||
#[cfg(feature = "update-notify")]
|
||||
/// The version of the lprs crate.
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
#[cfg(feature = "update-notify")]
|
||||
/// The last version check file. Used to store the last version check time.
|
||||
pub const LAST_VERSION_CHECK_FILE: &str = ".last_version_check";
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let lprs_cli = cli::Cli::parse();
|
||||
if lprs_cli.verbose {
|
||||
std::env::set_var("RUST_LOG", "lprs");
|
||||
env::set_var("RUST_LOG", "lprs");
|
||||
}
|
||||
pretty_env_logger::init();
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
use crate::{vault::Vaults, LprsResult};
|
||||
|
||||
/// Trait to work with the commands
|
||||
#[allow(clippy::missing_errors_doc)]
|
||||
pub trait LprsCommand {
|
||||
/// Run the command, should do all the logic, even the export
|
||||
fn run(self, vault_manager: Vaults) -> LprsResult<()>;
|
||||
|
|
52
src/utils.rs
52
src/utils.rs
|
@ -16,12 +16,20 @@
|
|||
|
||||
use std::{fs, path::PathBuf};
|
||||
|
||||
use inquire::validator::Validation;
|
||||
use inquire::{validator::Validation, PasswordDisplayMode};
|
||||
use passwords::{analyzer, scorer};
|
||||
use sha2::Digest;
|
||||
|
||||
#[cfg(feature = "update-notify")]
|
||||
use reqwest::blocking::Client as BlockingClient;
|
||||
|
||||
use crate::{LprsError, LprsResult};
|
||||
|
||||
/// Returns the local project dir joined with the given file name
|
||||
///
|
||||
/// ## Errors
|
||||
/// - If the project dir can't be extracted from the OS
|
||||
/// - If the local project dir can't be created
|
||||
pub fn local_project_file(filename: &str) -> LprsResult<PathBuf> {
|
||||
let local_dir = directories::ProjectDirs::from("", "", "lprs")
|
||||
.map(|d| d.data_local_dir().to_path_buf())
|
||||
|
@ -37,6 +45,10 @@ pub fn local_project_file(filename: &str) -> LprsResult<PathBuf> {
|
|||
}
|
||||
|
||||
/// Returns the default vaults json file
|
||||
///
|
||||
/// ## Errors
|
||||
/// - If the project dir can't be extracted from the OS
|
||||
/// - If the vaults file can't be created
|
||||
pub fn vaults_file() -> LprsResult<PathBuf> {
|
||||
let vaults_file = local_project_file(crate::DEFAULT_VAULTS_FILE)?;
|
||||
if !vaults_file.exists() {
|
||||
|
@ -50,22 +62,26 @@ pub fn vaults_file() -> LprsResult<PathBuf> {
|
|||
/// ## To pass
|
||||
/// - The length must be higher than 14 (>=15)
|
||||
/// - Its score must be greater than 80.0
|
||||
///
|
||||
/// ## Errors
|
||||
/// - There is no errors, just the return type of inquire validator
|
||||
/// must be Result<Validation, inquire::CustomUserError>
|
||||
pub fn password_validator(password: &str) -> Result<Validation, inquire::CustomUserError> {
|
||||
let analyzed = passwords::analyzer::analyze(password);
|
||||
if analyzed.length() < 15 {
|
||||
return Ok(Validation::Invalid(
|
||||
"The master password length must be beggier then 15".into(),
|
||||
));
|
||||
} else if passwords::scorer::score(&analyzed) < 80.0 {
|
||||
return Ok(Validation::Invalid(
|
||||
"Your master password is not stronge enough".into(),
|
||||
));
|
||||
}
|
||||
Ok(Validation::Valid)
|
||||
let analyzed = analyzer::analyze(password);
|
||||
Ok(if analyzed.length() < 15 {
|
||||
Validation::Invalid("The master password length must be beggier then 15".into())
|
||||
} else if scorer::score(&analyzed) < 80.0 {
|
||||
Validation::Invalid("Your master password is not stronge enough".into())
|
||||
} else {
|
||||
Validation::Valid
|
||||
})
|
||||
}
|
||||
|
||||
/// Ask the user for the master password, then returns it
|
||||
///
|
||||
/// ## Errors
|
||||
/// - Can't read the password from the user
|
||||
///
|
||||
/// Return's the password as 32 bytes after hash it (256 bit)
|
||||
pub fn master_password_prompt(is_new_vaults_file: bool) -> LprsResult<[u8; 32]> {
|
||||
inquire::Password {
|
||||
|
@ -79,13 +95,17 @@ pub fn master_password_prompt(is_new_vaults_file: bool) -> LprsResult<[u8; 32]>
|
|||
..inquire::Password::new("")
|
||||
}
|
||||
.with_formatter(&|p| "*".repeat(p.chars().count()))
|
||||
.with_display_mode(inquire::PasswordDisplayMode::Masked)
|
||||
.with_display_mode(PasswordDisplayMode::Masked)
|
||||
.prompt()
|
||||
.map(|p| sha2::Sha256::digest(p).into())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Retuns the current lprs version from `crates.io`
|
||||
///
|
||||
/// ## Errors
|
||||
/// - The project dir can't be extracted from the OS
|
||||
/// - If the last version check file can't be created
|
||||
#[cfg(feature = "update-notify")]
|
||||
pub fn lprs_version() -> LprsResult<Option<String>> {
|
||||
use std::time::SystemTime;
|
||||
|
@ -108,7 +128,7 @@ pub fn lprs_version() -> LprsResult<Option<String>> {
|
|||
|
||||
// Check if the last check is before one hour or not
|
||||
if (current_time - last_check) >= (60 * 60) || current_time == last_check {
|
||||
if let Ok(Ok(response)) = reqwest::blocking::Client::new()
|
||||
if let Ok(Ok(response)) = BlockingClient::new()
|
||||
.get("https://crates.io/api/v1/crates/lprs")
|
||||
.header(
|
||||
"User-Agent",
|
||||
|
@ -117,8 +137,8 @@ pub fn lprs_version() -> LprsResult<Option<String>> {
|
|||
.send()
|
||||
.map(|r| r.text())
|
||||
{
|
||||
let re =
|
||||
regex::Regex::new(r#""max_stable_version":"(?<version>\d+\.\d+\.\d+)""#).unwrap();
|
||||
let re = regex::Regex::new(r#""max_stable_version":"(?<version>\d+\.\d+\.\d+)""#)
|
||||
.expect("The regex is correct");
|
||||
if let Some(cap) = re.captures(&response) {
|
||||
return Ok(cap.name("version").map(|m| m.as_str().to_string()));
|
||||
}
|
||||
|
|
|
@ -1,3 +1,23 @@
|
|||
// Lprs - A local CLI vault manager
|
||||
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/gpl-3.0.html>.
|
||||
|
||||
// This file is not important, it is just a struct that is used to serialize and deserialize the vaults
|
||||
// from and to the BitWarden format.
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{Vault, Vaults};
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>.
|
||||
|
||||
use std::{fs, path::PathBuf};
|
||||
use std::{fmt, fs, path::PathBuf};
|
||||
|
||||
use base64::Engine;
|
||||
use clap::{Parser, ValueEnum};
|
||||
|
@ -22,6 +22,7 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use crate::{LprsError, LprsResult};
|
||||
|
||||
/// The chiper module, used to encrypt and decrypt the vaults
|
||||
pub mod cipher;
|
||||
|
||||
mod bitwarden;
|
||||
|
@ -29,8 +30,12 @@ mod bitwarden;
|
|||
pub use bitwarden::*;
|
||||
|
||||
#[derive(Clone, Debug, ValueEnum)]
|
||||
/// The vaults format
|
||||
pub enum Format {
|
||||
/// The lprs format, which is the default format
|
||||
/// and is is the result of the serialization/deserialization of the Vaults struct
|
||||
Lprs,
|
||||
/// The BitWarden format, which is the result of the serialization/deserialization of the BitWardenPasswords struct
|
||||
BitWarden,
|
||||
}
|
||||
|
||||
|
@ -85,7 +90,7 @@ impl Vault {
|
|||
|
||||
/// Return the name of the vault with the service if there
|
||||
pub fn list_name(&self) -> String {
|
||||
use std::fmt::Write;
|
||||
use fmt::Write;
|
||||
let mut list_name = self.name.clone();
|
||||
if let Some(ref username) = self.username {
|
||||
write!(&mut list_name, " <{username}>").expect("String never fail");
|
||||
|
@ -119,6 +124,10 @@ impl Vaults {
|
|||
///
|
||||
/// This function used to backup the vaults.
|
||||
///
|
||||
/// ## Errors
|
||||
/// - If the serialization failed
|
||||
/// - if the encryption failed
|
||||
///
|
||||
/// Note: The returned string is `Vec<Vault>`
|
||||
pub fn json_export(&self) -> LprsResult<String> {
|
||||
let encrypt = |val: &str| {
|
||||
|
@ -147,6 +156,10 @@ impl Vaults {
|
|||
|
||||
/// Reload the vaults from json data.
|
||||
///
|
||||
/// ## Errors
|
||||
/// - If base64 decoding failed (of the vault field encrypted data)
|
||||
/// - If decryption failed (wrong master password or the data is corrupted)
|
||||
///
|
||||
/// This function used to import backup vaults.
|
||||
pub fn json_reload(master_password: &[u8; 32], json_data: &[u8]) -> LprsResult<Vec<Vault>> {
|
||||
let decrypt = |val: &str| {
|
||||
|
@ -172,6 +185,9 @@ impl Vaults {
|
|||
}
|
||||
|
||||
/// Encrypt the vaults then export it to the file
|
||||
///
|
||||
/// ## Errors
|
||||
/// - Writing to the file failed
|
||||
pub fn try_export(self) -> LprsResult<()> {
|
||||
log::debug!(
|
||||
"Trying to export the vaults to the file: {}",
|
||||
|
@ -185,6 +201,11 @@ impl Vaults {
|
|||
}
|
||||
|
||||
/// Reload the vaults from the file then decrypt it
|
||||
///
|
||||
/// ## Errors
|
||||
/// - Reading the file failed
|
||||
/// - Decryption failed (wrong master password or the data is corrupted)
|
||||
/// - Bytecode deserialization failed (the data is corrupted)
|
||||
pub fn try_reload(vaults_file: PathBuf, master_password: [u8; 32]) -> LprsResult<Self> {
|
||||
let vaults_data = fs::read(&vaults_file)?;
|
||||
|
||||
|
@ -198,8 +219,8 @@ impl Vaults {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Format {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
impl fmt::Display for Format {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
|
@ -210,8 +231,8 @@ impl std::fmt::Display for Format {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Vault {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
impl fmt::Display for Vault {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Name: {}", self.name)?;
|
||||
if let Some(ref username) = self.username {
|
||||
write!(f, "\nUsername: {username}")?;
|
||||
|
|
Loading…
Reference in a new issue