Compare commits
33 commits
v2.0.0-rc.
...
master
Author | SHA1 | Date | |
---|---|---|---|
2cfddbbcbd | |||
e8a18d4f53 | |||
75625f53b4 | |||
ae7e310a49 | |||
00475f5240 | |||
e8ac0ffaae | |||
656cc9f984 | |||
f364f5bb4b | |||
78513b3694 | |||
|
6809a0f5bf | ||
6293eadafb | |||
dbaa518855 | |||
e033971665 | |||
aee33f819c | |||
ad3dc025a9 | |||
|
9b39da877f | ||
1c90a82544 | |||
|
5f38d23c28 | ||
5e4fb0ea7c | |||
|
04614aff57 | ||
6f5ca5f452 | |||
|
6e5e3c093c | ||
3567865c2d | |||
5d30b8b021 | |||
|
90bcf968d4 | ||
e02310401a | |||
|
0d674f9f2d | ||
81a360519e | |||
|
d6c38c3203 | ||
9a417e7d0b | |||
1221ed45db | |||
cd6e0f9bae | |||
cf9f3d9d0a |
31 changed files with 369 additions and 204 deletions
|
@ -9,16 +9,12 @@ assignees: ''
|
||||||
## Checks
|
## Checks
|
||||||
|
|
||||||
* [ ] I added a descriptive title to this issue
|
* [ ] I added a descriptive title to this issue
|
||||||
* [ ] I have searched Google for similar issues and couldn't find anything
|
* [ ] I have read [the documentation](https://lprs.4rs.nl) and still think this it's a bug
|
||||||
* [ ] 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
|
||||||
|
|
|
@ -12,5 +12,5 @@ For the location, please provide a link to the code or the documentation.
|
||||||
|
|
||||||
| Location | Suggestion |
|
| Location | Suggestion |
|
||||||
|----------|------------|
|
|----------|------------|
|
||||||
| Code-link| Suggestion |
|
| location | Suggestion |
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
sha256sum $APP_NAME > $APP_NAME.sha256 || true
|
test -f $APP_NAME && sha256sum $APP_NAME > $APP_NAME.sha256 || true
|
||||||
sha256sum $APP_NAME.exe > $APP_NAME.exe.sha256 || true
|
test -f $APP_NAME.exe && 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
|
||||||
sha256sum $APP_NAME > $APP_NAME.sha256 || true
|
test -f $APP_NAME && sha256sum $APP_NAME > $APP_NAME.sha256 || true
|
||||||
sha256sum $APP_NAME.exe > $APP_NAME.exe.sha256 || true
|
test -f $APP_NAME.exe && sha256sum $APP_NAME.exe > $APP_NAME.exe.sha256 || true
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
|
|
|
@ -25,7 +25,6 @@ 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
|
||||||
|
|
|
@ -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/TheAwiteb/rust-action@v1.74
|
- uses: https://codeberg.org/awiteb/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
|
||||||
|
|
|
@ -11,6 +11,7 @@ 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)
|
||||||
|
@ -20,25 +21,32 @@ 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
9
Cargo.lock
generated
|
@ -349,6 +349,12 @@ 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"
|
||||||
|
@ -698,7 +704,7 @@ checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lprs"
|
name = "lprs"
|
||||||
version = "2.0.0-rc.0"
|
version = "2.0.0-rc.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"base32",
|
"base32",
|
||||||
|
@ -708,6 +714,7 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"clap_complete",
|
"clap_complete",
|
||||||
"directories",
|
"directories",
|
||||||
|
"either",
|
||||||
"inquire",
|
"inquire",
|
||||||
"log",
|
"log",
|
||||||
"passwords",
|
"passwords",
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lprs"
|
name = "lprs"
|
||||||
version = "2.0.0-rc.0"
|
version = "2.0.0-rc.2"
|
||||||
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"]
|
||||||
|
@ -32,6 +33,7 @@ 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"]
|
||||||
|
|
33
README.md
33
README.md
|
@ -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,6 +14,7 @@ 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.
|
||||||
|
@ -36,6 +37,7 @@ 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
|
||||||
|
@ -44,9 +46,12 @@ cargo install lprs
|
||||||
cargo build --release
|
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
|
||||||
|
|
||||||
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
|
||||||
|
@ -54,7 +59,8 @@ update checking enabled have the `lrps-update-notify` prefix.
|
||||||
|
|
||||||
## Usage
|
## 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
|
A local CLI vaults manager. For human and machine use
|
||||||
|
@ -71,7 +77,7 @@ Commands:
|
||||||
get Get a entire vault or single field from it
|
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 the master password
|
change-master-password Change master password, reencrypt the vaults with new 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)
|
||||||
|
|
||||||
|
@ -79,26 +85,37 @@ 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
|
-h, --help Print help (see more with '--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 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
|
## 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, 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)
|
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/awiteb)
|
||||||
|
|
|
@ -4,3 +4,9 @@ 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"
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
- [Clening the vaults](commands/clean.md)
|
- [Cleaning 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)
|
||||||
|
|
|
@ -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)
|
||||||
- [Clening the vaults](commands/clean.md)
|
- [Cleaning 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)
|
||||||
|
|
|
@ -35,9 +35,11 @@ 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.
|
||||||
|
|
||||||
|
@ -45,9 +47,6 @@ 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
|
||||||
|
|
|
@ -1,13 +1,9 @@
|
||||||
# Clening the vaults
|
# Cleaning 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
|
||||||
|
|
|
@ -14,24 +14,28 @@ Options:
|
||||||
The new vault name
|
The new vault name
|
||||||
|
|
||||||
-u, --username <USERNAME>
|
-u, --username <USERNAME>
|
||||||
The new vault username
|
The new vault username, make it empty string to delete it
|
||||||
|
|
||||||
-p, --password [<PASSWORD>]
|
-p, --password [<PASSWORD>]
|
||||||
The new password, if there is no value for it you will prompt it
|
The new password, make it empty string to delete it
|
||||||
|
|
||||||
|
If there is no value for it you will prompt it
|
||||||
|
|
||||||
-s, --service <SERVICE>
|
-s, --service <SERVICE>
|
||||||
The new vault service
|
The new vault service, make it empty string to delete it
|
||||||
|
|
||||||
-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, if there is no value you will prompt it
|
The TOTP secret, make it empty string to delete it
|
||||||
|
|
||||||
|
If there is no value you will prompt it
|
||||||
|
|
||||||
-c, --custom <KEY=VALUE>
|
-c, --custom <KEY=VALUE>
|
||||||
The custom field, make its value empty to delete it
|
The custom field, make it empty string to delete it
|
||||||
|
|
||||||
If the custom field not exist will created it, if it's will update it
|
If the custom field not exist will created it, if it's will update it, if there is no value, you will enter it through a prompt (e.g `-c key`)
|
||||||
|
|
||||||
-f, --force
|
-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.
|
||||||
|
@ -40,9 +44,6 @@ 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
|
||||||
|
@ -55,6 +56,10 @@ 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,
|
||||||
|
|
|
@ -14,7 +14,6 @@ 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`,
|
||||||
|
|
|
@ -19,9 +19,6 @@ 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
|
||||||
|
|
|
@ -24,9 +24,6 @@ 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
|
||||||
|
|
|
@ -10,7 +10,6 @@ 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
|
||||||
|
@ -21,4 +20,5 @@ 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>
|
||||||
|
|
|
@ -3,18 +3,17 @@
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
Usage: lprs remove [OPTIONS] <INDEX-or-NAME>
|
Usage: lprs remove [OPTIONS] [INDEX-or-NAME]...
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
<INDEX-or-NAME> The vault to remove, index or name
|
[INDEX-or-NAME]... The vaults 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 vault you need to provide the index or the name of the vault. If you
|
To remove a vaults you need to provide the index or the name of each vault. If you
|
||||||
provide the index, the vault will be removed by its index, if you provide the
|
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.
|
||||||
|
|
||||||
|
@ -23,17 +22,17 @@ unless you provide the `--force` option, in which case the command will not
|
||||||
return an error if there is no vault with the given index or name.
|
return an error if there is no vault with the given index or name.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
Remove a vault by its index:
|
Remove a vaults by its index:
|
||||||
```sh
|
```sh
|
||||||
lprs remove 1
|
lprs remove 1 10 14
|
||||||
```
|
```
|
||||||
|
|
||||||
Remove a vault by its name:
|
Remove a vault by its name:
|
||||||
```sh
|
```sh
|
||||||
lprs remove my-vault
|
lprs remove my-vault 'another vault' "third vault"
|
||||||
```
|
```
|
||||||
|
|
||||||
Force remove a vault by its index:
|
Force remove a vault by its index (will not return an error if there is no vault with the given index):
|
||||||
```sh
|
```sh
|
||||||
lprs remove 234 --force
|
lprs remove 234 --force
|
||||||
```
|
```
|
||||||
|
|
|
@ -14,17 +14,48 @@
|
||||||
// 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, String)> {
|
pub fn kv_parser(value: &str) -> LprsResult<(String, Option<String>)> {
|
||||||
if let Some((key, value)) = value.split_once('=') {
|
if let Some((key, value)) = value.split_once('=') {
|
||||||
Ok((key.trim().to_owned(), value.trim().to_owned()))
|
Ok((key.trim().to_owned(), Some(value.trim().to_owned())))
|
||||||
} else {
|
} else if value.trim().is_empty() {
|
||||||
Err(LprsError::ArgParse(
|
Err(LprsError::ArgParse(
|
||||||
"There is no value, the syntax is `KEY=VALUE`".to_owned(),
|
"Invalid key, the syntax is `KEY(=VALUE)?`".to_owned(),
|
||||||
))
|
))
|
||||||
|
} else {
|
||||||
|
Ok((value.trim().to_owned(), None))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse `Either` type arguments.
|
||||||
|
///
|
||||||
|
/// ## Errors
|
||||||
|
/// - If the argument value can't be parsed to `L` or `R`
|
||||||
|
pub fn either_parser<L, R>(value: &str) -> LprsResult<Either<L, R>>
|
||||||
|
where
|
||||||
|
L: FromStr,
|
||||||
|
R: FromStr,
|
||||||
|
<L as FromStr>::Err: Display,
|
||||||
|
<R as FromStr>::Err: Display,
|
||||||
|
{
|
||||||
|
value
|
||||||
|
.trim()
|
||||||
|
.parse::<L>()
|
||||||
|
.map_err(|err| LprsError::ArgParse(err.to_string()))
|
||||||
|
.map(Left)
|
||||||
|
.or_else(|_| {
|
||||||
|
value
|
||||||
|
.parse::<R>()
|
||||||
|
.map_err(|err| LprsError::ArgParse(err.to_string()))
|
||||||
|
.map(Right)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -39,9 +39,11 @@ 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, String)>,
|
custom_fields: Vec<(String, Option<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
|
||||||
|
@ -73,7 +75,9 @@ 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 = self.custom_fields.into_iter().collect();
|
self.vault_info.custom_fields = utils::prompt_custom(self.custom_fields)?
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
vault_manager.add_vault(self.vault_info);
|
vault_manager.add_vault(self.vault_info);
|
||||||
vault_manager.try_export()?;
|
vault_manager.try_export()?;
|
||||||
}
|
}
|
||||||
|
@ -92,6 +96,29 @@ impl LprsCommand for Add {
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if self
|
||||||
|
.password
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|p| p.as_ref().is_some_and(String::is_empty))
|
||||||
|
|| self.vault_info.name.is_empty()
|
||||||
|
|| self
|
||||||
|
.vault_info
|
||||||
|
.username
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(String::is_empty)
|
||||||
|
|| self
|
||||||
|
.vault_info
|
||||||
|
.service
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(String::is_empty)
|
||||||
|
|| self.vault_info.note.as_ref().is_some_and(String::is_empty)
|
||||||
|
|| self
|
||||||
|
.custom_fields
|
||||||
|
.iter()
|
||||||
|
.any(|(k, v)| k.is_empty() || v.as_ref().is_some_and(String::is_empty))
|
||||||
|
{
|
||||||
|
return Err(LprsError::EmptyValue);
|
||||||
|
}
|
||||||
if self
|
if self
|
||||||
.custom_fields
|
.custom_fields
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -14,10 +14,13 @@
|
||||||
// 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,
|
clap_parsers::{either_parser, kv_parser},
|
||||||
utils,
|
utils,
|
||||||
vault::{cipher, Vaults},
|
vault::{cipher, Vaults},
|
||||||
LprsCommand,
|
LprsCommand,
|
||||||
|
@ -29,35 +32,40 @@ 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")]
|
#[arg(name = "INDEX-or-NAME", value_parser = either_parser::<NonZeroUsize, String>)]
|
||||||
location: String,
|
location: Either<NonZeroUsize, 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
|
/// The new vault username, make it empty string to delete it
|
||||||
username: Option<String>,
|
username: Option<String>,
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
/// The new password, if there is no value for it you will prompt it
|
/// The new password, make it empty string to delete it
|
||||||
|
///
|
||||||
|
/// If there is no value for it you will prompt it
|
||||||
#[allow(clippy::option_option)]
|
#[allow(clippy::option_option)]
|
||||||
password: Option<Option<String>>,
|
password: Option<Option<String>>,
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
/// The new vault service
|
/// The new vault service, make it empty string to delete it
|
||||||
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, if there is no value you will prompt it
|
/// The TOTP secret, make it empty string to delete it
|
||||||
|
///
|
||||||
|
/// If there is no value you will prompt it
|
||||||
#[arg(short, long)]
|
#[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 its value empty to delete it
|
/// The custom field, make it empty string to delete it
|
||||||
///
|
///
|
||||||
/// If the custom field not exist will created it, if it's will update it
|
/// If the custom field not exist will created it, if it's will update it,
|
||||||
|
/// if there is no value, you will enter it through a prompt (e.g `-c key`)
|
||||||
#[arg(name = "KEY=VALUE", short = 'c', long = "custom")]
|
#[arg(name = "KEY=VALUE", short = 'c', long = "custom")]
|
||||||
#[arg(value_parser = clap_parsers::kv_parser)]
|
#[arg(value_parser = kv_parser)]
|
||||||
custom_fields: Vec<(String, String)>,
|
custom_fields: Vec<(String, Option<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
|
||||||
|
@ -67,40 +75,62 @@ 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 =
|
let vault = match utils::vault_by_index_or_name(&self.location, &mut vault_manager.vaults) {
|
||||||
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 {
|
return Ok(());
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
return Err(err);
|
|
||||||
}
|
}
|
||||||
};
|
return Err(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
log::info!("Applying the new values to the vault");
|
log::info!("Applying the new values to the vault");
|
||||||
if let Some(new_name) = self.name {
|
if let Some(new_name) = self.name {
|
||||||
vault.name = new_name;
|
vault.name = new_name;
|
||||||
}
|
}
|
||||||
if self.password.is_some() {
|
if let Some(ref new_password) = self.password {
|
||||||
vault.password = utils::user_secret(self.password, "New vault password:", false)?;
|
if new_password.as_deref().is_some_and(|s| s.is_empty()) {
|
||||||
|
vault.password = None;
|
||||||
|
} else {
|
||||||
|
vault.password = utils::user_secret(self.password, "New vault password:", false)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if let Some(totp_secret) = utils::user_secret(self.totp_secret, "TOTP Secret:", false)? {
|
if let Some(totp_secret) = utils::user_secret(self.totp_secret, "TOTP Secret:", false)? {
|
||||||
cipher::base32_decode(&totp_secret).map_err(|_| {
|
if totp_secret.is_empty() {
|
||||||
LprsError::Base32("Invalid TOTP secret, must be valid base32 string".to_owned())
|
vault.totp_secret = None;
|
||||||
})?;
|
} else {
|
||||||
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 let Some(new_username) = self.username {
|
||||||
vault.username = Some(new_username);
|
if new_username.is_empty() {
|
||||||
|
vault.username = None;
|
||||||
|
} else {
|
||||||
|
vault.username = Some(new_username);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if let Some(new_service) = self.service {
|
if let Some(new_service) = self.service {
|
||||||
vault.service = Some(new_service);
|
if new_service.is_empty() {
|
||||||
|
vault.service = None;
|
||||||
|
} else {
|
||||||
|
vault.service = Some(new_service);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if let Some(new_note) = self.note {
|
if let Some(new_note) = self.note {
|
||||||
vault.note = Some(new_note);
|
if new_note.is_empty() {
|
||||||
|
vault.note = None;
|
||||||
|
} else {
|
||||||
|
vault.note = Some(new_note);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
utils::apply_custom_fields(&mut vault.custom_fields, self.custom_fields);
|
utils::apply_custom_fields(
|
||||||
|
&mut vault.custom_fields,
|
||||||
|
utils::prompt_custom(self.custom_fields)?,
|
||||||
|
);
|
||||||
|
|
||||||
vault_manager.try_export()
|
vault_manager.try_export()
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,11 +14,13 @@
|
||||||
// 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::str::FromStr;
|
use std::{num::NonZeroUsize, 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,
|
||||||
|
@ -94,8 +96,9 @@ 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(value_name = "INDEX-or-NAME")]
|
#[arg(name = "INDEX-or-NAME", value_parser = either_parser::<NonZeroUsize, String>)]
|
||||||
location: String,
|
location: Either<NonZeroUsize, 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,
|
||||||
|
@ -103,13 +106,13 @@ pub struct Get {
|
||||||
///
|
///
|
||||||
/// where the string means a custom field
|
/// where the string means a custom field
|
||||||
#[arg(value_parser = VaultGetField::from_str)]
|
#[arg(value_parser = VaultGetField::from_str)]
|
||||||
field: Option<VaultGetField>,
|
field: Option<VaultGetField>,
|
||||||
}
|
}
|
||||||
|
|
||||||
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.trim(), &mut vault_manager.vaults)?;
|
utils::vault_by_index_or_name(&self.location, &mut vault_manager.vaults)?;
|
||||||
|
|
||||||
if let Some(field) = self.field {
|
if let Some(field) = self.field {
|
||||||
if field == VaultGetField::Index {
|
if field == VaultGetField::Index {
|
||||||
|
|
|
@ -47,23 +47,30 @@ impl LprsCommand for List {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let pattern = if self.regex || self.filter.is_none() {
|
let pattern = if self.regex {
|
||||||
self.filter.unwrap_or_else(|| ".".to_owned())
|
self.filter
|
||||||
|
.expect("Is required if the `regex` option is `true`")
|
||||||
} else {
|
} else {
|
||||||
format!(
|
format!(
|
||||||
".*{}.*",
|
".*{}.*",
|
||||||
regex::escape(self.filter.as_deref().unwrap_or(""))
|
regex::escape(self.filter.as_deref().unwrap_or_default())
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
log::debug!("Listing vaults filtered by: {pattern}");
|
log::debug!("Listing vaults filtered by: {pattern}");
|
||||||
|
|
||||||
let re = regex::Regex::new(&pattern)?;
|
let re = regex::Regex::new(&pattern.to_lowercase())?;
|
||||||
|
|
||||||
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)
|
re.is_match(&v.name.to_lowercase())
|
||||||
|| v.username.as_deref().is_some_and(|u| re.is_match(u))
|
|| v.username
|
||||||
|| v.service.as_deref().is_some_and(|s| re.is_match(s))
|
.as_deref()
|
||||||
|| v.note.as_deref().is_some_and(|n| re.is_match(n))
|
.is_some_and(|u| re.is_match(&u.to_lowercase()))
|
||||||
|
|| v.service
|
||||||
|
.as_deref()
|
||||||
|
.is_some_and(|s| re.is_match(&s.to_lowercase()))
|
||||||
|
|| v.note
|
||||||
|
.as_deref()
|
||||||
|
.is_some_and(|n| re.is_match(&n.to_lowercase()))
|
||||||
});
|
});
|
||||||
|
|
||||||
if self.json {
|
if self.json {
|
||||||
|
|
|
@ -122,11 +122,16 @@ 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!("Creating the parent vaults file directory");
|
log::info!(
|
||||||
|
"Creating the parent vaults file directory: {}",
|
||||||
|
parent.display()
|
||||||
|
);
|
||||||
fs::create_dir_all(parent)?;
|
fs::create_dir_all(parent)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fs::File::create(&path)?;
|
if !path.exists() {
|
||||||
|
fs::File::create(&path)?;
|
||||||
|
}
|
||||||
path
|
path
|
||||||
} else {
|
} else {
|
||||||
log::info!("Using the default vaults file");
|
log::info!("Using the default vaults file");
|
||||||
|
|
|
@ -14,16 +14,25 @@
|
||||||
// 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 clap::Args;
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
use crate::{utils, vault::Vaults, LprsCommand, LprsResult};
|
use clap::Args;
|
||||||
|
use either::Either;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
clap_parsers::either_parser,
|
||||||
|
utils,
|
||||||
|
vault::{Vault, Vaults},
|
||||||
|
LprsCommand,
|
||||||
|
LprsResult,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[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 vault to remove, index or name
|
/// The vaults to remove, index or name
|
||||||
#[arg(name = "INDEX-or-NAME")]
|
#[arg(name = "INDEX-or-NAME", value_parser = either_parser::<NonZeroUsize, String>)]
|
||||||
location: String,
|
locations: Vec<Either<NonZeroUsize, 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
|
||||||
|
@ -33,17 +42,26 @@ 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 index =
|
let indexes = self
|
||||||
match utils::vault_by_index_or_name(self.location.trim(), &mut vault_manager.vaults) {
|
.locations
|
||||||
Ok((idx, _)) => idx,
|
.iter()
|
||||||
Err(err) => {
|
.map(|location| {
|
||||||
if self.force {
|
utils::vault_by_index_or_name(location, &mut vault_manager.vaults)
|
||||||
return Ok(());
|
.map(|(_, v)| v.clone())
|
||||||
}
|
})
|
||||||
return Err(err);
|
.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(());
|
||||||
}
|
}
|
||||||
};
|
return Err(err);
|
||||||
vault_manager.vaults.remove(index);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
vault_manager.try_export()
|
vault_manager.try_export()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,8 @@ 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}")]
|
||||||
|
|
134
src/utils.rs
134
src/utils.rs
|
@ -15,9 +15,15 @@
|
||||||
// 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 inquire::{validator::Validation, Password, PasswordDisplayMode};
|
use either::Either;
|
||||||
|
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;
|
||||||
|
@ -45,6 +51,26 @@ 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`
|
||||||
|
@ -64,15 +90,7 @@ 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(
|
Some(secret_prompt(prompt_message, confirmation, None)?)
|
||||||
Password {
|
|
||||||
enable_confirmation: confirmation,
|
|
||||||
..Password::new(prompt_message)
|
|
||||||
.with_formatter(&|p| "*".repeat(p.chars().count()))
|
|
||||||
.with_display_mode(PasswordDisplayMode::Masked)
|
|
||||||
}
|
|
||||||
.prompt()?,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -117,21 +135,12 @@ pub fn password_validator(password: &str) -> Result<Validation, inquire::CustomU
|
||||||
///
|
///
|
||||||
/// Return's the password as 32 bytes after hash it (256 bit)
|
/// 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]> {
|
||||||
inquire::Password {
|
secret_prompt(
|
||||||
message: "Master Password:",
|
"Master Password:",
|
||||||
enable_confirmation: is_new_vaults_file,
|
is_new_vaults_file,
|
||||||
validators: if is_new_vaults_file {
|
is_new_vaults_file.then(|| vec![Box::new(password_validator) as Box<dyn StringValidator>]),
|
||||||
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`
|
||||||
|
@ -181,7 +190,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, String)]) -> Option<&str> {
|
pub fn get_duplicated_field(fields: &[(String, Option<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());
|
||||||
|
@ -210,35 +219,56 @@ pub fn apply_custom_fields(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the vault with its index by its index or name
|
/// Make sure all custom field values are there, if not, ask the user for it
|
||||||
|
///
|
||||||
|
/// ## Errors
|
||||||
|
/// - If can't read the user input
|
||||||
|
pub fn prompt_custom(
|
||||||
|
custom_fields: Vec<(String, Option<String>)>,
|
||||||
|
) -> LprsResult<Vec<(String, String)>> {
|
||||||
|
let mut new_fields = Vec::new();
|
||||||
|
|
||||||
|
for (key, value) in custom_fields {
|
||||||
|
if let Some(value) = value {
|
||||||
|
new_fields.push((key, value));
|
||||||
|
} else {
|
||||||
|
let value = secret_prompt(&format!("Value of `{key}`:"), false, None)?;
|
||||||
|
new_fields.push((key, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(new_fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the vault with its index by either its index or name
|
||||||
///
|
///
|
||||||
/// ## Errors
|
/// ## 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<'a>(
|
pub fn vault_by_index_or_name<'v>(
|
||||||
index_or_name: &str,
|
location: &Either<NonZeroUsize, String>,
|
||||||
vaults: &'a mut [Vault],
|
vaults: &'v mut [Vault],
|
||||||
) -> LprsResult<(usize, &'a mut Vault)> {
|
) -> LprsResult<(usize, &'v mut Vault)> {
|
||||||
let parsed_index = index_or_name.parse::<usize>();
|
let idx = location
|
||||||
|
.as_ref()
|
||||||
|
.map_right(|name| {
|
||||||
|
vaults
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find_map(|(idx, v)| (&v.name == name).then_some(idx))
|
||||||
|
.ok_or_else(|| {
|
||||||
|
LprsError::Other(format!("There is no vault with the given name `{name}`"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.map_left(|idx| LprsResult::Ok(idx.get() - 1))
|
||||||
|
.into_inner()?;
|
||||||
|
|
||||||
let Some((index, vault)) = (if let Ok(index) = parsed_index {
|
Ok((
|
||||||
index
|
idx,
|
||||||
.checked_sub(1)
|
vaults.get_mut(idx).ok_or_else(|| {
|
||||||
.and_then(|zeroindex| vaults.get_mut(zeroindex).map(|v| (index, v)))
|
LprsError::Other(format!(
|
||||||
} else {
|
"There is no vault with the given index `{}`",
|
||||||
vaults
|
idx + 1
|
||||||
.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))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,37 +40,22 @@ pub enum TotpHash {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Create the TOTP code of the current time
|
/// Create the TOTP code of the current time with its remainig time in seconds
|
||||||
///
|
///
|
||||||
/// ## Errors
|
/// ## Errors
|
||||||
/// - If the given `secret_base32` are vaild base32
|
/// - If the given `secret_base32` are invalid 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)?;
|
||||||
Ok(match hash_function {
|
let totp_code = match hash_function {
|
||||||
TotpHash::Sha1 => {
|
TotpHash::Sha1 => totp_lite::totp_custom::<totp_lite::Sha1>(30, 6, &secret, now),
|
||||||
(
|
TotpHash::Sha256 => totp_lite::totp_custom::<totp_lite::Sha256>(30, 6, &secret, now),
|
||||||
totp_lite::totp_custom::<totp_lite::Sha1>(30, 6, &secret, now),
|
TotpHash::Sha512 => totp_lite::totp_custom::<totp_lite::Sha512>(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
|
||||||
|
|
|
@ -44,7 +44,7 @@ pub enum Format {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The vault struct
|
/// The vault struct
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, Parser)]
|
#[derive(Clone, Debug, Deserialize, Serialize, Parser, Eq, PartialEq)]
|
||||||
pub struct Vault {
|
pub struct Vault {
|
||||||
/// The name of the vault
|
/// The name of the vault
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
Loading…
Reference in a new issue