Compare commits

..

No commits in common. "master" and "v2.0.0-rc.0" have entirely different histories.

31 changed files with 204 additions and 369 deletions

View file

@ -9,12 +9,16 @@ assignees: ''
## Checks
* [ ] I added a descriptive title to this issue
* [ ] I have read [the documentation](https://lprs.4rs.nl) and still think this it's a bug
* [ ] I have searched Google for similar issues and couldn't find anything
* [ ] I have read [the README](https://git.4rs.nl/awiteb/lprs/src/branch/master/README.md) and still think this is a bug
## Version
<!-- Report for the bug only if it's present in the latest version of Lprs.
If you are not using the latest version, please update and check if the bug is still present. -->
<!-- Run `rustc --version` to get the version -->
Rustc version: `...`
<!-- Run `lprs --version` to get the version, and make sure it's the latest one -->
Lprs version: `...`
## Description

View file

@ -12,5 +12,5 @@ For the location, please provide a link to the code or the documentation.
| Location | Suggestion |
|----------|------------|
| location | Suggestion |
| Code-link| Suggestion |

View file

@ -58,8 +58,8 @@ jobs:
cp target/${{ matrix.target }}/release/$BIN_NAME.exe release-dir/$APP_NAME.exe || true
cp target/${{ matrix.target }}/release/$BIN_NAME release-dir/$APP_NAME || true
cd release-dir
test -f $APP_NAME && sha256sum $APP_NAME > $APP_NAME.sha256 || true
test -f $APP_NAME.exe && sha256sum $APP_NAME.exe > $APP_NAME.exe.sha256 || true
sha256sum $APP_NAME > $APP_NAME.sha256 || true
sha256sum $APP_NAME.exe > $APP_NAME.exe.sha256 || true
- name: Build the asset (update-notify)
run: |
@ -68,8 +68,8 @@ jobs:
cp target/${{ matrix.target }}/release/$BIN_NAME.exe release-dir/$APP_NAME.exe || true
cp target/${{ matrix.target }}/release/$BIN_NAME release-dir/$APP_NAME || true
cd release-dir
test -f $APP_NAME && sha256sum $APP_NAME > $APP_NAME.sha256 || true
test -f $APP_NAME.exe && sha256sum $APP_NAME.exe > $APP_NAME.exe.sha256 || true
sha256sum $APP_NAME > $APP_NAME.sha256 || true
sha256sum $APP_NAME.exe > $APP_NAME.exe.sha256 || true
- uses: actions/upload-artifact@v3
with:

View file

@ -25,6 +25,7 @@ jobs:
run: |
git config user.name forgejo-actions
git config user.email forgejo-actions@noreply.localhost
git config --unset-all extensions.worktreeconfig
git-cliff > CHANGELOG.md
if [[ $(git status | grep --extended-regexp '^\s+modified:\s+CHANGELOG.md$') ]]; then
git add CHANGELOG.md

View file

@ -12,7 +12,7 @@ jobs:
runs-on: debian
steps:
- uses: actions/checkout@v4
- uses: https://codeberg.org/awiteb/rust-action@v1.74
- uses: https://codeberg.org/TheAwiteb/rust-action@v1.74
- name: Check MSRV
run: cargo +1.74 build
- name: Build the source code

View file

@ -11,7 +11,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Ability to pass the master password as option ([`62d4060`](https://git.4rs.nl/awiteb/lprs/commit/62d4060bb8ffdfb834d5c860f79414cbca211f72))
- Add `--json` flag to the `list` command ([`a7ad8b4`](https://git.4rs.nl/awiteb/lprs/commit/a7ad8b468277aa5bc1df8616d93b757c3eab303f))
- Add `get` command ([`f9fbf1a`](https://git.4rs.nl/awiteb/lprs/commit/f9fbf1a0b7b85638ad64287738e05ec1a1c35d25))
- Case insensitive filter ([**#61**](https://git.4rs.nl/awiteb/lprs/issues/61)) ([`e023104`](https://git.4rs.nl/awiteb/lprs/commit/e02310401add677170bd402d1430ec34e2aded6d))
- Encrypt the hole vault file ([`6f6966d`](https://git.4rs.nl/awiteb/lprs/commit/6f6966d5b25b2b5047081304f7597fe80ec95387))
- **BC**: The previous format is not supported after this commit, so
you must export your vaults in bit-warden format (before this commit)
@ -21,32 +20,25 @@ and then re-invoke them (after this commit)
- Make the `name` option in `edit` & `add` as argument ([**#29**](https://git.4rs.nl/awiteb/lprs/issues/29)) ([`127f377`](https://git.4rs.nl/awiteb/lprs/commit/127f3779f8d805c7e1f5209555d8929082f85c82))
- **BC**: Change the `name` option to argument in `name` and `edit` commands
- Make the username & password optional in the vault ([**#12**](https://git.4rs.nl/awiteb/lprs/issues/12)) ([`af6664d`](https://git.4rs.nl/awiteb/lprs/commit/af6664da5c08cc39cf732d64ba74de1731095723))
- Remove vault field if its value is empty string ([`5d30b8b`](https://git.4rs.nl/awiteb/lprs/commit/5d30b8b021d5e2172aa8bbaafaa31c10980c4107))
- Support TOTP ([`6f83bcc`](https://git.4rs.nl/awiteb/lprs/commit/6f83bcccf94b88181d86358a922e61e3d3a2dad8))
- Support `--verbose` flag ([**#23**](https://git.4rs.nl/awiteb/lprs/issues/23)) ([`31a68b9`](https://git.4rs.nl/awiteb/lprs/commit/31a68b927764a7eb0b38539f630b70fa258ae7aa))
- Support `rm` and `ls` aliases ([**#22**](https://git.4rs.nl/awiteb/lprs/issues/22)) ([`791d390`](https://git.4rs.nl/awiteb/lprs/commit/791d390e636c1c29af23b343edb66279b791b121))
- Support changing master password ([**#50**](https://git.4rs.nl/awiteb/lprs/issues/50)) ([`ced363a`](https://git.4rs.nl/awiteb/lprs/commit/ced363a37f6f64282ca1a1fb022aa3d030edff79))
- Support completion generating ([`f022574`](https://git.4rs.nl/awiteb/lprs/commit/f022574631bfb1b6a62f95d3259617f302059781))
- Support custom fields ([`da568ab`](https://git.4rs.nl/awiteb/lprs/commit/da568ab5e9414ef77831066eb9b09621c0fedaee))
- Support entering custom keys value via STDIN ([**#64**](https://git.4rs.nl/awiteb/lprs/issues/64)) ([`5e4fb0e`](https://git.4rs.nl/awiteb/lprs/commit/5e4fb0ea7cafd62fc93d443a659215889e0520d5))
- Support export and import with different password ([`a6483cf`](https://git.4rs.nl/awiteb/lprs/commit/a6483cf333e6a5f3a0d48317b50c6304cfd956bb))
- Support removing multiple vaults in single command ([**#66**](https://git.4rs.nl/awiteb/lprs/issues/66)) ([`6293ead`](https://git.4rs.nl/awiteb/lprs/commit/6293eadafb604d822a8bd376c9d170d8c9c50524))
- Validate args before ask for the master password ([**#17**](https://git.4rs.nl/awiteb/lprs/issues/17)) ([`b4bcaa9`](https://git.4rs.nl/awiteb/lprs/commit/b4bcaa92ca63b7c71ea5c28d5e9a6af3ecb88a91))
### Changed
- Change 'password manager' to 'vault manager' ([`bae0cf1`](https://git.4rs.nl/awiteb/lprs/commit/bae0cf174736d9a1cd61becd20f7d87cf137249c))
- Make the `totp_now` function better ([`9a417e7`](https://git.4rs.nl/awiteb/lprs/commit/9a417e7d0b1a242a5ca2a41ed2a9f72ce8685b5f))
- Rename just file ([`e231352`](https://git.4rs.nl/awiteb/lprs/commit/e231352009c21886772b8f039d3e51ba0aeb7616))
- Add a ecryption state to the vault ([`4def4aa`](https://git.4rs.nl/awiteb/lprs/commit/4def4aadb20cc367d57466dc5e88c3043e468d20))
- **BC**: Moving from password to vault
- Move from GitHub to Forgejo ([`6163c3f`](https://git.4rs.nl/awiteb/lprs/commit/6163c3ff26ab81b07490a798f4047a09565ab1ac))
- Rename `Password`s `Vault`s ([`f6aaecb`](https://git.4rs.nl/awiteb/lprs/commit/f6aaecb9cf43d7dfa3ef653ff0cd117b3197308b))
- Use `Either<usize, String>` type instade of `String` for index or name ([**#65**](https://git.4rs.nl/awiteb/lprs/issues/65)) ([`1c90a82`](https://git.4rs.nl/awiteb/lprs/commit/1c90a825440b1c8ad4eee627407797e0b017a279))
- Use select for vaults listing ([**#19**](https://git.4rs.nl/awiteb/lprs/issues/19)) ([`83c7296`](https://git.4rs.nl/awiteb/lprs/commit/83c7296bf7bf469423f53b024cb65e608ff6c9d9))
### Fixed
- Merge rust ci jobs into one job ([**#2**](https://git.4rs.nl/awiteb/lprs/issues/2)) ([`34eb9d1`](https://git.4rs.nl/awiteb/lprs/commit/34eb9d10f0ad514c6a7878fd8415a50f04db2be8))
- Create the vaults file if it's not exist ([**#60**](https://git.4rs.nl/awiteb/lprs/issues/60)) ([`81a3605`](https://git.4rs.nl/awiteb/lprs/commit/81a360519ec97e63c542bef6cc69f1707198c81f))
- Overflow in utils::vault_by_index_or_name function ([`40e49bf`](https://git.4rs.nl/awiteb/lprs/commit/40e49bffe4e9ecd682eb746deafd68bd088dd415))
- Reject empty string field value ([`6f5ca5f`](https://git.4rs.nl/awiteb/lprs/commit/6f5ca5f4524bec3cda2990f352080f08524e578b))
- Show the totp code in `get` command ([`38f6447`](https://git.4rs.nl/awiteb/lprs/commit/38f6447681d20cef313ed270cc67edc99a5ab3e2))
- Validate all fields in `add` & `edit` ([`02bf53b`](https://git.4rs.nl/awiteb/lprs/commit/02bf53b2a1fd420bf66ac571531d060499559c29))
### Removed

9
Cargo.lock generated
View file

@ -349,12 +349,6 @@ version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "env_logger"
version = "0.10.2"
@ -704,7 +698,7 @@ checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "lprs"
version = "2.0.0-rc.2"
version = "2.0.0-rc.0"
dependencies = [
"aes",
"base32",
@ -714,7 +708,6 @@ dependencies = [
"clap",
"clap_complete",
"directories",
"either",
"inquire",
"log",
"passwords",

View file

@ -1,12 +1,11 @@
[package]
name = "lprs"
version = "2.0.0-rc.2"
version = "2.0.0-rc.0"
edition = "2021"
license = "GPL-3.0-or-later"
authors = ["Awiteb <a@4rs.nl>"]
readme = "README.md"
description = "A local CLI vaults manager. For human and machine use"
homepage = "https://lprs.4rs.nl"
repository = "https://git.4rs.nl/awiteb/lprs"
rust-version = "1.74.1"
keywords = ["password", "vault", "manager", "CLI"]
@ -33,7 +32,6 @@ base64 = "0.22.1"
clap_complete = "4.5.2"
totp-lite = "2.0.1"
base32 = "0.5.0"
either = { version = "1.13.0", default-features = false }
[features]
default = ["update-notify"]

View file

@ -1,7 +1,7 @@
<div align="center">
# Lprs
A local vault manager designed to securely store and manage your vaults.
[![Docs](https://img.shields.io/badge/docs-lprs.4rs.nl-purple)](https://lprs.4rs.nl)
@ -14,7 +14,6 @@ A local vault manager designed to securely store and manage your vaults.
</div>
## Features
- Auto checks for updates (Can be disabled).
- Passing the master password as an argument and via stdin.
- Changing the master password.
@ -37,21 +36,17 @@ A local vault manager designed to securely store and manage your vaults.
## Installation
### Build from source (MSRV: `1.74.0`)
```bash
# From crates.io
cargo install lprs
cargo install lprs
# From source (after cloning the repository)
# The binary will be in target/release/lprs
cargo build --release
```
This will build Lprs with update checking enabled. If you want to disable update
checking, you can build Lprs without the default features by passing the
`--no-default-features` flag.
This will build Lprs with update checking enabled. If you want to disable update checking, you can build Lprs without the default features by passing the `--no-default-features` flag.
### Pre-built binaries
Pre-built binaries are available for Linux and Windows on the [releases
page](https://git.4rs.nl/awiteb/lprs/releases/latest). There is tow version of
the binary, one with update checking enabled and other not, the binaries with
@ -59,8 +54,7 @@ update checking enabled have the `lrps-update-notify` prefix.
## Usage
Lprs provides a command-line interface for managing your vaults. The following
commands are available:
Lprs provides a command-line interface for managing your vaults. The following commands are available:
```
A local CLI vaults manager. For human and machine use
@ -77,7 +71,7 @@ Commands:
get Get a entire vault or single field from it
export Export the vaults
import Import vaults
change-master-password Change master password, reencrypt the vaults with new password
change-master-password Change the master password
completion Generate shell completion
help Print this message or the help of the given subcommand(s)
@ -85,37 +79,26 @@ Options:
-f, --vaults-file <VAULTS_FILE> The vaults json file
-v, --verbose Show the logs in the stdout
-m, --master-password <MASTER_PASSWORD> The master password, or you will prompt it
-h, --help Print help (see more with '--help')
-h, --help Print help
-V, --version Print version
```
## Documentation
You can find the full documentation for Lprs here <https://lprs.4rs.nl>.
You can find the full documentation for Lprs on the official website at https://lprs.4rs.nl.
## Contributing
Contributions to Lprs are welcome! If you would like to contribute, please
follow the guidelines outlined in the [CONTRIBUTING.md](CONTRIBUTING.md) file.
Contributions to Lprs are welcome! If you would like to contribute, please follow the guidelines outlined in the [CONTRIBUTING.md](CONTRIBUTING.md) file.
## Mirrors
This repository is mirrored on the following platforms:
- [GitHub](https://github.com/TheAwiteb/lprs)
- [Codeberg](https://codeberg.org/awiteb/lprs)
## License
Lprs is licensed under the GPL-3.0 License. This means that you are free to use,
modify, and distribute the software under the terms of this license. Please
refer to the [LICENSE](LICENSE) file for more details.
Lprs is licensed under the GPL-3.0 License. This means that you are free to use, modify, and distribute the software under the terms of this license. Please refer to the [LICENSE](LICENSE) file for more details.
---
## Support
If you like this project and want to support it, you can do so by donating via
[Ko-fi](https://ko-fi.com/awiteb).
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/awiteb)

View file

@ -4,9 +4,3 @@ language = "en"
multilingual = false
src = "docs"
title = "Lprs Documentation"
[output.html]
edit-url-template = "https://git.4rs.nl/awiteb/lprs/_edit/master/{path}"
git-repository-icon = "fa-git"
git-repository-url = "https://git.4rs.nl/awiteb/lprs"

View file

@ -8,7 +8,7 @@
- [Editing a vault](commands/edit.md)
- [Getting a vault](commands/get.md)
- [Listing all vaults](commands/list.md)
- [Cleaning the vaults](commands/clean.md)
- [Clening the vaults](commands/clean.md)
- [Generating a password](commands/gen.md)
- [Importing and exporting vaults](commands/import-export.md)
- [Changing the master password](commands/change-master-password.md)

View file

@ -36,7 +36,7 @@ Now let's take a look at the available commands and how to use them.
- [Editing a vault](commands/edit.md)
- [Getting a vault](commands/get.md)
- [Listing all vaults](commands/list.md)
- [Cleaning the vaults](commands/clean.md)
- [Clening the vaults](commands/clean.md)
- [Generating a password](commands/generate-password.md)
- [Importing and exporting vaults](commands/import-export.md)
- [Changing the master password](commands/change-master-password.md)

View file

@ -35,10 +35,8 @@ Options:
-t, --totp-secret [<TOTP_SECRET>]
The TOTP secret, if there is no value you will prompt it
-c, --custom <KEY(=VALUE)?>
-c, --custom <KEY=VALUE>
Add a custom field to the vault
If there is no value, you will enter it through a prompt
-f, --force
Force add, will not return error if there is a problem with the args.
@ -47,6 +45,9 @@ Options:
-h, --help
Print help (see a summary with '-h')
-V, --version
Print version
```
So, to add a vault you need to provide a name for the vault, and you can provide

View file

@ -1,9 +1,13 @@
# Cleaning the vaults
# Clening the vaults
## Usage
```
Usage: lprs clean
Options:
-h, --help Print help
-V, --version Print version
```
Is simple, just run `lprs clean` and the vaults file will be cleaned, removing

View file

@ -14,28 +14,24 @@ Options:
The new vault name
-u, --username <USERNAME>
The new vault username, make it empty string to delete it
The new vault username
-p, --password [<PASSWORD>]
The new password, make it empty string to delete it
If there is no value for it you will prompt it
The new password, if there is no value for it you will prompt it
-s, --service <SERVICE>
The new vault service, make it empty string to delete it
The new vault service
-o, --note <NOTE>
The new vault note
-t, --totp-secret [<TOTP_SECRET>]
The TOTP secret, make it empty string to delete it
If there is no value you will prompt it
The TOTP secret, if there is no value you will prompt it
-c, --custom <KEY=VALUE>
The custom field, make it empty string to delete it
The custom field, make its value empty 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`)
If the custom field not exist will created it, if it's will update it
-f, --force
Force edit, will not return error if there is a problem with the args.
@ -44,6 +40,9 @@ Options:
-h, --help
Print help (see a summary with '-h')
-V, --version
Print version
```
To edit a vault you need to provide the index or the name of the vault. If you
@ -56,10 +55,6 @@ custom fields.
For secrets like the password and TOTP secret, you can provide them as arguments
or you will be prompted for them.
## Field removal
If you want to remove a field from the vault, you can provide an empty value for
it, e.g. `-o ""`.
## Custom fields
If you want to add a custom field to the vault, you can use the `-c, --custom`
option, and provide the key-value pair. If you want to delete a custom field,

View file

@ -14,6 +14,7 @@ Options:
-n, --numbers With numbers (0-9)
-s, --symbols With symbols (!,# ...)
-h, --help Print help
-V, --version Print version
```
Generate a password with the specified length, by default the length is `18`,

View file

@ -19,6 +19,9 @@ Arguments:
Options:
-h, --help
Print help (see a summary with '-h')
-V, --version
Print version
```
Get a single field from a vault, if the field is not provided, the whole vault

View file

@ -24,6 +24,9 @@ Options:
-h, --help
Print help (see a summary with '-h')
-V, --version
Print version
```
## Export usage

View file

@ -10,6 +10,7 @@ Options:
-r, --regex Enable regex when use `--filter` option
--json Returns the output as `json` list of vaults
-h, --help Print help
-V, --version Print version
```
Lprs `list` command is used to list all vaults in the vaults file, you can also
@ -20,5 +21,4 @@ work with it with `jq`).
### Examples
<script src="https://asciinema.org/a/eEVkDi0NroBjKNILg7KW3hSKY.js" id="asciicast-eEVkDi0NroBjKNILg7KW3hSKY" async="true" data-cols=48 data-rows=10></script>

View file

@ -3,17 +3,18 @@
## Usage
```
Usage: lprs remove [OPTIONS] [INDEX-or-NAME]...
Usage: lprs remove [OPTIONS] <INDEX-or-NAME>
Arguments:
[INDEX-or-NAME]... The vaults to remove, index or name
<INDEX-or-NAME> The vault to remove, index or name
Options:
-f, --force Force remove, will not return error if there is no vault with the given index or name
-h, --help Print help
-f, --force Force remove, will not return error if there is no vault with the given index or name
-h, --help Print help
-V, --version Print version
```
To remove a vaults you need to provide the index or the name of each vault. If you
To remove a vault you need to provide the index or the name of the vault. If you
provide the index, the vault will be removed by its index, if you provide the
name, the vault will be removed the first vault with the given name.
@ -22,17 +23,17 @@ unless you provide the `--force` option, in which case the command will not
return an error if there is no vault with the given index or name.
## Examples
Remove a vaults by its index:
Remove a vault by its index:
```sh
lprs remove 1 10 14
lprs remove 1
```
Remove a vault by its name:
```sh
lprs remove my-vault 'another vault' "third vault"
lprs remove my-vault
```
Force remove a vault by its index (will not return an error if there is no vault with the given index):
Force remove a vault by its index:
```sh
lprs remove 234 --force
```

View file

@ -14,48 +14,17 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://gnu.org/licenses/gpl-3.0.html>.
use std::{fmt::Display, str::FromStr};
use either::Either::{self, Left, Right};
use crate::{LprsError, LprsResult};
/// Parse the key & value arguments.
///
/// ## Errors
/// - If the argument value syntax not `key=value`
pub fn kv_parser(value: &str) -> LprsResult<(String, Option<String>)> {
pub fn kv_parser(value: &str) -> LprsResult<(String, String)> {
if let Some((key, value)) = value.split_once('=') {
Ok((key.trim().to_owned(), Some(value.trim().to_owned())))
} else if value.trim().is_empty() {
Err(LprsError::ArgParse(
"Invalid key, the syntax is `KEY(=VALUE)?`".to_owned(),
))
Ok((key.trim().to_owned(), value.trim().to_owned()))
} else {
Ok((value.trim().to_owned(), None))
Err(LprsError::ArgParse(
"There is no value, the syntax is `KEY=VALUE`".to_owned(),
))
}
}
/// Parse `Either` type arguments.
///
/// ## Errors
/// - If the argument value can't be parsed to `L` or `R`
pub fn either_parser<L, R>(value: &str) -> LprsResult<Either<L, R>>
where
L: FromStr,
R: FromStr,
<L as FromStr>::Err: Display,
<R as FromStr>::Err: Display,
{
value
.trim()
.parse::<L>()
.map_err(|err| LprsError::ArgParse(err.to_string()))
.map(Left)
.or_else(|_| {
value
.parse::<R>()
.map_err(|err| LprsError::ArgParse(err.to_string()))
.map(Right)
})
}

View file

@ -39,11 +39,9 @@ pub struct Add {
#[allow(clippy::option_option)]
totp_secret: Option<Option<String>>,
/// Add a custom field to the vault
///
/// If there is no value, you will enter it through a prompt
#[arg(name = "KEY(=VALUE)?", short = 'c', long = "custom")]
#[arg(name = "KEY=VALUE", short = 'c', long = "custom")]
#[arg(value_parser = clap_parsers::kv_parser)]
custom_fields: Vec<(String, Option<String>)>,
custom_fields: Vec<(String, String)>,
/// Force add, will not return error if there is a problem with the args.
///
/// For example, duplication in the custom fields and try to adding empty
@ -75,9 +73,7 @@ impl LprsCommand for Add {
self.vault_info.name = self.vault_info.name.trim().to_string();
self.vault_info.password = utils::user_secret(self.password, "Vault password:", false)?;
self.vault_info.custom_fields = utils::prompt_custom(self.custom_fields)?
.into_iter()
.collect();
self.vault_info.custom_fields = self.custom_fields.into_iter().collect();
vault_manager.add_vault(self.vault_info);
vault_manager.try_export()?;
}
@ -96,29 +92,6 @@ impl LprsCommand for Add {
)));
}
}
if self
.password
.as_ref()
.is_some_and(|p| p.as_ref().is_some_and(String::is_empty))
|| self.vault_info.name.is_empty()
|| self
.vault_info
.username
.as_ref()
.is_some_and(String::is_empty)
|| self
.vault_info
.service
.as_ref()
.is_some_and(String::is_empty)
|| self.vault_info.note.as_ref().is_some_and(String::is_empty)
|| self
.custom_fields
.iter()
.any(|(k, v)| k.is_empty() || v.as_ref().is_some_and(String::is_empty))
{
return Err(LprsError::EmptyValue);
}
if self
.custom_fields
.iter()

View file

@ -14,13 +14,10 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://gnu.org/licenses/gpl-3.0.html>.
use std::num::NonZeroUsize;
use clap::Args;
use either::Either;
use crate::{
clap_parsers::{either_parser, kv_parser},
clap_parsers,
utils,
vault::{cipher, Vaults},
LprsCommand,
@ -32,40 +29,35 @@ use crate::{
/// 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::<NonZeroUsize, String>)]
location: Either<NonZeroUsize, String>,
#[arg(name = "INDEX-or-NAME")]
location: String,
#[arg(short, long)]
/// The new vault name
name: Option<String>,
#[arg(short, long)]
/// The new vault username, make it empty string to delete it
/// The new vault username
username: Option<String>,
#[arg(short, long)]
/// The new password, make it empty string to delete it
///
/// If there is no value for it you will prompt it
/// The new password, if there is no value for it you will prompt it
#[allow(clippy::option_option)]
password: Option<Option<String>>,
#[arg(short, long)]
/// The new vault service, make it empty string to delete it
/// The new vault service
service: Option<String>,
#[arg(short = 'o', long)]
/// The new vault note
note: Option<String>,
/// The TOTP secret, make it empty string to delete it
///
/// If there is no value you will prompt it
/// The TOTP secret, if there is no value you will prompt it
#[arg(short, long)]
#[allow(clippy::option_option)]
totp_secret: Option<Option<String>>,
/// The custom field, make it empty string to delete it
/// The custom field, make its value empty 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`)
/// If the custom field not exist will created it, if it's will update it
#[arg(name = "KEY=VALUE", short = 'c', long = "custom")]
#[arg(value_parser = kv_parser)]
custom_fields: Vec<(String, Option<String>)>,
#[arg(value_parser = clap_parsers::kv_parser)]
custom_fields: Vec<(String, String)>,
/// 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
@ -75,62 +67,40 @@ pub struct Edit {
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(());
let vault =
match utils::vault_by_index_or_name(self.location.trim(), &mut vault_manager.vaults) {
Ok((_, v)) => v,
Err(err) => {
if self.force {
return Ok(());
}
return Err(err);
}
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 self.password.is_some() {
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);
}
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);
}
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);
}
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);
}
vault.note = Some(new_note);
}
utils::apply_custom_fields(
&mut vault.custom_fields,
utils::prompt_custom(self.custom_fields)?,
);
utils::apply_custom_fields(&mut vault.custom_fields, self.custom_fields);
vault_manager.try_export()
}

View file

@ -14,13 +14,11 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://gnu.org/licenses/gpl-3.0.html>.
use std::{num::NonZeroUsize, str::FromStr};
use std::str::FromStr;
use clap::Args;
use either::Either;
use crate::{
clap_parsers::either_parser,
utils,
vault::{cipher, Vault, Vaults},
LprsCommand,
@ -96,9 +94,8 @@ impl VaultGetField {
/// Command to get a entire vault or single field from it
pub struct Get {
/// Whether the index of the vault or its name
#[arg(name = "INDEX-or-NAME", value_parser = either_parser::<NonZeroUsize, String>)]
location: Either<NonZeroUsize, String>,
#[arg(value_name = "INDEX-or-NAME")]
location: String,
/// A Specific field to get.
///
/// Can be [name, username, password, service, note, totp_secret, totp_code,
@ -106,13 +103,13 @@ pub struct Get {
///
/// where the string means a custom field
#[arg(value_parser = VaultGetField::from_str)]
field: Option<VaultGetField>,
field: Option<VaultGetField>,
}
impl LprsCommand for Get {
fn run(self, mut vault_manager: Vaults) -> LprsResult<()> {
let (index, vault) =
utils::vault_by_index_or_name(&self.location, &mut vault_manager.vaults)?;
utils::vault_by_index_or_name(self.location.trim(), &mut vault_manager.vaults)?;
if let Some(field) = self.field {
if field == VaultGetField::Index {

View file

@ -47,30 +47,23 @@ impl LprsCommand for List {
));
}
let pattern = if self.regex {
self.filter
.expect("Is required if the `regex` option is `true`")
let pattern = if self.regex || self.filter.is_none() {
self.filter.unwrap_or_else(|| ".".to_owned())
} else {
format!(
".*{}.*",
regex::escape(self.filter.as_deref().unwrap_or_default())
regex::escape(self.filter.as_deref().unwrap_or(""))
)
};
log::debug!("Listing vaults filtered by: {pattern}");
let re = regex::Regex::new(&pattern.to_lowercase())?;
let re = regex::Regex::new(&pattern)?;
let vaults_list = vault_manager.vaults.iter().enumerate().filter(|(_, v)| {
re.is_match(&v.name.to_lowercase())
|| v.username
.as_deref()
.is_some_and(|u| re.is_match(&u.to_lowercase()))
|| v.service
.as_deref()
.is_some_and(|s| re.is_match(&s.to_lowercase()))
|| v.note
.as_deref()
.is_some_and(|n| re.is_match(&n.to_lowercase()))
re.is_match(&v.name)
|| v.username.as_deref().is_some_and(|u| re.is_match(u))
|| v.service.as_deref().is_some_and(|s| re.is_match(s))
|| v.note.as_deref().is_some_and(|n| re.is_match(n))
});
if self.json {

View file

@ -122,16 +122,11 @@ impl Cli {
log::info!("Using the given vaults file");
if let Some(parent) = path.parent() {
if parent.to_str() != Some("") && !parent.exists() {
log::info!(
"Creating the parent vaults file directory: {}",
parent.display()
);
log::info!("Creating the parent vaults file directory");
fs::create_dir_all(parent)?;
}
}
if !path.exists() {
fs::File::create(&path)?;
}
fs::File::create(&path)?;
path
} else {
log::info!("Using the default vaults file");

View file

@ -14,25 +14,16 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://gnu.org/licenses/gpl-3.0.html>.
use std::num::NonZeroUsize;
use clap::Args;
use either::Either;
use crate::{
clap_parsers::either_parser,
utils,
vault::{Vault, Vaults},
LprsCommand,
LprsResult,
};
use crate::{utils, vault::Vaults, LprsCommand, LprsResult};
#[derive(Debug, Args)]
/// Remove command, used to remove a vault from the vaults file
pub struct Remove {
/// The vaults to remove, index or name
#[arg(name = "INDEX-or-NAME", value_parser = either_parser::<NonZeroUsize, String>)]
locations: Vec<Either<NonZeroUsize, String>>,
/// The vault to remove, index or name
#[arg(name = "INDEX-or-NAME")]
location: String,
/// Force remove, will not return error if there is no vault with the given
/// index or name
@ -42,26 +33,17 @@ pub struct Remove {
impl LprsCommand for Remove {
fn run(self, mut vault_manager: Vaults) -> LprsResult<()> {
let indexes = self
.locations
.iter()
.map(|location| {
utils::vault_by_index_or_name(location, &mut vault_manager.vaults)
.map(|(_, v)| v.clone())
})
.collect::<LprsResult<Vec<Vault>>>();
match indexes {
Ok(indexes) => vault_manager.vaults.retain(|v| !indexes.contains(v)),
Err(err) => {
if self.force {
log::warn!("Ignoring error: {err}");
return Ok(());
let index =
match utils::vault_by_index_or_name(self.location.trim(), &mut vault_manager.vaults) {
Ok((idx, _)) => idx,
Err(err) => {
if self.force {
return Ok(());
}
return Err(err);
}
return Err(err);
}
}
};
vault_manager.vaults.remove(index);
vault_manager.try_export()
}
}

View file

@ -44,8 +44,6 @@ pub enum Error {
custom fields {0}"
)]
ReservedPrefix(&'static str),
#[error("Invalid Field Value: Field value cannot be empty")]
EmptyValue,
#[error("Base32 Error: {0}")]
Base32(String),
#[error("{0}")]

View file

@ -15,15 +15,9 @@
// along with this program. If not, see <https://gnu.org/licenses/gpl-3.0.html>.
use std::collections::BTreeMap;
use std::num::NonZeroUsize;
use std::{fs, path::PathBuf};
use either::Either;
use inquire::{
validator::{StringValidator, Validation},
Password,
PasswordDisplayMode,
};
use inquire::{validator::Validation, Password, PasswordDisplayMode};
use passwords::{analyzer, scorer};
#[cfg(feature = "update-notify")]
use reqwest::blocking::Client as BlockingClient;
@ -51,26 +45,6 @@ pub fn local_project_file(filename: &str) -> LprsResult<PathBuf> {
Ok(local_dir.join(filename))
}
/// Ask the user for a secret in the stdin
///
/// ## Errors
/// - If can't read the user input
fn secret_prompt(
prompt_message: &str,
confirmation: bool,
validators: Option<Vec<Box<dyn StringValidator>>>,
) -> LprsResult<String> {
Password {
validators: validators.unwrap_or_default(),
enable_confirmation: confirmation,
..Password::new(prompt_message)
.with_formatter(&|p| "*".repeat(p.chars().count()))
.with_display_mode(PasswordDisplayMode::Masked)
}
.prompt()
.map_err(LprsError::Inquire)
}
/// Returns the user secret if any
///
/// - If the `secret` is `None` will return `None`
@ -90,7 +64,15 @@ pub fn user_secret(
Some(Some(p)) => Some(p),
Some(None) => {
log::debug!("User didn't provide a secret, prompting it");
Some(secret_prompt(prompt_message, confirmation, None)?)
Some(
Password {
enable_confirmation: confirmation,
..Password::new(prompt_message)
.with_formatter(&|p| "*".repeat(p.chars().count()))
.with_display_mode(PasswordDisplayMode::Masked)
}
.prompt()?,
)
}
})
}
@ -135,12 +117,21 @@ pub fn password_validator(password: &str) -> Result<Validation, inquire::CustomU
///
/// 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]> {
secret_prompt(
"Master Password:",
is_new_vaults_file,
is_new_vaults_file.then(|| vec![Box::new(password_validator) as Box<dyn StringValidator>]),
)
inquire::Password {
message: "Master Password:",
enable_confirmation: is_new_vaults_file,
validators: if is_new_vaults_file {
vec![Box::new(password_validator)]
} else {
vec![]
},
..inquire::Password::new("")
}
.with_formatter(&|p| "*".repeat(p.chars().count()))
.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`
@ -190,7 +181,7 @@ pub fn lprs_version() -> LprsResult<Option<String>> {
}
/// Returns the duplicated field from the custom field (unprocessed fields)
pub fn get_duplicated_field(fields: &[(String, Option<String>)]) -> Option<&str> {
pub fn get_duplicated_field(fields: &[(String, String)]) -> Option<&str> {
fields.iter().find_map(|(key, _)| {
if fields.iter().filter(|(k, _)| key == k).count() > 1 {
return Some(key.as_str());
@ -219,56 +210,35 @@ pub fn apply_custom_fields(
}
}
/// Make sure all custom field values are there, if not, ask the user for it
///
/// ## Errors
/// - If can't read the user input
pub fn prompt_custom(
custom_fields: Vec<(String, Option<String>)>,
) -> LprsResult<Vec<(String, String)>> {
let mut new_fields = Vec::new();
for (key, value) in custom_fields {
if let Some(value) = value {
new_fields.push((key, value));
} else {
let value = secret_prompt(&format!("Value of `{key}`:"), false, None)?;
new_fields.push((key, value));
}
}
Ok(new_fields)
}
/// Returns the vault with its index by either its index or name
/// Returns the vault with its index by its index or name
///
/// ## Errors
/// - If there is no vault with the given index or name
pub fn vault_by_index_or_name<'v>(
location: &Either<NonZeroUsize, String>,
vaults: &'v mut [Vault],
) -> LprsResult<(usize, &'v mut Vault)> {
let idx = location
.as_ref()
.map_right(|name| {
vaults
.iter()
.enumerate()
.find_map(|(idx, v)| (&v.name == name).then_some(idx))
.ok_or_else(|| {
LprsError::Other(format!("There is no vault with the given name `{name}`"))
})
})
.map_left(|idx| LprsResult::Ok(idx.get() - 1))
.into_inner()?;
pub fn vault_by_index_or_name<'a>(
index_or_name: &str,
vaults: &'a mut [Vault],
) -> LprsResult<(usize, &'a mut Vault)> {
let parsed_index = index_or_name.parse::<usize>();
Ok((
idx,
vaults.get_mut(idx).ok_or_else(|| {
LprsError::Other(format!(
"There is no vault with the given index `{}`",
idx + 1
))
})?,
))
let Some((index, vault)) = (if let Ok(index) = parsed_index {
index
.checked_sub(1)
.and_then(|zeroindex| vaults.get_mut(zeroindex).map(|v| (index, v)))
} else {
vaults
.iter_mut()
.enumerate()
.find(|(_, v)| v.name == index_or_name)
}) else {
return Err(LprsError::Other(format!(
"There is no vault with the given {} `{}`",
if parsed_index.is_ok() {
"index"
} else {
"name"
},
index_or_name,
)));
};
Ok((index, vault))
}

View file

@ -40,22 +40,37 @@ pub enum TotpHash {
}
/// Create the TOTP code of the current time with its remainig time in seconds
/// Create the TOTP code of the current time
///
/// ## Errors
/// - If the given `secret_base32` are invalid base32
/// - If the given `secret_base32` are vaild base32
pub fn totp_now(secret_base32: &str, hash_function: &TotpHash) -> LprsResult<(String, u8)> {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("SystemTime before UNIX EPOCH!")
.as_secs();
let remaining = 30 - (now % 30) as u8;
let secret = base32_decode(secret_base32)?;
let totp_code = match hash_function {
TotpHash::Sha1 => totp_lite::totp_custom::<totp_lite::Sha1>(30, 6, &secret, now),
TotpHash::Sha256 => totp_lite::totp_custom::<totp_lite::Sha256>(30, 6, &secret, now),
TotpHash::Sha512 => totp_lite::totp_custom::<totp_lite::Sha512>(30, 6, &secret, now),
};
Ok((totp_code, 30 - (now % 30) as u8))
Ok(match hash_function {
TotpHash::Sha1 => {
(
totp_lite::totp_custom::<totp_lite::Sha1>(30, 6, &secret, now),
remaining,
)
}
TotpHash::Sha256 => {
(
totp_lite::totp_custom::<totp_lite::Sha256>(30, 6, &secret, now),
remaining,
)
}
TotpHash::Sha512 => {
(
totp_lite::totp_custom::<totp_lite::Sha512>(30, 6, &secret, now),
remaining,
)
}
})
}
/// Base32 decode

View file

@ -44,7 +44,7 @@ pub enum Format {
}
/// The vault struct
#[derive(Clone, Debug, Deserialize, Serialize, Parser, Eq, PartialEq)]
#[derive(Clone, Debug, Deserialize, Serialize, Parser)]
pub struct Vault {
/// The name of the vault
pub name: String,