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 ## Checks
* [ ] I added a descriptive title to this issue * [ ] 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 ## Version
<!-- Report for the bug only if it's present in the latest version of Lprs. <!-- 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. --> 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: `...` Lprs version: `...`
## Description ## Description

View file

@ -12,5 +12,5 @@ For the location, please provide a link to the code or the documentation.
| Location | Suggestion | | 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.exe release-dir/$APP_NAME.exe || true
cp target/${{ matrix.target }}/release/$BIN_NAME release-dir/$APP_NAME || true cp target/${{ matrix.target }}/release/$BIN_NAME release-dir/$APP_NAME || true
cd release-dir cd release-dir
test -f $APP_NAME && sha256sum $APP_NAME > $APP_NAME.sha256 || true sha256sum $APP_NAME > $APP_NAME.sha256 || true
test -f $APP_NAME.exe && sha256sum $APP_NAME.exe > $APP_NAME.exe.sha256 || true sha256sum $APP_NAME.exe > $APP_NAME.exe.sha256 || true
- name: Build the asset (update-notify) - name: Build the asset (update-notify)
run: | 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.exe release-dir/$APP_NAME.exe || true
cp target/${{ matrix.target }}/release/$BIN_NAME release-dir/$APP_NAME || true cp target/${{ matrix.target }}/release/$BIN_NAME release-dir/$APP_NAME || true
cd release-dir cd release-dir
test -f $APP_NAME && sha256sum $APP_NAME > $APP_NAME.sha256 || true sha256sum $APP_NAME > $APP_NAME.sha256 || true
test -f $APP_NAME.exe && sha256sum $APP_NAME.exe > $APP_NAME.exe.sha256 || true sha256sum $APP_NAME.exe > $APP_NAME.exe.sha256 || true
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
with: with:

View file

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

View file

