Compare commits
No commits in common. "master" and "v2.0.0-rc.1" have entirely different histories.
master
...
v2.0.0-rc.
25 changed files with 166 additions and 326 deletions
|
@ -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
|
||||
|
|
|
@ -12,5 +12,5 @@ For the location, please provide a link to the code or the documentation.
|
|||
|
||||
| Location | Suggestion |
|
||||
|----------|------------|
|
||||
| location | Suggestion |
|
||||
| Code-link| Suggestion |
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -21,16 +21,13 @@ 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))
|
||||
|
@ -40,13 +37,11 @@ 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
9
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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"]
|
||||
|
|
35
README.md
35
README.md
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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`,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -24,6 +24,9 @@ Options:
|
|||
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
-V, --version
|
||||
Print version
|
||||
```
|
||||
|
||||
## Export usage
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
```
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}")]
|
||||
|
|
134
src/utils.rs
134
src/utils.rs
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue