chore: password refactor #11
20 changed files with 385 additions and 334 deletions
33
README.md
33
README.md
|
@ -1,6 +1,6 @@
|
||||||
# Lprs
|
# Lprs
|
||||||
|
|
||||||
Lprs is a local password manager designed to securely store and manage your passwords.
|
Lprs is a local vault manager designed to securely store and manage your vaults.
|
||||||
|
|
||||||
### MSRV
|
### MSRV
|
||||||
The Minimum Supported Rust Version (MSRV) is `1.70.0`.
|
The Minimum Supported Rust Version (MSRV) is `1.70.0`.
|
||||||
|
@ -31,7 +31,7 @@ cargo uninstall lprs
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Lprs provides a command-line interface for managing your passwords. The following commands are available:
|
Lprs provides a command-line interface for managing your vaults. The following commands are available:
|
||||||
|
|
||||||
```
|
```
|
||||||
A local CLI password manager
|
A local CLI password manager
|
||||||
|
@ -39,23 +39,20 @@ A local CLI password manager
|
||||||
Usage: lprs [OPTIONS] <COMMAND>
|
Usage: lprs [OPTIONS] <COMMAND>
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
add Add new password
|
add Add new vault
|
||||||
remove Remove password
|
remove Remove vault
|
||||||
list List your password and search
|
list List your vaults and search
|
||||||
clean Clean the password file
|
clean Clean the vaults file
|
||||||
edit Edit the password content
|
edit Edit the vault content
|
||||||
gen Generate password
|
gen Generate a password
|
||||||
export Export the passwords
|
export Export the vaults
|
||||||
import Import passwords
|
import Import vaults
|
||||||
help Print this message or the help of the given subcommand(s)
|
help Print this message or the help of the given subcommand(s)
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-p, --passwords-file <PASSWORDS_FILE>
|
-v, --vaults-file <VAULTS_FILE> The vaults json file
|
||||||
The passwords json file, default: $HOME/.local/share/lprs/passwords.json
|
-h, --help Print help
|
||||||
-h, --help
|
-V, --version Print version
|
||||||
Print help
|
|
||||||
-V, --version
|
|
||||||
Print version
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
|
@ -78,7 +75,7 @@ Master Password: ***************
|
||||||
|
|
||||||
### Backup
|
### Backup
|
||||||
|
|
||||||
It is important to regularly backup your passwords to prevent data loss. Lprs does not provide an automatic backup feature. To backup your passwords, you can use the `export` command provided by Lprs. This command allows you to export your encrypted passwords to a json file, which you can then manually backup to a secure location.
|
It is important to regularly backup your vaults to prevent data loss. Lprs does not provide an automatic backup feature. To backup your vaults, you can use the `export` command provided by Lprs. This command allows you to export your encrypted vaults to a json file, which you can then manually backup to a secure location.
|
||||||
|
|
||||||
#### Formats
|
#### Formats
|
||||||
The format of the exported file can be specified using the `--format` option. The following formats are supported:
|
The format of the exported file can be specified using the `--format` option. The following formats are supported:
|
||||||
|
@ -104,4 +101,4 @@ Lprs is licensed under the GPL-3.0 License. This means that you are free to use,
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/awiteb)
|
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/awiteb)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Lprs - A local CLI password manager
|
// Lprs - A local CLI vault manager
|
||||||
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
password::{Password, Passwords},
|
vault::{vault_state::*, Vault, Vaults},
|
||||||
LprsResult, RunCommand,
|
LprsResult, RunCommand,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -25,12 +25,12 @@ use crate::{
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
pub struct Add {
|
pub struct Add {
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
password_info: Password,
|
vault_info: Vault<Plain>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunCommand for Add {
|
impl RunCommand for Add {
|
||||||
fn run(&self, mut password_manager: Passwords) -> LprsResult<()> {
|
fn run(&self, mut vault_manager: Vaults<Plain>) -> LprsResult<()> {
|
||||||
password_manager.add_password(self.password_info.clone());
|
vault_manager.add_vault(self.vault_info.clone());
|
||||||
password_manager.try_export()
|
vault_manager.try_export()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Lprs - A local CLI password manager
|
// Lprs - A local CLI vault manager
|
||||||
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
@ -18,14 +18,17 @@ use std::fs;
|
||||||
|
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
|
|
||||||
use crate::{password::Passwords, LprsError, LprsResult, RunCommand};
|
use crate::{
|
||||||
|
vault::{vault_state::*, Vaults},
|
||||||
|
LprsError, LprsResult, RunCommand,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
pub struct Clean {}
|
pub struct Clean {}
|
||||||
|
|
||||||
impl RunCommand for Clean {
|
impl RunCommand for Clean {
|
||||||
fn run(&self, password_manager: Passwords) -> LprsResult<()> {
|
fn run(&self, vault_manager: Vaults<Plain>) -> LprsResult<()> {
|
||||||
fs::write(password_manager.passwords_file, "[]").map_err(LprsError::Io)
|
fs::write(vault_manager.vaults_file, "[]").map_err(LprsError::Io)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Lprs - A local CLI password manager
|
// Lprs - A local CLI vault manager
|
||||||
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
@ -19,7 +19,7 @@ use std::num::NonZeroU64;
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
password::{Password, Passwords},
|
vault::{vault_state::*, Vault, Vaults},
|
||||||
LprsError, LprsResult, RunCommand,
|
LprsError, LprsResult, RunCommand,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -30,27 +30,27 @@ pub struct Edit {
|
||||||
index: NonZeroU64,
|
index: NonZeroU64,
|
||||||
|
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
/// The new password name
|
/// The new vault name
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
/// The new password username
|
/// The new vault username
|
||||||
username: Option<String>,
|
username: Option<String>,
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
/// The new password
|
/// The new password
|
||||||
password: Option<String>,
|
password: Option<String>,
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
/// The new password service
|
/// The new vault service
|
||||||
service: Option<String>,
|
service: Option<String>,
|
||||||
#[arg(short = 'o', long)]
|
#[arg(short = 'o', long)]
|
||||||
/// The new password note
|
/// The new vault note
|
||||||
note: Option<String>,
|
note: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunCommand for Edit {
|
impl RunCommand for Edit {
|
||||||
fn run(&self, mut password_manager: Passwords) -> LprsResult<()> {
|
fn run(&self, mut vault_manager: Vaults<Plain>) -> LprsResult<()> {
|
||||||
let index = self.index.get() as usize;
|
let index = self.index.get() as usize;
|
||||||
|
|
||||||
if let Some(password) = password_manager.passwords.get_mut(index - 1) {
|
if let Some(vault) = vault_manager.vaults.get_mut(index - 1) {
|
||||||
if self.name.is_none()
|
if self.name.is_none()
|
||||||
&& self.username.is_none()
|
&& self.username.is_none()
|
||||||
&& self.password.is_none()
|
&& self.password.is_none()
|
||||||
|
@ -61,28 +61,20 @@ impl RunCommand for Edit {
|
||||||
"You must edit one option at least".to_owned(),
|
"You must edit one option at least".to_owned(),
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
*password = Password {
|
*vault = Vault::<Plain>::new(
|
||||||
name: self.name.as_ref().unwrap_or(&password.name).to_string(),
|
self.name.as_ref().unwrap_or(&vault.name),
|
||||||
username: self
|
self.username.as_ref().unwrap_or(&vault.username),
|
||||||
.username
|
self.password.as_ref().unwrap_or(&vault.password),
|
||||||
.as_ref()
|
self.service.as_ref().or(vault.service.as_ref()),
|
||||||
.unwrap_or(&password.username)
|
self.note.as_ref().or(vault.note.as_ref()),
|
||||||
.to_string(),
|
);
|
||||||
password: self
|
vault_manager.try_export()
|
||||||
.password
|
|
||||||
.as_ref()
|
|
||||||
.unwrap_or(&password.password)
|
|
||||||
.to_string(),
|
|
||||||
service: self.service.as_ref().or(password.service.as_ref()).cloned(),
|
|
||||||
note: self.note.as_ref().or(password.note.as_ref()).cloned(),
|
|
||||||
};
|
|
||||||
password_manager.try_export()
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(LprsError::InvalidPasswordIndex(format!(
|
Err(LprsError::InvalidVaultIndex(format!(
|
||||||
"The index `{}` is greater than the passwords count {}",
|
"The index `{}` is greater than the vaults count {}",
|
||||||
self.index,
|
self.index,
|
||||||
password_manager.passwords.len()
|
vault_manager.vaults.len()
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Lprs - A local CLI password manager
|
// Lprs - A local CLI vault manager
|
||||||
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
@ -19,7 +19,7 @@ use std::{fs, io::Error as IoError, io::ErrorKind as IoErrorKind, path::PathBuf}
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
password::{BitWardenPasswords, Format, Passwords},
|
vault::{vault_state::*, BitWardenPasswords, Format, Vault, Vaults},
|
||||||
LprsError, LprsResult, RunCommand,
|
LprsError, LprsResult, RunCommand,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -28,13 +28,13 @@ use crate::{
|
||||||
pub struct Export {
|
pub struct Export {
|
||||||
/// The path to export to
|
/// The path to export to
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
/// Format to export passwords in
|
/// Format to export vaults in
|
||||||
#[arg(short, long, value_name = "FORMAT", default_value_t= Format::Lprs)]
|
#[arg(short, long, value_name = "FORMAT", default_value_t= Format::Lprs)]
|
||||||
format: Format,
|
format: Format,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunCommand for Export {
|
impl RunCommand for Export {
|
||||||
fn run(&self, password_manager: Passwords) -> LprsResult<()> {
|
fn run(&self, vault_manager: Vaults<Plain>) -> LprsResult<()> {
|
||||||
if self
|
if self
|
||||||
.path
|
.path
|
||||||
.extension()
|
.extension()
|
||||||
|
@ -42,9 +42,11 @@ impl RunCommand for Export {
|
||||||
{
|
{
|
||||||
if !self.path.exists() {
|
if !self.path.exists() {
|
||||||
let exported_data = match self.format {
|
let exported_data = match self.format {
|
||||||
Format::Lprs => serde_json::to_string(&password_manager.encrypt()?.passwords),
|
Format::Lprs => serde_json::to_string::<Vec<Vault<Encrypted>>>(
|
||||||
|
&vault_manager.encrypt_vaults()?,
|
||||||
|
),
|
||||||
Format::BitWarden => {
|
Format::BitWarden => {
|
||||||
serde_json::to_string(&BitWardenPasswords::from(password_manager))
|
serde_json::to_string(&BitWardenPasswords::from(vault_manager))
|
||||||
}
|
}
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Lprs - A local CLI password manager
|
// Lprs - A local CLI vault manager
|
||||||
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
@ -18,7 +18,10 @@ use std::num::NonZeroU64;
|
||||||
|
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
|
|
||||||
use crate::{password::Passwords, LprsError, LprsResult, RunCommand};
|
use crate::{
|
||||||
|
vault::{vault_state::*, Vaults},
|
||||||
|
LprsError, LprsResult, RunCommand,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
|
@ -42,7 +45,7 @@ pub struct Gen {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunCommand for Gen {
|
impl RunCommand for Gen {
|
||||||
fn run(&self, _password_manager: Passwords) -> LprsResult<()> {
|
fn run(&self, _vault_manager: Vaults<Plain>) -> LprsResult<()> {
|
||||||
if self.uppercase || self.lowercase || self.numbers || self.symbols {
|
if self.uppercase || self.lowercase || self.numbers || self.symbols {
|
||||||
println!(
|
println!(
|
||||||
"{}",
|
"{}",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Lprs - A local CLI password manager
|
// Lprs - A local CLI vault manager
|
||||||
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
@ -19,7 +19,7 @@ use std::{fs::File, io::Error as IoError, io::ErrorKind as IoErrorKind, path::Pa
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
password::{BitWardenPasswords, Format, Password, Passwords},
|
vault::{vault_state::*, BitWardenPasswords, Format, Vault, Vaults},
|
||||||
LprsError, LprsResult, RunCommand,
|
LprsError, LprsResult, RunCommand,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ pub struct Import {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunCommand for Import {
|
impl RunCommand for Import {
|
||||||
fn run(&self, mut password_manager: Passwords) -> LprsResult<()> {
|
fn run(&self, mut vault_manager: Vaults<Plain>) -> LprsResult<()> {
|
||||||
if self.path.exists() {
|
if self.path.exists() {
|
||||||
if self
|
if self
|
||||||
.path
|
.path
|
||||||
|
@ -44,30 +44,30 @@ impl RunCommand for Import {
|
||||||
{
|
{
|
||||||
let imported_passwords_len = match self.format {
|
let imported_passwords_len = match self.format {
|
||||||
Format::Lprs => {
|
Format::Lprs => {
|
||||||
let passwords = Passwords::try_reload(
|
let vaults = Vaults::try_reload(
|
||||||
self.path.to_path_buf(),
|
self.path.to_path_buf(),
|
||||||
password_manager.master_password.to_vec(),
|
vault_manager.master_password.to_vec(),
|
||||||
)?;
|
)?;
|
||||||
let passwords_len = passwords.passwords.len();
|
let vaults_len = vaults.vaults.len();
|
||||||
|
|
||||||
password_manager.passwords.extend(passwords.passwords);
|
vault_manager.vaults.extend(vaults.vaults);
|
||||||
password_manager.try_export()?;
|
vault_manager.try_export()?;
|
||||||
passwords_len
|
vaults_len
|
||||||
}
|
}
|
||||||
Format::BitWarden => {
|
Format::BitWarden => {
|
||||||
let passwords: BitWardenPasswords =
|
let vaults: BitWardenPasswords =
|
||||||
serde_json::from_reader(File::open(&self.path)?)?;
|
serde_json::from_reader(File::open(&self.path)?)?;
|
||||||
let passwords_len = passwords.items.len();
|
let vaults_len = vaults.items.len();
|
||||||
|
|
||||||
password_manager
|
vault_manager
|
||||||
.passwords
|
.vaults
|
||||||
.extend(passwords.items.into_iter().map(Password::from));
|
.extend(vaults.items.into_iter().map(Vault::from));
|
||||||
password_manager.try_export()?;
|
vault_manager.try_export()?;
|
||||||
passwords_len
|
vaults_len
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
println!(
|
println!(
|
||||||
"{imported_passwords_len} password{s} were imported successfully",
|
"{imported_passwords_len} vault{s} were imported successfully",
|
||||||
s = if imported_passwords_len >= 2 { "s" } else { "" }
|
s = if imported_passwords_len >= 2 { "s" } else { "" }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Lprs - A local CLI password manager
|
// Lprs - A local CLI vault manager
|
||||||
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
@ -20,7 +20,10 @@ use clap::Args;
|
||||||
use comfy_table::Table;
|
use comfy_table::Table;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
use crate::{password::Passwords, LprsError, LprsResult, RunCommand};
|
use crate::{
|
||||||
|
vault::{vault_state::*, Vaults},
|
||||||
|
LprsError, LprsResult, RunCommand,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
|
@ -49,8 +52,8 @@ pub struct List {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunCommand for List {
|
impl RunCommand for List {
|
||||||
fn run(&self, password_manager: Passwords) -> LprsResult<()> {
|
fn run(&self, vault_manager: Vaults<Plain>) -> LprsResult<()> {
|
||||||
if password_manager.passwords.is_empty() {
|
if vault_manager.vaults.is_empty() {
|
||||||
Err(LprsError::Other(
|
Err(LprsError::Other(
|
||||||
"Looks like there is no passwords to list".to_owned(),
|
"Looks like there is no passwords to list".to_owned(),
|
||||||
))
|
))
|
||||||
|
@ -77,8 +80,8 @@ impl RunCommand for List {
|
||||||
let re = Regex::new(self.search.as_deref().unwrap_or("."))?;
|
let re = Regex::new(self.search.as_deref().unwrap_or("."))?;
|
||||||
|
|
||||||
table.set_header(header);
|
table.set_header(header);
|
||||||
let passwords = password_manager
|
let vaults = vault_manager
|
||||||
.passwords
|
.vaults
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter(|(idx, pass)| {
|
.filter(|(idx, pass)| {
|
||||||
|
@ -112,24 +115,24 @@ impl RunCommand for List {
|
||||||
|
|
||||||
true
|
true
|
||||||
});
|
});
|
||||||
for (idx, password) in passwords {
|
for (idx, vault) in vaults {
|
||||||
let hide_password = "*".repeat(password.password.chars().count());
|
let hide_password = "*".repeat(vault.password.chars().count());
|
||||||
let idx = (idx + 1).to_string();
|
let idx = (idx + 1).to_string();
|
||||||
let mut row = vec![
|
let mut row = vec![
|
||||||
idx.as_str(),
|
idx.as_str(),
|
||||||
password.name.as_str(),
|
vault.name.as_str(),
|
||||||
password.username.as_str(),
|
vault.username.as_str(),
|
||||||
if self.unhide_password {
|
if self.unhide_password {
|
||||||
password.password.as_str()
|
vault.password.as_str()
|
||||||
} else {
|
} else {
|
||||||
hide_password.as_str()
|
hide_password.as_str()
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
if self.with_service {
|
if self.with_service {
|
||||||
row.push(password.service.as_deref().unwrap_or("Not Set"))
|
row.push(vault.service.as_deref().unwrap_or("Not Set"))
|
||||||
}
|
}
|
||||||
if self.with_note {
|
if self.with_note {
|
||||||
row.push(password.note.as_deref().unwrap_or("Not Set"))
|
row.push(vault.note.as_deref().unwrap_or("Not Set"))
|
||||||
}
|
}
|
||||||
table.add_row(row);
|
table.add_row(row);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Lprs - A local CLI password manager
|
// Lprs - A local CLI vault manager
|
||||||
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
@ -19,7 +19,7 @@ use std::path::PathBuf;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
password::{self, Passwords},
|
vault::{self, Vaults},
|
||||||
LprsError, LprsResult, RunCommand,
|
LprsError, LprsResult, RunCommand,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -34,22 +34,22 @@ pub mod remove_command;
|
||||||
|
|
||||||
crate::create_commands!(
|
crate::create_commands!(
|
||||||
enum Commands
|
enum Commands
|
||||||
"Add new password", Add => add_command::Add
|
"Add new vault", Add => add_command::Add
|
||||||
"Remove password", Remove => remove_command::Remove
|
"Remove vault", Remove => remove_command::Remove
|
||||||
"List your password and search", List => list_command::List
|
"List your vaults and search", List => list_command::List
|
||||||
"Clean the password file", Clean => clean_command::Clean
|
"Clean the vaults file", Clean => clean_command::Clean
|
||||||
"Edit the password content", Edit => edit_command::Edit
|
"Edit the vault content", Edit => edit_command::Edit
|
||||||
"Generate password", Gen => gen_command::Gen
|
"Generate a password", Gen => gen_command::Gen
|
||||||
"Export the passwords", Export => export_command::Export
|
"Export the vaults", Export => export_command::Export
|
||||||
"Import passwords", Import => import_command::Import
|
"Import vaults", Import => import_command::Import
|
||||||
);
|
);
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
pub struct Cli {
|
pub struct Cli {
|
||||||
/// The passwords json file, default: $HOME/.local/share/lprs/passwords.json
|
/// The vaults json file
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
passwords_file: Option<PathBuf>,
|
vaults_file: Option<PathBuf>,
|
||||||
|
|
||||||
// TODO: verbose flag
|
// TODO: verbose flag
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
|
@ -59,43 +59,40 @@ pub struct Cli {
|
||||||
impl Cli {
|
impl Cli {
|
||||||
/// Run the cli
|
/// Run the cli
|
||||||
pub fn run(self) -> LprsResult<()> {
|
pub fn run(self) -> LprsResult<()> {
|
||||||
let passwords_file = if let Some(ref path) = self.passwords_file {
|
let vaults_file = if let Some(ref path) = self.vaults_file {
|
||||||
path.clone()
|
path.clone()
|
||||||
} else {
|
} else {
|
||||||
crate::utils::passwords_file()?
|
crate::utils::vaults_file()?
|
||||||
};
|
};
|
||||||
log::debug!(
|
log::debug!("Getting the vaults file: {}", vaults_file.to_string_lossy());
|
||||||
"Getting password file: {}",
|
let vault_manager = if matches!(self.command, Commands::Clean(..) | Commands::Gen(..)) {
|
||||||
passwords_file.to_string_lossy()
|
Vaults {
|
||||||
);
|
vaults_file,
|
||||||
let password_manager = if matches!(self.command, Commands::Clean(..) | Commands::Gen(..)) {
|
|
||||||
Passwords {
|
|
||||||
passwords_file,
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let password = scanpw::scanpw!("Master Password: ");
|
let master_password = scanpw::scanpw!("Master Password: ");
|
||||||
|
|
||||||
if password::is_new_password_file(&passwords_file)? {
|
if vault::is_new_vaults_file(&vaults_file)? {
|
||||||
let analyzed = passwords::analyzer::analyze(&password);
|
let analyzed = passwords::analyzer::analyze(&master_password);
|
||||||
if analyzed.length() < 15 {
|
if analyzed.length() < 15 {
|
||||||
return Err(LprsError::WeakPassword(
|
return Err(LprsError::WeakPassword(
|
||||||
"The password length must be beggier then 15".to_owned(),
|
"The master password length must be beggier then 15".to_owned(),
|
||||||
));
|
));
|
||||||
} else if passwords::scorer::score(&analyzed) < 80.0 {
|
} else if passwords::scorer::score(&analyzed) < 80.0 {
|
||||||
return Err(LprsError::WeakPassword(
|
return Err(LprsError::WeakPassword(
|
||||||
"Your password is not stronge enough".to_owned(),
|
"Your master password is not stronge enough".to_owned(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let master_password = sha256::digest(password);
|
let master_password = sha256::digest(master_password);
|
||||||
Passwords::try_reload(
|
Vaults::try_reload(
|
||||||
passwords_file,
|
vaults_file,
|
||||||
master_password.into_bytes().into_iter().take(32).collect(),
|
master_password.into_bytes().into_iter().take(32).collect(),
|
||||||
)?
|
)?
|
||||||
};
|
};
|
||||||
self.command.run(password_manager)?;
|
self.command.run(vault_manager)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Lprs - A local CLI password manager
|
// Lprs - A local CLI vault manager
|
||||||
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
@ -18,7 +18,10 @@ use std::num::NonZeroU64;
|
||||||
|
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
|
|
||||||
use crate::{password::Passwords, LprsError, LprsResult, RunCommand};
|
use crate::{
|
||||||
|
vault::{vault_state::*, Vaults},
|
||||||
|
LprsError, LprsResult, RunCommand,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
|
@ -32,17 +35,17 @@ pub struct Remove {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunCommand for Remove {
|
impl RunCommand for Remove {
|
||||||
fn run(&self, mut password_manager: Passwords) -> LprsResult<()> {
|
fn run(&self, mut vault_manager: Vaults<Plain>) -> LprsResult<()> {
|
||||||
let index = (self.index.get() - 1) as usize;
|
let index = (self.index.get() - 1) as usize;
|
||||||
if index > password_manager.passwords.len() {
|
if index > vault_manager.vaults.len() {
|
||||||
if !self.force {
|
if !self.force {
|
||||||
return Err(LprsError::Other(
|
return Err(LprsError::Other(
|
||||||
"The index is greater than the passwords counts".to_owned(),
|
"The index is greater than the passwords counts".to_owned(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
password_manager.passwords.remove(index);
|
vault_manager.vaults.remove(index);
|
||||||
password_manager.try_export()?;
|
vault_manager.try_export()?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Lprs - A local CLI password manager
|
// Lprs - A local CLI vault manager
|
||||||
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
@ -34,8 +34,8 @@ pub enum Error {
|
||||||
WeakPassword(String),
|
WeakPassword(String),
|
||||||
#[error("Args Conflict Error: {0}")]
|
#[error("Args Conflict Error: {0}")]
|
||||||
ArgsConflict(String),
|
ArgsConflict(String),
|
||||||
#[error("Invalid Password Index Error: {0}")]
|
#[error("Invalid Vault Index Error: {0}")]
|
||||||
InvalidPasswordIndex(String),
|
InvalidVaultIndex(String),
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Other(String),
|
Other(String),
|
||||||
|
|
||||||
|
@ -56,6 +56,7 @@ pub enum Error {
|
||||||
impl Error {
|
impl Error {
|
||||||
/// Return the exit code of the error
|
/// Return the exit code of the error
|
||||||
pub fn exit_code(&self) -> ExitCode {
|
pub fn exit_code(&self) -> ExitCode {
|
||||||
|
// TODO: Exit with more specific exit code
|
||||||
ExitCode::FAILURE
|
ExitCode::FAILURE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Lprs - A local CLI password manager
|
// Lprs - A local CLI vault manager
|
||||||
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
@ -40,11 +40,11 @@
|
||||||
/// impl crate::RunCommand for TestCommands {
|
/// impl crate::RunCommand for TestCommands {
|
||||||
/// fn run(
|
/// fn run(
|
||||||
/// &self,
|
/// &self,
|
||||||
/// password_manager: crate::password::Passwords,
|
/// vault_manager: crate::vault::Vaults,
|
||||||
/// ) -> crate::LprsResult<()> {
|
/// ) -> crate::LprsResult<()> {
|
||||||
/// match self {
|
/// match self {
|
||||||
/// Self::Test(command) => command.run(password_manager),
|
/// Self::Test(command) => command.run(vault_manager),
|
||||||
/// Self::Some(command) => command.run(password_manager),
|
/// Self::Some(command) => command.run(vault_manager),
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
|
@ -63,10 +63,10 @@ macro_rules! create_commands {
|
||||||
|
|
||||||
#[automatically_derived]
|
#[automatically_derived]
|
||||||
impl $crate::RunCommand for $enum_name{
|
impl $crate::RunCommand for $enum_name{
|
||||||
fn run(&self, password_manager: $crate::password::Passwords) -> $crate::LprsResult<()> {
|
fn run(&self, vault_manager: $crate::vault::Vaults<$crate::vault::vault_state::Plain>) -> $crate::LprsResult<()> {
|
||||||
match self {
|
match self {
|
||||||
$(
|
$(
|
||||||
Self::$varint(command) => command.run(password_manager),
|
Self::$varint(command) => command.run(vault_manager),
|
||||||
)+
|
)+
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Lprs - A local CLI password manager
|
// Lprs - A local CLI vault manager
|
||||||
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
@ -24,8 +24,8 @@ use clap::Parser;
|
||||||
|
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
pub mod password;
|
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
pub mod vault;
|
||||||
|
|
||||||
mod macros;
|
mod macros;
|
||||||
mod traits;
|
mod traits;
|
||||||
|
@ -34,7 +34,7 @@ pub use errors::{Error as LprsError, Result as LprsResult};
|
||||||
pub use traits::*;
|
pub use traits::*;
|
||||||
|
|
||||||
pub const STANDARDBASE: GeneralPurpose = GeneralPurpose::new(&alphabet::STANDARD, PAD);
|
pub const STANDARDBASE: GeneralPurpose = GeneralPurpose::new(&alphabet::STANDARD, PAD);
|
||||||
pub const DEFAULT_PASSWORD_FILE: &str = "passwords.json";
|
pub const DEFAULT_VAULTS_FILE: &str = "vaults.json";
|
||||||
|
|
||||||
#[cfg(feature = "update-notify")]
|
#[cfg(feature = "update-notify")]
|
||||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
|
@ -1,162 +0,0 @@
|
||||||
// Lprs - A local CLI password 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>.
|
|
||||||
|
|
||||||
use std::{fs, path::PathBuf};
|
|
||||||
|
|
||||||
use clap::{Parser, ValueEnum};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::{LprsError, LprsResult};
|
|
||||||
|
|
||||||
pub mod cipher;
|
|
||||||
|
|
||||||
mod bitwarden;
|
|
||||||
mod validator;
|
|
||||||
|
|
||||||
pub use bitwarden::*;
|
|
||||||
pub use validator::*;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, ValueEnum)]
|
|
||||||
pub enum Format {
|
|
||||||
Lprs,
|
|
||||||
BitWarden,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The password struct
|
|
||||||
#[serde_with_macros::skip_serializing_none]
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, Parser)]
|
|
||||||
pub struct Password {
|
|
||||||
/// The name of the password
|
|
||||||
#[arg(short, long)]
|
|
||||||
pub name: String,
|
|
||||||
/// The username
|
|
||||||
#[arg(short, long)]
|
|
||||||
pub username: String,
|
|
||||||
/// The password
|
|
||||||
#[arg(short, long)]
|
|
||||||
pub password: String,
|
|
||||||
/// The service name. e.g the website url
|
|
||||||
#[arg(short, long)]
|
|
||||||
pub service: Option<String>,
|
|
||||||
/// The note of the password
|
|
||||||
#[arg(short = 'o', long)]
|
|
||||||
pub note: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The passwords manager
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Passwords {
|
|
||||||
/// Hash of the master password
|
|
||||||
pub master_password: Vec<u8>,
|
|
||||||
/// The json passwords file
|
|
||||||
pub passwords_file: PathBuf,
|
|
||||||
/// The passwords
|
|
||||||
pub passwords: Vec<Password>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Password {
|
|
||||||
/// Encrypt the password data
|
|
||||||
pub fn encrypt(self, master_password: &[u8]) -> LprsResult<Self> {
|
|
||||||
Ok(Self {
|
|
||||||
name: cipher::encrypt(master_password, &self.name)?,
|
|
||||||
username: cipher::encrypt(master_password, &self.username)?,
|
|
||||||
password: cipher::encrypt(master_password, &self.password)?,
|
|
||||||
service: self
|
|
||||||
.service
|
|
||||||
.map(|url| cipher::encrypt(master_password, &url))
|
|
||||||
.transpose()?,
|
|
||||||
note: self
|
|
||||||
.note
|
|
||||||
.map(|note| cipher::encrypt(master_password, ¬e))
|
|
||||||
.transpose()?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decrypt the password data
|
|
||||||
pub fn decrypt(self, master_password: &[u8]) -> LprsResult<Self> {
|
|
||||||
Ok(Self {
|
|
||||||
name: cipher::decrypt(master_password, &self.name)?,
|
|
||||||
username: cipher::decrypt(master_password, &self.username)?,
|
|
||||||
password: cipher::decrypt(master_password, &self.password)?,
|
|
||||||
service: self
|
|
||||||
.service
|
|
||||||
.map(|url| cipher::decrypt(master_password, &url))
|
|
||||||
.transpose()?,
|
|
||||||
note: self
|
|
||||||
.note
|
|
||||||
.map(|note| cipher::decrypt(master_password, ¬e))
|
|
||||||
.transpose()?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Passwords {
|
|
||||||
/// Create new Passwords instnce
|
|
||||||
pub fn new(
|
|
||||||
master_password: Vec<u8>,
|
|
||||||
passwords_file: PathBuf,
|
|
||||||
passwords: Vec<Password>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
master_password,
|
|
||||||
passwords_file,
|
|
||||||
passwords,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Encrypt the passwords
|
|
||||||
pub fn encrypt(self) -> LprsResult<Self> {
|
|
||||||
Ok(Self {
|
|
||||||
passwords: self
|
|
||||||
.passwords
|
|
||||||
.into_iter()
|
|
||||||
.map(|p| p.encrypt(&self.master_password))
|
|
||||||
.collect::<LprsResult<Vec<Password>>>()?,
|
|
||||||
..self
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reload the passwords from the file
|
|
||||||
pub fn try_reload(passwords_file: PathBuf, master_password: Vec<u8>) -> LprsResult<Self> {
|
|
||||||
let passwords =
|
|
||||||
serde_json::from_str::<Vec<Password>>(&fs::read_to_string(&passwords_file)?)?
|
|
||||||
.into_iter()
|
|
||||||
.map(|p| p.decrypt(master_password.as_slice()))
|
|
||||||
.collect::<LprsResult<Vec<Password>>>()?;
|
|
||||||
|
|
||||||
Ok(Self::new(master_password, passwords_file, passwords))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Export the passwords to the file
|
|
||||||
pub fn try_export(self) -> LprsResult<()> {
|
|
||||||
let path = self.passwords_file.to_path_buf();
|
|
||||||
fs::write(path, serde_json::to_string(&self.encrypt()?.passwords)?).map_err(LprsError::Io)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add new password
|
|
||||||
pub fn add_password(&mut self, password: Password) {
|
|
||||||
self.passwords.push(password)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToString for Format {
|
|
||||||
fn to_string(&self) -> String {
|
|
||||||
self.to_possible_value()
|
|
||||||
.expect("There is no skiped values")
|
|
||||||
.get_name()
|
|
||||||
.to_owned()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Lprs - A local CLI password manager
|
// Lprs - A local CLI vault manager
|
||||||
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
@ -14,9 +14,12 @@
|
||||||
// You should have received a copy of the GNU General Public License
|
// 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>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>.
|
||||||
|
|
||||||
use crate::{password::Passwords, LprsResult};
|
use crate::{
|
||||||
|
vault::{vault_state::*, Vaults},
|
||||||
|
LprsResult,
|
||||||
|
};
|
||||||
|
|
||||||
/// Trait to run the command
|
/// Trait to run the command
|
||||||
pub trait RunCommand {
|
pub trait RunCommand {
|
||||||
fn run(&self, password_manager: Passwords) -> LprsResult<()>;
|
fn run(&self, vault_manager: Vaults<Plain>) -> LprsResult<()>;
|
||||||
}
|
}
|
||||||
|
|
14
src/utils.rs
14
src/utils.rs
|
@ -1,4 +1,4 @@
|
||||||
// Lprs - A local CLI password manager
|
// Lprs - A local CLI vault manager
|
||||||
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
@ -31,13 +31,13 @@ pub fn local_project_file(filename: &str) -> LprsResult<PathBuf> {
|
||||||
Ok(local_dir.join(filename))
|
Ok(local_dir.join(filename))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the default passwords json file
|
/// Returns the default vaults json file
|
||||||
pub fn passwords_file() -> LprsResult<PathBuf> {
|
pub fn vaults_file() -> LprsResult<PathBuf> {
|
||||||
let password_file = local_project_file(crate::DEFAULT_PASSWORD_FILE)?;
|
let vaults_file = local_project_file(crate::DEFAULT_VAULTS_FILE)?;
|
||||||
if !password_file.exists() {
|
if !vaults_file.exists() {
|
||||||
fs::write(&password_file, "[]")?;
|
fs::write(&vaults_file, "[]")?;
|
||||||
}
|
}
|
||||||
Ok(password_file)
|
Ok(vaults_file)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retuns the current lprs version from `crates.io`
|
/// Retuns the current lprs version from `crates.io`
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::{Password, Passwords};
|
use super::{vault_state::*, Vault, Vaults};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct BitWardenLoginData {
|
pub struct BitWardenLoginData {
|
||||||
|
@ -39,28 +39,28 @@ pub struct BitWardenPasswords {
|
||||||
pub items: Vec<BitWardenPassword>,
|
pub items: Vec<BitWardenPassword>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<BitWardenPassword> for Password {
|
impl From<BitWardenPassword> for Vault<Plain> {
|
||||||
fn from(value: BitWardenPassword) -> Self {
|
fn from(value: BitWardenPassword) -> Self {
|
||||||
Self {
|
Self::new(
|
||||||
name: value.name,
|
value.name,
|
||||||
username: value
|
value
|
||||||
.login
|
.login
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or_else(String::new, |l| l.username.to_owned().unwrap_or_default()),
|
.map_or_else(String::new, |l| l.username.to_owned().unwrap_or_default()),
|
||||||
password: value
|
value
|
||||||
.login
|
.login
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or_else(String::new, |l| l.password.to_owned().unwrap_or_default()),
|
.map_or_else(String::new, |l| l.password.to_owned().unwrap_or_default()),
|
||||||
service: value
|
value
|
||||||
.login
|
.login
|
||||||
.and_then(|l| l.uris.and_then(|p| p.first().map(|u| u.uri.clone()))),
|
.and_then(|l| l.uris.and_then(|p| p.first().map(|u| u.uri.clone()))),
|
||||||
note: value.notes,
|
value.notes,
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Password> for BitWardenPassword {
|
impl From<Vault<Plain>> for BitWardenPassword {
|
||||||
fn from(value: Password) -> Self {
|
fn from(value: Vault<Plain>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
ty: 1,
|
ty: 1,
|
||||||
name: value.name,
|
name: value.name,
|
||||||
|
@ -76,13 +76,13 @@ impl From<Password> for BitWardenPassword {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Passwords> for BitWardenPasswords {
|
impl From<Vaults<Plain>> for BitWardenPasswords {
|
||||||
fn from(value: Passwords) -> Self {
|
fn from(value: Vaults<Plain>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
encrypted: false,
|
encrypted: false,
|
||||||
folders: Vec::new(),
|
folders: Vec::new(),
|
||||||
items: value
|
items: value
|
||||||
.passwords
|
.vaults
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(BitWardenPassword::from)
|
.map(BitWardenPassword::from)
|
||||||
.collect(),
|
.collect(),
|
|
@ -1,4 +1,4 @@
|
||||||
// Lprs - A local CLI password manager
|
// Lprs - A local CLI vault manager
|
||||||
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// This program is free software: you can redistribute it and/or modify
|
209
src/vault/mod.rs
Normal file
209
src/vault/mod.rs
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
// 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>.
|
||||||
|
|
||||||
|
use std::{fs, marker::PhantomData, path::PathBuf};
|
||||||
|
|
||||||
|
use clap::{Parser, ValueEnum};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{LprsError, LprsResult};
|
||||||
|
use vault_state::*;
|
||||||
|
|
||||||
|
pub mod cipher;
|
||||||
|
|
||||||
|
mod bitwarden;
|
||||||
|
mod validator;
|
||||||
|
|
||||||
|
pub use bitwarden::*;
|
||||||
|
pub use validator::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, ValueEnum)]
|
||||||
|
pub enum Format {
|
||||||
|
Lprs,
|
||||||
|
BitWarden,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The states of the vaults
|
||||||
|
pub mod vault_state {
|
||||||
|
/// Means the vault is encrypted
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct Encrypted;
|
||||||
|
/// Means the vault is not encrypted
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct Plain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The vault struct
|
||||||
|
#[serde_with_macros::skip_serializing_none]
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, Parser)]
|
||||||
|
pub struct Vault<T>
|
||||||
|
where
|
||||||
|
T: std::fmt::Debug + Clone,
|
||||||
|
{
|
||||||
|
/// The name of the vault
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub name: String,
|
||||||
|
/// The username
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub username: String,
|
||||||
|
/// The password
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub password: String,
|
||||||
|
/// The service name. e.g the website url
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub service: Option<String>,
|
||||||
|
/// Add a note to the vault
|
||||||
|
#[arg(short = 'o', long)]
|
||||||
|
pub note: Option<String>,
|
||||||
|
|
||||||
|
/// State phantom
|
||||||
|
#[serde(skip)]
|
||||||
|
#[arg(skip)]
|
||||||
|
phantom: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The vaults manager
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Vaults<T>
|
||||||
|
where
|
||||||
|
T: std::fmt::Debug + Clone,
|
||||||
|
{
|
||||||
|
/// Hash of the master password
|
||||||
|
pub master_password: Vec<u8>,
|
||||||
|
/// The json vaults file
|
||||||
|
pub vaults_file: PathBuf,
|
||||||
|
/// The vaults
|
||||||
|
pub vaults: Vec<Vault<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Vault<T>
|
||||||
|
where
|
||||||
|
T: std::fmt::Debug + Clone,
|
||||||
|
{
|
||||||
|
/// Create new [`Vault`] instance
|
||||||
|
pub fn new(
|
||||||
|
name: impl Into<String>,
|
||||||
|
username: impl Into<String>,
|
||||||
|
password: impl Into<String>,
|
||||||
|
service: Option<impl Into<String>>,
|
||||||
|
note: Option<impl Into<String>>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
name: name.into(),
|
||||||
|
username: username.into(),
|
||||||
|
password: password.into(),
|
||||||
|
service: service.map(|s| s.into()),
|
||||||
|
note: note.map(|s| s.into()),
|
||||||
|
phantom: std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vault<Encrypted> {
|
||||||
|
/// Decrypt the vault
|
||||||
|
pub fn decrypt(&self, master_password: &[u8]) -> LprsResult<Vault<Plain>> {
|
||||||
|
Ok(Vault::<Plain>::new(
|
||||||
|
cipher::decrypt(master_password, &self.name)?,
|
||||||
|
cipher::decrypt(master_password, &self.username)?,
|
||||||
|
cipher::decrypt(master_password, &self.password)?,
|
||||||
|
self.service
|
||||||
|
.as_ref()
|
||||||
|
.map(|url| cipher::decrypt(master_password, url))
|
||||||
|
.transpose()?,
|
||||||
|
self.note
|
||||||
|
.as_ref()
|
||||||
|
.map(|note| cipher::decrypt(master_password, note))
|
||||||
|
.transpose()?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vault<Plain> {
|
||||||
|
/// Encrypt the vault
|
||||||
|
pub fn encrypt(&self, master_password: &[u8]) -> LprsResult<Vault<Encrypted>> {
|
||||||
|
Ok(Vault::<Encrypted>::new(
|
||||||
|
cipher::encrypt(master_password, &self.name)?,
|
||||||
|
cipher::encrypt(master_password, &self.username)?,
|
||||||
|
cipher::encrypt(master_password, &self.password)?,
|
||||||
|
self.service
|
||||||
|
.as_ref()
|
||||||
|
.map(|url| cipher::encrypt(master_password, url))
|
||||||
|
.transpose()?,
|
||||||
|
self.note
|
||||||
|
.as_ref()
|
||||||
|
.map(|note| cipher::encrypt(master_password, note))
|
||||||
|
.transpose()?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Vaults<T>
|
||||||
|
where
|
||||||
|
T: std::fmt::Debug + Clone,
|
||||||
|
{
|
||||||
|
/// Create new [`Vaults`] instnce
|
||||||
|
pub fn new(master_password: Vec<u8>, vaults_file: PathBuf, vaults: Vec<Vault<T>>) -> Self {
|
||||||
|
Self {
|
||||||
|
master_password,
|
||||||
|
vaults_file,
|
||||||
|
vaults,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vaults<Plain> {
|
||||||
|
/// Encrypt the vaults
|
||||||
|
pub fn encrypt_vaults(&self) -> LprsResult<Vec<Vault<Encrypted>>> {
|
||||||
|
self.vaults
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.encrypt(&self.master_password))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reload the vaults from the file then decrypt it
|
||||||
|
pub fn try_reload(vaults_file: PathBuf, master_password: Vec<u8>) -> LprsResult<Self> {
|
||||||
|
let vaults =
|
||||||
|
serde_json::from_str::<Vec<Vault<Encrypted>>>(&fs::read_to_string(&vaults_file)?)?
|
||||||
|
.into_iter()
|
||||||
|
.map(|p| p.decrypt(master_password.as_slice()))
|
||||||
|
.collect::<LprsResult<Vec<Vault<Plain>>>>()?;
|
||||||
|
|
||||||
|
Ok(Self::new(master_password, vaults_file, vaults))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encrypt the vaults then export it to the file
|
||||||
|
pub fn try_export(self) -> LprsResult<()> {
|
||||||
|
fs::write(
|
||||||
|
&self.vaults_file,
|
||||||
|
serde_json::to_string(&self.encrypt_vaults()?)?,
|
||||||
|
)
|
||||||
|
.map_err(LprsError::Io)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add new vault
|
||||||
|
pub fn add_vault(&mut self, vault: Vault<Plain>) {
|
||||||
|
self.vaults.push(vault)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToString for Format {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
self.to_possible_value()
|
||||||
|
.expect("There is no skiped values")
|
||||||
|
.get_name()
|
||||||
|
.to_owned()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
// Lprs - A local CLI password manager
|
// Lprs - A local CLI vault manager
|
||||||
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
// Copyright (C) 2024 Awiteb <a@4rs.nl>
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
@ -18,15 +18,15 @@ use std::{fs, path::Path};
|
||||||
|
|
||||||
use crate::LprsResult;
|
use crate::LprsResult;
|
||||||
|
|
||||||
use super::Password;
|
use super::{vault_state::*, Vault};
|
||||||
|
|
||||||
/// Return if the password file new file or not
|
/// Return if the vaults file new file or not
|
||||||
pub fn is_new_password_file(path: &Path) -> LprsResult<bool> {
|
pub fn is_new_vaults_file(path: &Path) -> LprsResult<bool> {
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
let file_content = fs::read_to_string(path)?;
|
let file_content = fs::read_to_string(path)?;
|
||||||
if !file_content.is_empty()
|
if !file_content.is_empty()
|
||||||
&& file_content.trim() != "[]"
|
&& file_content.trim() != "[]"
|
||||||
&& serde_json::from_str::<Vec<Password>>(&file_content).is_ok()
|
&& serde_json::from_str::<Vec<Vault<Encrypted>>>(&file_content).is_ok()
|
||||||
{
|
{
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
Loading…
Reference in a new issue