@ -12,7 +12,7 @@ jobs:
runs-on: debian runs-on: debian
steps: steps:
- uses: actions/checkout@v4 - 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 - name: Check MSRV
run: cargo +1.74 build run: cargo +1.74 build
- name: Build the source code - 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)) - 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 `--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)) - 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)) - 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 - **BC**: The previous format is not supported after this commit, so
you must export your vaults in bit-warden format (before this commit) 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)) - 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 - **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)) - 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 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 `--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 `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 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 completion generating ([`f022574`](https://git.4rs.nl/awiteb/lprs/commit/f022574631bfb1b6a62f95d3259617f302059781))
- Support custom fields ([`da568ab`](https://git.4rs.nl/awiteb/lprs/commit/da568ab5e9414ef77831066eb9b09621c0fedaee)) - 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 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)) - 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 ### Changed
- Change 'password manager' to 'vault manager' ([`bae0cf1`](https://git.4rs.nl/awiteb/lprs/commit/bae0cf174736d9a1cd61becd20f7d87cf137249c)) - 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)) - 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)) - Add a ecryption state to the vault ([`4def4aa`](https://git.4rs.nl/awiteb/lprs/commit/4def4aadb20cc367d57466dc5e88c3043e468d20))
- **BC**: Moving from password to vault - **BC**: Moving from password to vault
- Move from GitHub to Forgejo ([`6163c3f`](https://git.4rs.nl/awiteb/lprs/commit/6163c3ff26ab81b07490a798f4047a09565ab1ac)) - 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)) - 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)) - Use select for vaults listing ([**#19**](https://git.4rs.nl/awiteb/lprs/issues/19)) ([`83c7296`](https://git.4rs.nl/awiteb/lprs/commit/83c7296bf7bf469423f53b024cb65e608ff6c9d9))
### Fixed ### 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)) - 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)) - 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)) - 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)) - Validate all fields in `add` & `edit` ([`02bf53b`](https://git.4rs.nl/awiteb/lprs/commit/02bf53b2a1fd420bf66ac571531d060499559c29))
### Removed ### 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" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]] [[package]]
name = "env_logger" name = "env_logger"
version = "0.10.2" version = "0.10.2"
@ -704,7 +698,7 @@ checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]] [[package]]
name = "lprs" name = "lprs"
version = "2.0.0-rc.2" version = "2.0.0-rc.0"
dependencies = [ dependencies = [
"aes", "aes",
"base32", "base32",
@ -714,7 +708,6 @@ dependencies = [
"clap", "clap",
"clap_complete", "clap_complete",
"directories", "directories",
"either",
"inquire", "inquire",
"log", "log",
"passwords", "passwords",

View file

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

View file

@ -1,7 +1,7 @@
<div align="center"> <div align="center">
# Lprs # Lprs
A local vault manager designed to securely store and manage your vaults. 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) [![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> </div>
## Features ## Features
- Auto checks for updates (Can be disabled). - Auto checks for updates (Can be disabled).
- Passing the master password as an argument and via stdin. - Passing the master password as an argument and via stdin.
- Changing the master password. - Changing the master password.
@ -37,7 +36,6 @@ A local vault manager designed to securely store and manage your vaults.
## Installation ## Installation
### Build from source (MSRV: `1.74.0`) ### Build from source (MSRV: `1.74.0`)
```bash ```bash
# From crates.io # From crates.io
cargo install lprs cargo install lprs
@ -46,12 +44,9 @@ cargo install lprs
cargo build --release cargo build --release
``` ```
This will build Lprs with update checking enabled. If you want to disable update 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.
checking, you can build Lprs without the default features by passing the
`--no-default-features` flag.
### Pre-built binaries ### Pre-built binaries
Pre-built binaries are available for Linux and Windows on the [releases 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 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 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 ## Usage
Lprs provides a command-line interface for managing your vaults. The following Lprs provides a command-line interface for managing your vaults. The following commands are available:
commands are available:
``` ```
A local CLI vaults manager. For human and machine use 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 get Get a entire vault or single field from it
export Export the vaults export Export the vaults
import Import 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 completion Generate shell completion
help Print this message or the help of the given subcommand(s) 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 -f, --vaults-file <VAULTS_FILE> The vaults json file
-v, --verbose Show the logs in the stdout -v, --verbose Show the logs in the stdout
-m, --master-password <MASTER_PASSWORD> The master password, or you will prompt it -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 -V, --version Print version
``` ```
## Documentation ## 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 ## Contributing
Contributions to Lprs are welcome! If you would like to contribute, please Contributions to Lprs are welcome! If you would like to contribute, please follow the guidelines outlined in the [CONTRIBUTING.md](CONTRIBUTING.md) file.
follow the guidelines outlined in the [CONTRIBUTING.md](CONTRIBUTING.md) file.
## Mirrors ## Mirrors
This repository is mirrored on the following platforms: This repository is mirrored on the following platforms:
- [GitHub](https://github.com/TheAwiteb/lprs) - [GitHub](https://github.com/TheAwiteb/lprs)
- [Codeberg](https://codeberg.org/awiteb/lprs) - [Codeberg](https://codeberg.org/awiteb/lprs)
## License ## License
Lprs is licensed under the GPL-3.0 License. This means that you are free to use, 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.
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) [![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 multilingual = false
src = "docs" src = "docs"
title = "Lprs Documentation" 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) - [Editing a vault](commands/edit.md)
- [Getting a vault](commands/get.md) - [Getting a vault](commands/get.md)
- [Listing all vaults](commands/list.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) - [Generating a password](commands/gen.md)
- [Importing and exporting vaults](commands/import-export.md) - [Importing and exporting vaults](commands/import-export.md)
- [Changing the master password](commands/change-master-password.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) - [Editing a vault](commands/edit.md)
- [Getting a vault](commands/get.md) - [Getting a vault](commands/get.md)
- [Listing all vaults](commands/list.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) - [Generating a password](commands/generate-password.md)
- [Importing and exporting vaults](commands/import-export.md) - [Importing and exporting vaults](commands/import-export.md)
- [Changing the master password](commands/change-master-password.md) - [Changing the master password](commands/change-master-password.md)

View file

@ -35,11 +35,9 @@ Options:
-t, --totp-secret [<TOTP_SECRET>] -t, --totp-secret [<TOTP_SECRET>]
The TOTP secret, 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)?> -c, --custom <KEY=VALUE>
Add a custom field to the vault Add a custom field to the vault
If there is no value, you will enter it through a prompt
-f, --force -f, --force
Force add, will not return error if there is a problem with the args. Force add, will not return error if there is a problem with the args.
@ -47,6 +45,9 @@ Options:
-h, --help -h, --help
Print help (see a summary with '-h') 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 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
``` ```
Usage: lprs clean 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 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 The new vault name
-u, --username <USERNAME> -u, --username <USERNAME>
The new vault username, make it empty string to delete it The new vault username
-p, --password [<PASSWORD>] -p, --password [<PASSWORD>]
The new password, make it empty string to delete it The new password, if there is no value for it you will prompt it
If there is no value for it you will prompt it
-s, --service <SERVICE> -s, --service <SERVICE>
The new vault service, make it empty string to delete it The new vault service
-o, --note <NOTE> -o, --note <NOTE>
The new vault note The new vault note
-t, --totp-secret [<TOTP_SECRET>] -t, --totp-secret [<TOTP_SECRET>]
The TOTP secret, make it empty string to delete it The TOTP secret, if there is no value you will prompt it
If there is no value you will prompt it
-c, --custom <KEY=VALUE> -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 -f, --force
Force edit, will not return error if there is a problem with the args. Force edit, will not return error if there is a problem with the args.
@ -44,6 +40,9 @@ Options:
-h, --help -h, --help
Print help (see a summary with '-h') 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 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 For secrets like the password and TOTP secret, you can provide them as arguments
or you will be prompted for them. 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 ## Custom fields
If you want to add a custom field to the vault, you can use the `-c, --custom` 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, 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) -n, --numbers With numbers (0-9)
-s, --symbols With symbols (!,# ...) -s, --symbols With symbols (!,# ...)
-h, --help Print help -h, --help Print help
-V, --version Print version
``` ```
Generate a password with the specified length, by default the length is `18`, Generate a password with the specified length, by default the length is `18`,

View file

@ -19,6 +19,9 @@ Arguments:
Options: Options:
-h, --help -h, --help
Print help (see a summary with '-h') 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 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 -h, --help
Print help (see a summary with '-h') Print help (see a summary with '-h')
-V, --version
Print version
``` ```
## Export usage ## Export usage

View file

@ -10,6 +10,7 @@ Options:
-r, --regex Enable regex when use `--filter` option -r, --regex Enable regex when use `--filter` option
--json Returns the output as `json` list of vaults --json Returns the output as `json` list of vaults
-h, --help Print help -h, --help Print help
-V, --version Print version
``` ```
Lprs `list` command is used to list all vaults in the vaults file, you can also 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 ### Examples
<script src="https://asciinema.org/a/eEVkDi0NroBjKNILg7KW3hSKY.js" id="asciicast-eEVkDi0NroBjKNILg7KW3hSKY" async="true" data-cols=48 data-rows=10></script> <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
``` ```
Usage: lprs remove [OPTIONS] [INDEX-or-NAME]... Usage: lprs remove [OPTIONS] <INDEX-or-NAME>
Arguments: Arguments:
[INDEX-or-NAME]... The vaults to remove, index or name <INDEX-or-NAME> The vault to remove, index or name
Options: Options:
-f, --force Force remove, will not return error if there is no vault with the given index or name -f, --force Force remove, will not return error if there is no vault with the given index or name
-h, --help Print help -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 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. 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. return an error if there is no vault with the given index or name.
## Examples ## Examples
Remove a vaults by its index: Remove a vault by its index:
```sh ```sh
lprs remove 1 10 14 lprs remove 1
``` ```
Remove a vault by its name: Remove a vault by its name:
```sh ```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 ```sh
lprs remove 234 --force lprs remove 234 --force
``` ```

