Compare commits

..

24 commits

Author SHA1 Message Date
2cfddbbcbd
chore: Update README.md
All checks were successful
Write changelog / write-changelog (push) Successful in 24s
Rust CI / Rust CI (push) Successful in 2m54s
Signed-off-by: Awiteb <a@4rs.nl>
2024-08-21 17:09:35 +00:00
e8a18d4f53
chore: Do not validate password if file exists
Signed-off-by: Awiteb <a@4rs.nl>
2024-08-21 20:07:33 +03:00
75625f53b4
chore(typo): Fix typo Clening -> Cleaning
Signed-off-by: Awiteb <a@4rs.nl>
2024-08-21 16:32:37 +00:00
ae7e310a49
chore: Update the version in the lock file
Signed-off-by: Awiteb <a@4rs.nl>
2024-08-21 16:30:40 +00:00
00475f5240
chore: Add the docs as homepage
Signed-off-by: Awiteb <a@4rs.nl>
2024-08-21 16:30:24 +00:00
e8ac0ffaae
docs: Update issues template
Signed-off-by: Awiteb <a@4rs.nl>
2024-08-21 16:30:00 +00:00
656cc9f984
chore(ci): Update rust action username
Signed-off-by: Awiteb <a@4rs.nl>
2024-08-21 16:29:41 +00:00
f364f5bb4b
docs: Update remove command docs
All checks were successful
Write changelog / write-changelog (push) Successful in 3s
Rust CI / Rust CI (push) Successful in 1m56s
Signed-off-by: Awiteb <a@4rs.nl>
2024-08-20 17:46:12 +00:00
78513b3694
chore: Bump the version to v2.0.0-rc.2
All checks were successful
Write changelog / write-changelog (push) Successful in 3s
Rust CI / Rust CI (push) Successful in 3m8s
CD / build-assets (aarch64-unknown-linux-gnu) (push) Successful in 5m37s
CD / build-assets (aarch64-unknown-linux-musl) (push) Successful in 5m33s
CD / build-assets (x86_64-pc-windows-gnu) (push) Successful in 5m3s
CD / build-assets (x86_64-unknown-linux-gnu) (push) Successful in 4m13s
CD / build-assets (x86_64-unknown-linux-musl) (push) Successful in 4m18s
CD / release (push) Successful in 15s
Signed-off-by: Awiteb <a@4rs.nl>
2024-08-20 17:06:50 +00:00
forgejo-actions
6809a0f5bf chore(changelog): Update changelog 2024-08-20 17:05:04 +00:00
6293eadafb feat: Support removing multiple vaults in single command (#66)
All checks were successful
Write changelog / write-changelog (push) Successful in 4s
Rust CI / Rust CI (push) Successful in 1m56s
Reviewed-on: https://git.4rs.nl///awiteb/lprs/pulls/66
Co-authored-by: Awiteb <a@4rs.nl>
Co-committed-by: Awiteb <a@4rs.nl>
2024-08-20 19:04:53 +02:00
dbaa518855
chore: Impl Eq and PartialEq for Vault
Some checks failed
Write changelog / write-changelog (push) Successful in 3s
Rust CI / Rust CI (push) Failing after 1m56s
Signed-off-by: Awiteb <a@4rs.nl>
2024-08-20 16:59:22 +00:00
e033971665
chore: Add 1 to the index when print it
Signed-off-by: Awiteb <a@4rs.nl>
2024-08-20 16:58:07 +00:00
aee33f819c
chore: Pass refrence of either to utils::vault_by_index_or_name function
Signed-off-by: Awiteb <a@4rs.nl>
2024-08-20 17:16:59 +03:00
ad3dc025a9
chore: Improve prompt functions
Signed-off-by: Awiteb <a@4rs.nl>
2024-08-20 14:05:44 +00:00
forgejo-actions
9b39da877f chore(changelog): Update changelog 2024-08-20 11:45:04 +00:00
1c90a82544 refactor: Use Either<usize, String> type instade of String for index or name (#65)
Some checks failed
Write changelog / write-changelog (push) Successful in 4s
Rust CI / Rust CI (push) Failing after 31s
Reviewed-on: https://git.4rs.nl///awiteb/lprs/pulls/65
Co-authored-by: Awiteb <a@4rs.nl>
Co-committed-by: Awiteb <a@4rs.nl>
2024-08-20 13:44:50 +02:00
forgejo-actions
5f38d23c28 chore(changelog): Update changelog 2024-08-17 15:23:46 +00:00
5e4fb0ea7c feat: Support entering custom keys value via STDIN (#64)
All checks were successful
Write changelog / write-changelog (push) Successful in 5s
Rust CI / Rust CI (push) Successful in 3m7s
Reviewed-on: https://git.4rs.nl///awiteb/lprs/pulls/64
Co-authored-by: Awiteb <a@4rs.nl>
Co-committed-by: Awiteb <a@4rs.nl>
2024-08-17 17:23:33 +02:00
forgejo-actions
04614aff57 chore(changelog): Update changelog 2024-07-24 08:09:01 +00:00
6f5ca5f452
fix: Reject empty string field value
All checks were successful
Rust CI / Rust CI (pull_request) Successful in 2m3s
Write changelog / write-changelog (push) Successful in 4s
Rust CI / Rust CI (push) Successful in 1m58s
Signed-off-by: Awiteb <a@4rs.nl>
2024-07-24 11:03:42 +03:00
forgejo-actions
6e5e3c093c chore(changelog): Update changelog 2024-07-24 07:45:41 +00:00
3567865c2d
docs: Update edit command docs
All checks were successful
Rust CI / Rust CI (pull_request) Successful in 2m0s
Write changelog / write-changelog (push) Successful in 4s
Rust CI / Rust CI (push) Successful in 2m28s
Signed-off-by: Awiteb <a@4rs.nl>
2024-07-24 10:36:17 +03:00
5d30b8b021
feat: Remove vault field if its value is empty string
Signed-off-by: Awiteb <a@4rs.nl>
2024-07-24 10:36:02 +03:00
25 changed files with 326 additions and 166 deletions

View file

@ -9,16 +9,12 @@ assignees: ''
## Checks
* [ ] I added a descriptive title to this issue
* [ ] 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
* [ ] I have read [the documentation](https://lprs.4rs.nl) and still think this it's 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 |
|----------|------------|
| Code-link| Suggestion |
| location | Suggestion |

View file

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

View file

@ -21,13 +21,16 @@ 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))
@ -37,11 +40,13 @@ and then re-invoke them (after this commit)
- **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,6 +349,12 @@ 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"
@ -698,7 +704,7 @@ checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "lprs"
version = "2.0.0-rc.0"
version = "2.0.0-rc.2"
dependencies = [
"aes",
"base32",
@ -708,6 +714,7 @@ dependencies = [
"clap",
"clap_complete",
"directories",
"either",
"inquire",
"log",
"passwords",

View file

@ -1,11 +1,12 @@
[package]
name = "lprs"
version = "2.0.0-rc.0"
version = "2.0.0-rc.2"
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"]
@ -32,6 +33,7 @@ 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,6 +14,7 @@ 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.
@ -36,6 +37,7 @@ 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
@ -44,9 +46,12 @@ cargo install 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
@ -54,7 +59,8 @@ 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
@ -71,7 +77,7 @@ Commands:
get Get a entire vault or single field from it
export Export the vaults
import Import vaults
change-master-password Change the master password
change-master-password Change master password, reencrypt the vaults with new password
completion Generate shell completion
help Print this message or the help of the given subcommand(s)
@ -79,26 +85,37 @@ 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
-h, --help Print help (see more with '--help')
-V, --version Print version
```
## Documentation
You can find the full documentation for Lprs on the official website at https://lprs.4rs.nl.
You can find the full documentation for Lprs here <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

@ -8,7 +8,7 @@
- [Editing a vault](commands/edit.md)
- [Getting a vault](commands/get.md)
- [Listing all vaults](commands/list.md)
- [Clening the vaults](commands/clean.md)
- [Cleaning 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)
- [Clening the vaults](commands/clean.md)
- [Cleaning 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,9 +35,11 @@ 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.
@ -45,9 +47,6 @@ 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,13 +1,9 @@
# Clening the vaults
# Cleaning 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,24 +14,28 @@ Options:
The new vault name
-u, --username <USERNAME>
The new vault username
The new vault username, make it empty string to delete it
-p, --password [<PASSWORD>]
The new password, if there is no value for it you will prompt it
The new password, make it empty string to delete it
If there is no value for it you will prompt it
-s, --service <SERVICE>
The new vault service
The new vault service, make it empty string to delete it
-o, --note <NOTE>
The new vault note
-t, --totp-secret [<TOTP_SECRET>]
The TOTP secret, if there is no value you will prompt it
The TOTP secret, make it empty string to delete it
If there is no value you will prompt it
-c, --custom <KEY=VALUE>
The custom field, make its value empty to delete it
The custom field, make it empty string to delete it
If the custom field not exist will created it, if it's will update it
If 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`)
-f, --force
Force edit, will not return error if there is a problem with the args.
@ -40,9 +44,6 @@ 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
@ -55,6 +56,10 @@ 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,7 +14,6 @@ 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,9 +19,6 @@ 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,9 +24,6 @@ Options:
-h, --help
Print help (see a summary with '-h')
-V, --version
Print version
```
## Export usage

View file

@ -10,7 +10,6 @@ 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
@ -21,4 +20,5 @@ 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,18 +3,17 @@
## Usage
```
Usage: lprs remove [OPTIONS] <INDEX-or-NAME>
Usage: lprs remove [OPTIONS] [INDEX-or-NAME]...
Arguments:
<INDEX-or-NAME> The vault to remove, index or name
[INDEX-or-NAME]... The vaults 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
-V, --version Print version
-f, --force Force remove, will not return error if there is no vault with the given index or name
-h, --help Print help
```
To remove a vault you need to provide the index or the name of the vault. If you
To remove a vaults you need to provide the index or the name of each 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.
@ -23,17 +22,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 vault by its index:
Remove a vaults by its index:
```sh
lprs remove 1
lprs remove 1 10 14
```
Remove a vault by its name:
```sh
lprs remove my-vault
lprs remove my-vault 'another vault' "third vault"
```
Force remove a vault by its index:
Force remove a vault by its index (will not return an error if there is no vault with the given index):
```sh
lprs remove 234 --force
```

View file

@ -14,17 +14,48 @@
// 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, String)> {
pub fn kv_parser(value: &str) -> LprsResult<(String, Option<String>)> {
if let Some((key, value)) = value.split_once('=') {
Ok((key.trim().to_owned(), value.trim().to_owned()))
} else {
Ok((key.trim().to_owned(), Some(value.trim().to_owned())))
} else if value.trim().is_empty() {
Err(LprsError::ArgParse(
"There is no value, the syntax is `KEY=VALUE`".to_owned(),
"Invalid key, the syntax is `KEY(=VALUE)?`".to_owned(),
))
} else {
Ok((value.trim().to_owned(), None))
}
}
/// 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,9 +39,11 @@ pub struct Add {
#[allow(clippy::option_option)]
totp_secret: Option<Option<String>>,
/// Add a custom field to the vault
#[arg(name = "KEY=VALUE", short = 'c', long = "custom")]
///
/// If there is no value, you will enter it through a prompt
#[arg(name = "KEY(=VALUE)?", short = 'c', long = "custom")]
#[arg(value_parser = clap_parsers::kv_parser)]
custom_fields: Vec<(String, String)>,
custom_fields: Vec<(String, Option<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
@ -73,7 +75,9 @@ 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 = self.custom_fields.into_iter().collect();
self.vault_info.custom_fields = utils::prompt_custom(self.custom_fields)?
.into_iter()
.collect();
vault_manager.add_vault(self.vault_info);
vault_manager.try_export()?;
}
@ -92,6 +96,29 @@ 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,10 +14,13 @@
// 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,
clap_parsers::{either_parser, kv_parser},
utils,
vault::{cipher, Vaults},
LprsCommand,
@ -29,35 +32,40 @@ 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")]
location: String,
#[arg(name = "INDEX-or-NAME", value_parser = either_parser::<NonZeroUsize, String>)]
location: Either<NonZeroUsize, String>,
#[arg(short, long)]
/// The new vault name
name: Option<String>,
#[arg(short, long)]
/// The new vault username
/// The new vault username, make it empty string to delete it
username: Option<String>,
#[arg(short, long)]
/// The new password, if there is no value for it you will prompt it
/// The new password, make it empty string to delete it
///
/// If there is no value for it you will prompt it
#[allow(clippy::option_option)]
password: Option<Option<String>>,
#[arg(short, long)]
/// The new vault service
/// The new vault service, make it empty string to delete it
service: Option<String>,
#[arg(short = 'o', long)]
/// The new vault note
note: Option<String>,
/// The TOTP secret, if there is no value you will prompt it
/// The TOTP secret, make it empty string to delete it
///
/// If there is no value you will prompt it
#[arg(short, long)]
#[allow(clippy::option_option)]
totp_secret: Option<Option<String>>,
/// The custom field, make its value empty to delete it
/// The custom field, make it empty string to delete it
///
/// If the custom field not exist will created it, if it's will update it
/// If the custom field not exist will created it, if it's will update it,
/// if there is no value, you will enter it through a prompt (e.g `-c key`)
#[arg(name = "KEY=VALUE", short = 'c', long = "custom")]
#[arg(value_parser = clap_parsers::kv_parser)]
custom_fields: Vec<(String, String)>,
#[arg(value_parser = kv_parser)]
custom_fields: Vec<(String, Option<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
@ -67,40 +75,62 @@ 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.trim(), &mut vault_manager.vaults) {
Ok((_, v)) => v,
Err(err) => {
if self.force {
return Ok(());
}
return Err(err);
let vault = match utils::vault_by_index_or_name(&self.location, &mut vault_manager.vaults) {
Ok((_, v)) => v,
Err(err) => {
if self.force {
return Ok(());
}
};
return Err(err);
}
};
log::info!("Applying the new values to the vault");
if let Some(new_name) = self.name {
vault.name = new_name;
}
if self.password.is_some() {
vault.password = utils::user_secret(self.password, "New vault password:", false)?;
if let Some(ref new_password) = self.password {
if new_password.as_deref().is_some_and(|s| s.is_empty()) {
vault.password = None;
} else {
vault.password = utils::user_secret(self.password, "New vault password:", false)?;
}
}
if let Some(totp_secret) = utils::user_secret(self.totp_secret, "TOTP Secret:", false)? {
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 totp_secret.is_empty() {
vault.totp_secret = None;
} else {
cipher::base32_decode(&totp_secret).map_err(|_| {
LprsError::Base32("Invalid TOTP secret, must be valid base32 string".to_owned())
})?;
vault.totp_secret = Some(totp_secret);
}
}
if let Some(new_username) = self.username {
vault.username = Some(new_username);
if new_username.is_empty() {
vault.username = None;
} else {
vault.username = Some(new_username);
}
}
if let Some(new_service) = self.service {
vault.service = Some(new_service);
if new_service.is_empty() {
vault.service = None;
} else {
vault.service = Some(new_service);
}
}
if let Some(new_note) = self.note {
vault.note = Some(new_note);
if new_note.is_empty() {
vault.note = None;
} else {
vault.note = Some(new_note);
}
}
utils::apply_custom_fields(&mut vault.custom_fields, self.custom_fields);
utils::apply_custom_fields(
&mut vault.custom_fields,
utils::prompt_custom(self.custom_fields)?,
);
vault_manager.try_export()
}

View file

@ -14,11 +14,13 @@
// 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::str::FromStr;
use std::{num::NonZeroUsize, str::FromStr};
use clap::Args;
use either::Either;
use crate::{
clap_parsers::either_parser,
utils,
vault::{cipher, Vault, Vaults},
LprsCommand,
@ -94,8 +96,9 @@ 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(value_name = "INDEX-or-NAME")]
location: String,
#[arg(name = "INDEX-or-NAME", value_parser = either_parser::<NonZeroUsize, String>)]
location: Either<NonZeroUsize, String>,
/// A Specific field to get.
///
/// Can be [name, username, password, service, note, totp_secret, totp_code,
@ -103,13 +106,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.trim(), &mut vault_manager.vaults)?;
utils::vault_by_index_or_name(&self.location, &mut vault_manager.vaults)?;
if let Some(field) = self.field {
if field == VaultGetField::Index {

View file

@ -14,16 +14,25 @@
// 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 clap::Args;
use std::num::NonZeroUsize;
use crate::{utils, vault::Vaults, LprsCommand, LprsResult};
use clap::Args;
use either::Either;
use crate::{
clap_parsers::either_parser,
utils,
vault::{Vault, Vaults},
LprsCommand,
LprsResult,
};
#[derive(Debug, Args)]
/// Remove command, used to remove a vault from the vaults file
pub struct Remove {
/// The vault to remove, index or name
#[arg(name = "INDEX-or-NAME")]
location: String,
/// The vaults to remove, index or name
#[arg(name = "INDEX-or-NAME", value_parser = either_parser::<NonZeroUsize, String>)]
locations: Vec<Either<NonZeroUsize, String>>,
/// Force remove, will not return error if there is no vault with the given
/// index or name
@ -33,17 +42,26 @@ pub struct Remove {
impl LprsCommand for Remove {
fn run(self, mut vault_manager: Vaults) -> LprsResult<()> {
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);
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(());
}
};
vault_manager.vaults.remove(index);
return Err(err);
}
}
vault_manager.try_export()
}
}

View file

@ -44,6 +44,8 @@ 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,9 +15,15 @@
// 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 inquire::{validator::Validation, Password, PasswordDisplayMode};
use either::Either;
use inquire::{
validator::{StringValidator, Validation},
Password,
PasswordDisplayMode,
};
use passwords::{analyzer, scorer};
#[cfg(feature = "update-notify")]
use reqwest::blocking::Client as BlockingClient;
@ -45,6 +51,26 @@ 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`
@ -64,15 +90,7 @@ pub fn user_secret(
Some(Some(p)) => Some(p),
Some(None) => {
log::debug!("User didn't provide a secret, prompting it");
Some(
Password {
enable_confirmation: confirmation,
..Password::new(prompt_message)
.with_formatter(&|p| "*".repeat(p.chars().count()))
.with_display_mode(PasswordDisplayMode::Masked)
}
.prompt()?,
)
Some(secret_prompt(prompt_message, confirmation, None)?)
}
})
}
@ -117,21 +135,12 @@ 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]> {
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()
secret_prompt(
"Master Password:",
is_new_vaults_file,
is_new_vaults_file.then(|| vec![Box::new(password_validator) as Box<dyn StringValidator>]),
)
.map(|p| sha2::Sha256::digest(p).into())
.map_err(Into::into)
}
/// Retuns the current lprs version from `crates.io`
@ -181,7 +190,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, String)]) -> Option<&str> {
pub fn get_duplicated_field(fields: &[(String, Option<String>)]) -> Option<&str> {
fields.iter().find_map(|(key, _)| {
if fields.iter().filter(|(k, _)| key == k).count() > 1 {
return Some(key.as_str());
@ -210,35 +219,56 @@ pub fn apply_custom_fields(
}
}
/// Returns the vault with its index by its index or name
/// 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
///
/// ## Errors
/// - If there is no vault with the given index or name
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>();
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()?;
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))
Ok((
idx,
vaults.get_mut(idx).ok_or_else(|| {
LprsError::Other(format!(
"There is no vault with the given index `{}`",
idx + 1
))
})?,
))
}

View file

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