pashage

Yet Another Opinionated Re-engineering of the Unix Password Store
git clone https://git.instinctive.eu/pashage.git
Log | Files | Refs | README | LICENSE

commit ac9120660432d96504a818249a9a9b60906ef885
parent 6a286d4509cad4e0658b9e3a51c8ce2470faa777
Author: Natasha Kerensikova <natgh@instinctive.eu>
Date:   Sat, 16 Nov 2024 07:30:54 +0000

Generate command optionally asks for confirmation before saving
Diffstat:
MREADME.md | 19++++++++++++-------
Mspec/action_spec.sh | 43+++++++++++++++++++++++++++++++++++++++++++
Mspec/pashage_extra_spec.sh | 38++++++++++++++++++++++++++++++++++++++
Mspec/usage_spec.sh | 53++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/pashage.sh | 28++++++++++++++++++++++++----
5 files changed, 169 insertions(+), 12 deletions(-)

diff --git a/README.md b/README.md @@ -81,6 +81,10 @@ passwords. - The `generate` command has a new command-line argument to specify explicitly the character set. +- The `generate` command optionally asks for confirmation before storing +the generated secret (e.g. for iterative attempts against stupid password +rules) + - The `init` command has new flags to control re-encryption (never or ask for each file). @@ -102,8 +106,6 @@ The following features are currently under consideration: - partial display of secrets on standard output - successive clipboard copy of several lines from a single decryption (e.g. username then password) -- optional interactive confirmation between generation and encryption -(e.g. for iterative attempts against stupid password rules) - OTP support - maybe extension support? @@ -274,7 +276,8 @@ Environment: ``` pashage generate [--no-symbols,-n] [--clip,-c | --qrcode,-q] - [--in-place,-i | --force,-f] pass-name [pass-length] + [--in-place,-i | --force,-f] [--try,-t] + pass-name [pass-length [character-set]] ``` This subcommand generates a new secret from `/dev/urandom`, stores it in @@ -291,6 +294,8 @@ Flags: characters - `-q` or `--qrcode`: display the secret as a QR-code instead of using the standard output +- `-t` or `--try`: display the secret and ask for confirmation before + storing it into the database Environment: - `CLICOLOR`: when set to a non-empty value, use ANSI escape sequences to @@ -305,10 +310,10 @@ Environment: when `PASHAGE_DIR` is unset - `PASSAGE_IDENTITIES_FILE`: _identity_ file to use instead of `~/.passage/identities` when `PASHAGE_IDENTITIES_FILE` is unset -- `PASSWORD_STORE_CHARACTER_SET_NO_SYMBOLS`: character set to use with - `tr(1)` when `-n` is specified, instead of `[:alnum:]` -- `PASSWORD_STORE_CHARACTER_SET`: character set to use with `tr(1)` when - `-n` is not specified, instead of `[:punct:][:alnum:]` +- `PASSWORD_STORE_CHARACTER_SET_NO_SYMBOLS`: default character set to use + with `tr(1)` when `-n` is specified, instead of `[:alnum:]` +- `PASSWORD_STORE_CHARACTER_SET`: default character set to use with `tr(1)` + when `-n` is not specified, instead of `[:punct:][:alnum:]` - `PASSWORD_STORE_CLIP_TIME`: number of second before clearing the clipboard when `-c` is used, instead of 45 - `PASSWORD_STORE_DIR`: database directory to use instead of diff --git a/spec/action_spec.sh b/spec/action_spec.sh @@ -1032,6 +1032,7 @@ Describe 'Action Functions' End Describe 'do_generate' + DECISION=default PREFIX="${SHELLSPEC_WORKDIR}/prefix" SHOW=none @@ -1253,6 +1254,48 @@ Describe 'Action Functions' The output should equal 'Decrypting previous secret for existing' The error should equal "$(result)" End + + It 'saves the password after showing it and getting confirmation' + DECISION=interactive + yesno() { + mocklog yesno "$@" + ANSWER=y + } + result(){ + %text:expand + #|$ do_show sub/new + #|> 0123456789 + #|$ yesno Save generated password for sub/new? + #|$ scm_begin + #|$ mkdir -p -- ${PREFIX}/sub + #|$ do_encrypt sub/new.age + #|> 0123456789 + #|$ scm_add ${PREFIX}/sub/new.age + #|$ scm_commit Add generated password for sub/new. + } + When call do_generate sub/new 10 '[alnum:]' + The status should be success + The output should be blank + The error should equal "$(result)" + End + + It 'does not save the password after showing it and getting cancellation' + DECISION=interactive + yesno() { + mocklog yesno "$@" + ANSWER=n + } + result(){ + %text:expand + #|$ do_show sub/new + #|> 0123456789 + #|$ yesno Save generated password for sub/new? + } + When call do_generate sub/new 10 '[alnum:]' + The status should be success + The output should be blank + The error should equal "$(result)" + End End Describe 'do_grep' diff --git a/spec/pashage_extra_spec.sh b/spec/pashage_extra_spec.sh @@ -612,6 +612,7 @@ Describe 'Integrated Command Functions' End Describe 'cmd_generate' + DECISION=default MULTILINE=no OVERWRITE=no SHOW=text @@ -688,6 +689,42 @@ Describe 'Integrated Command Functions' The output should equal 'Decrypting previous secret for stale' The result of function check_git_log should be successful End + + It 'saves after showing and getting confirmation' + Data 'y' + When call cmd_generate --try new + The status should be success + The error should be blank + The file "${PREFIX}/new.age" should be exist + expected_out() { + %putsn '(B)The generated password for (U)new(!U) is:(N)' + @sed '$s/^age://p;d' "${PREFIX}/new.age" + %putsn 'Save generated password for new? [y/n]' + } + The output should equal "$(expected_out)" + expected_log() { %text + #|Add generated password for new. + #| + #| new.age | 2 ++ + #| 1 file changed, 2 insertions(+) + setup_log + } + The result of function check_git_log should be successful + End + + It 'does not save after showing and getting cancellation' + Data 'n' + When call cmd_generate --try new 5 '[:lower:]' + The status should be success + The error should be blank + The lines of output should equal 3 + The line 1 of output should \ + equal '(B)The generated password for (U)new(!U) is:(N)' + The line 2 of output should match pattern '[a-z][a-z][a-z][a-z][a-z]' + The line 3 of output should \ + equal 'Save generated password for new? [y/n]' + The result of function check_git_log should be successful + End End Describe 'cmd_git' @@ -1509,6 +1546,7 @@ Describe 'Integrated Command Functions' # 'find' does not change the repository Example 'generate' + DECISION=default MULTILINE=no OVERWRITE=no When run cmd_generate new-pass diff --git a/spec/usage_spec.sh b/spec/usage_spec.sh @@ -96,6 +96,7 @@ Describe 'Command-Line Parsing' do_generate() { mocklog do_generate "$@" %text:expand >&2 + #|DECISION=${DECISION} #|MULTILINE=${MULTILINE} #|OVERWRITE=${OVERWRITE} #|SELECTED_LINE=${SELECTED_LINE} @@ -626,7 +627,7 @@ Describe 'Command-Line Parsing' usage_text() { %text #|Usage: prg generate [--no-symbols,-n] [--clip,-c | --qrcode,-q] - #| [--in-place,-i | --force,-f] + #| [--in-place,-i | --force,-f] [--try,-t] #| pass-name [pass-length [character-set]] } @@ -635,6 +636,7 @@ Describe 'Command-Line Parsing' %text #|$ check_sneaky_path secret #|$ do_generate secret 25 [:punct:][:alnum:] + #|DECISION=default #|MULTILINE=no #|OVERWRITE=no #|SELECTED_LINE=1 @@ -651,6 +653,7 @@ Describe 'Command-Line Parsing' %text #|$ check_sneaky_path secret #|$ do_generate secret 12 [:punct:][:alnum:] + #|DECISION=default #|MULTILINE=no #|OVERWRITE=no #|SELECTED_LINE=1 @@ -667,6 +670,7 @@ Describe 'Command-Line Parsing' %text #|$ check_sneaky_path secret #|$ do_generate secret 12 [A-Z] + #|DECISION=default #|MULTILINE=no #|OVERWRITE=no #|SELECTED_LINE=1 @@ -683,6 +687,7 @@ Describe 'Command-Line Parsing' %text #|$ check_sneaky_path -f #|$ do_generate -f 25 [:punct:][:alnum:] + #|DECISION=default #|MULTILINE=no #|OVERWRITE=no #|SELECTED_LINE=1 @@ -699,6 +704,7 @@ Describe 'Command-Line Parsing' %text #|$ check_sneaky_path secret #|$ do_generate secret 25 [:punct:][:alnum:] + #|DECISION=default #|MULTILINE=no #|OVERWRITE=no #|SELECTED_LINE=1 @@ -715,6 +721,7 @@ Describe 'Command-Line Parsing' %text #|$ check_sneaky_path secret #|$ do_generate secret 25 [:punct:][:alnum:] + #|DECISION=default #|MULTILINE=no #|OVERWRITE=no #|SELECTED_LINE=1 @@ -731,6 +738,7 @@ Describe 'Command-Line Parsing' %text #|$ check_sneaky_path secret #|$ do_generate secret 25 [:punct:][:alnum:] + #|DECISION=default #|MULTILINE=no #|OVERWRITE=no #|SELECTED_LINE=1 @@ -747,6 +755,7 @@ Describe 'Command-Line Parsing' %text #|$ check_sneaky_path secret #|$ do_generate secret 25 [:punct:][:alnum:] + #|DECISION=default #|MULTILINE=no #|OVERWRITE=no #|SELECTED_LINE=1 @@ -763,6 +772,7 @@ Describe 'Command-Line Parsing' %text #|$ check_sneaky_path secret #|$ do_generate secret 25 [:alnum:] + #|DECISION=default #|MULTILINE=no #|OVERWRITE=no #|SELECTED_LINE=1 @@ -779,6 +789,7 @@ Describe 'Command-Line Parsing' %text #|$ check_sneaky_path secret #|$ do_generate secret 25 [:alnum:] + #|DECISION=default #|MULTILINE=no #|OVERWRITE=no #|SELECTED_LINE=1 @@ -795,6 +806,7 @@ Describe 'Command-Line Parsing' %text #|$ check_sneaky_path secret #|$ do_generate secret 25 [:alnum:] + #|DECISION=default #|MULTILINE=no #|OVERWRITE=no #|SELECTED_LINE=1 @@ -811,6 +823,7 @@ Describe 'Command-Line Parsing' %text #|$ check_sneaky_path secret #|$ do_generate secret 25 [:alnum:] + #|DECISION=default #|MULTILINE=no #|OVERWRITE=no #|SELECTED_LINE=1 @@ -827,6 +840,7 @@ Describe 'Command-Line Parsing' %text #|$ check_sneaky_path secret #|$ do_generate secret 25 [:punct:][:alnum:] + #|DECISION=default #|MULTILINE=yes #|OVERWRITE=no #|SELECTED_LINE=1 @@ -843,6 +857,7 @@ Describe 'Command-Line Parsing' %text #|$ check_sneaky_path secret #|$ do_generate secret 25 [:punct:][:alnum:] + #|DECISION=default #|MULTILINE=yes #|OVERWRITE=no #|SELECTED_LINE=1 @@ -859,6 +874,7 @@ Describe 'Command-Line Parsing' %text #|$ check_sneaky_path secret #|$ do_generate secret 25 [:punct:][:alnum:] + #|DECISION=default #|MULTILINE=no #|OVERWRITE=yes #|SELECTED_LINE=1 @@ -875,6 +891,7 @@ Describe 'Command-Line Parsing' %text #|$ check_sneaky_path secret #|$ do_generate secret 25 [:punct:][:alnum:] + #|DECISION=default #|MULTILINE=no #|OVERWRITE=yes #|SELECTED_LINE=1 @@ -886,6 +903,40 @@ Describe 'Command-Line Parsing' The error should equal "$(result)" End + It 'asks for confirmation before saving the generated password (long)' + result() { + %text + #|$ check_sneaky_path secret + #|$ do_generate secret 25 [:punct:][:alnum:] + #|DECISION=interactive + #|MULTILINE=no + #|OVERWRITE=no + #|SELECTED_LINE=1 + #|SHOW=text + } + When call cmd_generate --try secret + The status should be success + The output should be blank + The error should equal "$(result)" + End + + It 'asks for confirmation before saving the generated password (short)' + result() { + %text + #|$ check_sneaky_path secret + #|$ do_generate secret 25 [:punct:][:alnum:] + #|DECISION=interactive + #|MULTILINE=no + #|OVERWRITE=no + #|SELECTED_LINE=1 + #|SHOW=text + } + When call cmd_generate -t secret + The status should be success + The output should be blank + The error should equal "$(result)" + End + It 'reports incompatible generation long options' cat() { @cat; } When run cmd_generate --in-place --force secret diff --git a/src/pashage.sh b/src/pashage.sh @@ -610,6 +610,7 @@ do_encrypt() { # $1: secret name # $2: new password length # $3: new password charset +# DECISION: when interactive, show-ask-commit instead of commit-show # MULTILINE: whether to re-use existing secret data # OVERWRITE: whether to overwrite without confirmation do_generate() { @@ -622,6 +623,20 @@ do_generate() { fi unset NEW_PASS_LEN + if [ "${DECISION}" = interactive ]; then + do_generate_show "$@" + yesno "Save generated password for $1?" + [ "${ANSWER}" = y ] && do_generate_commit "$@" + else + do_generate_commit "$@" + [ "${ANSWER-y}" = y ] && do_generate_show "$@" + fi + + unset NEW_PASS +} + +# SCM-committing part of do_generate +do_generate_commit() { scm_begin mkdir -p -- "$(dirname "${PREFIX}/$1.age")" @@ -670,7 +685,10 @@ do_generate() { scm_commit "${VERB} generated password for $1." unset VERB +} +# Showing part of do_generate +do_generate_show() { if [ "${SHOW}" = text ]; then printf '%sThe generated password for %s%s%s is:%s\n' \ "${BOLD_TEXT}" \ @@ -683,8 +701,6 @@ do_generate() { do_show "$1" <<-EOF ${NEW_PASS} EOF - - unset NEW_PASS } # Recursively grep decrypted secrets in current directory @@ -1222,7 +1238,10 @@ cmd_generate() { fi SHOW=qrcode shift ;; - -[cfinq]?*) + -t|--try) + DECISION=interactive + shift ;; + -[cfinqt]?*) REST="${1#-?}" ARG="${1%"${REST}"}" shift @@ -1664,7 +1683,7 @@ EOF generate) cat <<EOF ${F}${PROGRAM} generate [--no-symbols,-n] [--clip,-c | --qrcode,-q] -${I}${BLANKPG} [--in-place,-i | --force,-f] +${I}${BLANKPG} [--in-place,-i | --force,-f] [--try,-t] ${I}${BLANKPG} pass-name [pass-length [character-set]] EOF [ "${VERBOSE}" = yes ] && cat <<EOF @@ -1675,6 +1694,7 @@ ${I} or display it as a QR-code. ${I} Prompt before overwriting existing password unless forced. ${I} Optionally replace only the first line of an existing file ${I} with a new password. +${I} Optionally prompt for confirmation between generation and saving. EOF ;; git)