View file

@ -14,48 +14,17 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://gnu.org/licenses/gpl-3.0.html>. // 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}; use crate::{LprsError, LprsResult};
/// Parse the key & value arguments. /// Parse the key & value arguments.
///
/// ## Errors /// ## Errors
/// - If the argument value syntax not `key=value` /// - 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('=') { if let Some((key, value)) = value.split_once('=') {
Ok((key.trim().to_owned(), Some(value.trim().to_owned()))) Ok((key.trim().to_owned(), value.trim().to_owned()))
} else if value.trim().is_empty() {
Err(LprsError::ArgParse(
"Invalid key, the syntax is `KEY(=VALUE)?`".to_owned(),
))
} else { } 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)] #[allow(clippy::option_option)]
totp_secret: Option<Option<String>>, totp_secret: Option<Option<String>>,
/// Add a custom field to the vault /// 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)] #[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. /// 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 /// 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.name = self.vault_info.name.trim().to_string();
self.vault_info.password = utils::user_secret(self.password, "Vault password:", false)?; self.vault_info.password = utils::user_secret(self.password, "Vault password:", false)?;
self.vault_info.custom_fields = utils::prompt_custom(self.custom_fields)? self.vault_info.custom_fields = self.custom_fields.into_iter().collect();
.into_iter()
.collect();
vault_manager.add_vault(self.vault_info); vault_manager.add_vault(self.vault_info);
vault_manager.try_export()?; 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 if self
.custom_fields .custom_fields
.iter() .iter()

