Compare commits

..

33 commits

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

View file

@ -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

View file

@ -12,5 +12,5 @@ For the location, please provide a link to the code or the documentation.
| Location | Suggestion | | Location | Suggestion |
|----------|------------| |----------|------------|
| Code-link| Suggestion | | location | 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
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:

View file

@ -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

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/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

View file

@ -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
View file

@ -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",

View file

@ -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"]

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,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)

View file

@ -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"

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)
- [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)

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)
- [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)

View file

@ -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

View file

@ -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

View file

@ -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,

View file

@ -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`,

View file

@ -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

View file

@ -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

View file

@ -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>

View file

@ -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
``` ```

View file

@ -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)
})
}

View file

@ -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()

View file

@ -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()
} }

View file

@ -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 {

View file

@ -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 {

View file

@ -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");

View 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()
} }
} }

View file

@ -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}")]

View file

@ -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))
} }

View file

@ -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

View file

@ -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,