View file

@ -14,13 +14,10 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://gnu.org/licenses/gpl-3.0.html>. // along with this program. If not, see <https://gnu.org/licenses/gpl-3.0.html>.
use std::num::NonZeroUsize;
use clap::Args; use clap::Args;
use either::Either;
use crate::{ use crate::{
clap_parsers::{either_parser, kv_parser}, clap_parsers,
utils, utils,
vault::{cipher, Vaults}, vault::{cipher, Vaults},
LprsCommand, LprsCommand,
@ -32,40 +29,35 @@ use crate::{
/// Edit command, used to edit the vault content /// Edit command, used to edit the vault content
pub struct Edit { pub struct Edit {
/// The vault to edit, index or name /// The vault to edit, index or name
#[arg(name = "INDEX-or-NAME", value_parser = either_parser::<NonZeroUsize, String>)] #[arg(name = "INDEX-or-NAME")]
location: Either<NonZeroUsize, String>, location: String,
#[arg(short, long)] #[arg(short, long)]
/// The new vault name /// The new vault name
name: Option<String>, name: Option<String>,
#[arg(short, long)] #[arg(short, long)]
/// The new vault username, make it empty string to delete it /// The new vault username
username: Option<String>, username: Option<String>,
#[arg(short, long)] #[arg(short, long)]
/// The new password, make it empty string to delete it /// The new password, if there is no value for it you will prompt it
///
/// If there is no value for it you will prompt it
#[allow(clippy::option_option)] #[allow(clippy::option_option)]
password: Option<Option<String>>, password: Option<Option<String>>,
#[arg(short, long)] #[arg(short, long)]
/// The new vault service, make it empty string to delete it /// The new vault service
service: Option<String>, service: Option<String>,
#[arg(short = 'o', long)] #[arg(short = 'o', long)]
/// The new vault note /// The new vault note
note: Option<String>, note: Option<String>,
/// The TOTP secret, make it empty string to delete it /// The TOTP secret, if there is no value you will prompt it
///
/// If there is no value you will prompt it
#[arg(short, long)] #[arg(short, long)]
#[allow(clippy::option_option)] #[allow(clippy::option_option)]
totp_secret: Option<Option<String>>, 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 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(name = "KEY=VALUE", short = 'c', long = "custom")]
#[arg(value_parser = kv_parser)] #[arg(value_parser = clap_parsers::kv_parser)]
custom_fields: Vec<(String, Option<String>)>, custom_fields: Vec<(String, String)>,
/// Force edit, will not return error if there is a problem with the args. /// 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 /// For example, duplication in the custom fields and try to editing nothing
@ -75,7 +67,8 @@ pub struct Edit {
impl LprsCommand for Edit { impl LprsCommand for Edit {
fn run(self, mut vault_manager: Vaults) -> LprsResult<()> { fn run(self, mut vault_manager: Vaults) -> LprsResult<()> {
let vault = match utils::vault_by_index_or_name(&self.location, &mut vault_manager.vaults) { let vault =
match utils::vault_by_index_or_name(self.location.trim(), &mut vault_manager.vaults) {
Ok((_, v)) => v, Ok((_, v)) => v,
Err(err) => { Err(err) => {
if self.force { if self.force {
@ -89,48 +82,25 @@ impl LprsCommand for Edit {
if let Some(new_name) = self.name { if let Some(new_name) = self.name {
vault.name = new_name; vault.name = new_name;
} }
if let Some(ref new_password) = self.password { if self.password.is_some() {
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)?; 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 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(|_| { cipher::base32_decode(&totp_secret).map_err(|_| {
LprsError::Base32("Invalid TOTP secret, must be valid base32 string".to_owned()) LprsError::Base32("Invalid TOTP secret, must be valid base32 string".to_owned())
})?; })?;
vault.totp_secret = Some(totp_secret); vault.totp_secret = Some(totp_secret);
} }
}
if let Some(new_username) = self.username { 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 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 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, self.custom_fields);
utils::apply_custom_fields(
&mut vault.custom_fields,
utils::prompt_custom(self.custom_fields)?,
);
vault_manager.try_export() vault_manager.try_export()
} }

View file

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

View file

@ -47,30 +47,23 @@ impl LprsCommand for List {
)); ));
} }
let pattern = if self.regex { let pattern = if self.regex || self.filter.is_none() {
self.filter self.filter.unwrap_or_else(|| ".".to_owned())
.expect("Is required if the `regex` option is `true`")
} else { } else {
format!( 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}"); 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)| { let vaults_list = vault_manager.vaults.iter().enumerate().filter(|(_, v)| {
re.is_match(&v.name.to_lowercase()) re.is_match(&v.name)
|| v.username || v.username.as_deref().is_some_and(|u| re.is_match(u))
.as_deref() || v.service.as_deref().is_some_and(|s| re.is_match(s))
.is_some_and(|u| re.is_match(&u.to_lowercase())) || v.note.as_deref().is_some_and(|n| re.is_match(n))
|| 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()))
}); });
if self.json { if self.json {

View file

@ -122,16 +122,11 @@ impl Cli {
log::info!("Using the given vaults file"); log::info!("Using the given vaults file");
if let Some(parent) = path.parent() { if let Some(parent) = path.parent() {
if parent.to_str() != Some("") && !parent.exists() { if parent.to_str() != Some("") && !parent.exists() {
log::info!( log::info!("Creating the parent vaults file directory");
"Creating the parent vaults file directory: {}",
parent.display()
);
fs::create_dir_all(parent)?; fs::create_dir_all(parent)?;
} }
} }
if !path.exists() {
fs::File::create(&path)?; fs::File::create(&path)?;
}
path path
} else { } else {
log::info!("Using the default vaults file"); 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 // 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>. // along with this program. If not, see <https://gnu.org/licenses/gpl-3.0.html>.
use std::num::NonZeroUsize;
use clap::Args; use clap::Args;
use either::Either;
use crate::{ use crate::{utils, vault::Vaults, LprsCommand, LprsResult};
clap_parsers::either_parser,
utils,
vault::{Vault, Vaults},
LprsCommand,
LprsResult,
};
#[derive(Debug, Args)] #[derive(Debug, Args)]
/// Remove command, used to remove a vault from the vaults file /// Remove command, used to remove a vault from the vaults file
pub struct Remove { pub struct Remove {
/// The vaults to remove, index or name /// The vault to remove, index or name
#[arg(name = "INDEX-or-NAME", value_parser = either_parser::<NonZeroUsize, String>)] #[arg(name = "INDEX-or-NAME")]
locations: Vec<Either<NonZeroUsize, String>>, location: String,
/// Force remove, will not return error if there is no vault with the given /// Force remove, will not return error if there is no vault with the given
/// index or name /// index or name
@ -42,26 +33,17 @@ pub struct Remove {
impl LprsCommand for Remove { impl LprsCommand for Remove {
fn run(self, mut vault_manager: Vaults) -> LprsResult<()> { fn run(self, mut vault_manager: Vaults) -> LprsResult<()> {
let indexes = self let index =
.locations match utils::vault_by_index_or_name(self.location.trim(), &mut vault_manager.vaults) {
.iter() Ok((idx, _)) => idx,
.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) => { Err(err) => {
if self.force { if self.force {
log::warn!("Ignoring error: {err}");
return Ok(()); return Ok(());
} }
return Err(err); return Err(err);
} }
} };
vault_manager.vaults.remove(index);
vault_manager.try_export() vault_manager.try_export()
} }
} }

View file

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

View file

@ -15,15 +15,9 @@
// along with this program. If not, see <https://gnu.org/licenses/gpl-3.0.html>. // along with this program. If not, see <https://gnu.org/licenses/gpl-3.0.html>.
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::num::NonZeroUsize;
use std::{fs, path::PathBuf}; use std::{fs, path::PathBuf};
use either::Either; use inquire::{validator::Validation, Password, PasswordDisplayMode};
use inquire::{
validator::{StringValidator, Validation},
Password,
PasswordDisplayMode,
};
use passwords::{analyzer, scorer}; use passwords::{analyzer, scorer};
#[cfg(feature = "update-notify")] #[cfg(feature = "update-notify")]
use reqwest::blocking::Client as BlockingClient; use reqwest::blocking::Client as BlockingClient;
@ -51,26 +45,6 @@ pub fn local_project_file(filename: &str) -> LprsResult<PathBuf> {
Ok(local_dir.join(filename)) 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 /// Returns the user secret if any
/// ///
/// - If the `secret` is `None` will return `None` /// - If the `secret` is `None` will return `None`
@ -90,7 +64,15 @@ pub fn user_secret(
Some(Some(p)) => Some(p), Some(Some(p)) => Some(p),
Some(None) => { Some(None) => {
log::debug!("User didn't provide a secret, prompting it"); 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) /// 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]> { pub fn master_password_prompt(is_new_vaults_file: bool) -> LprsResult<[u8; 32]> {
secret_prompt( inquire::Password {
"Master Password:", message: "Master Password:",
is_new_vaults_file, enable_confirmation: is_new_vaults_file,
is_new_vaults_file.then(|| vec![Box::new(password_validator) as Box<dyn StringValidator>]), 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(|p| sha2::Sha256::digest(p).into())
.map_err(Into::into)
} }
/// Retuns the current lprs version from `crates.io` /// 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) /// 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, _)| { fields.iter().find_map(|(key, _)| {
if fields.iter().filter(|(k, _)| key == k).count() > 1 { if fields.iter().filter(|(k, _)| key == k).count() > 1 {
return Some(key.as_str()); 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 /// Returns the vault with its index by its index or name
///
/// ## 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 /// ## Errors
/// - If there is no vault with the given index or name /// - If there is no vault with the given index or name
pub fn vault_by_index_or_name<'v>( pub fn vault_by_index_or_name<'a>(
location: &Either<NonZeroUsize, String>, index_or_name: &str,
vaults: &'v mut [Vault], vaults: &'a mut [Vault],
) -> LprsResult<(usize, &'v mut Vault)> { ) -> LprsResult<(usize, &'a mut Vault)> {
let idx = location let parsed_index = index_or_name.parse::<usize>();
.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()?;
Ok(( let Some((index, vault)) = (if let Ok(index) = parsed_index {
idx, index
vaults.get_mut(idx).ok_or_else(|| { .checked_sub(1)
LprsError::Other(format!( .and_then(|zeroindex| vaults.get_mut(zeroindex).map(|v| (index, v)))
"There is no vault with the given index `{}`", } else {
idx + 1 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 /// ## 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)> { pub fn totp_now(secret_base32: &str, hash_function: &TotpHash) -> LprsResult<(String, u8)> {
let now = SystemTime::now() let now = SystemTime::now()
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
.expect("SystemTime before UNIX EPOCH!") .expect("SystemTime before UNIX EPOCH!")
.as_secs(); .as_secs();
let remaining = 30 - (now % 30) as u8;
let secret = base32_decode(secret_base32)?; let secret = base32_decode(secret_base32)?;
let totp_code = match hash_function { Ok(match hash_function {
TotpHash::Sha1 => totp_lite::totp_custom::<totp_lite::Sha1>(30, 6, &secret, now), TotpHash::Sha1 => {
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), totp_lite::totp_custom::<totp_lite::Sha1>(30, 6, &secret, now),
}; remaining,
Ok((totp_code, 30 - (now % 30) as u8)) )
}
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 /// Base32 decode

View file

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