commit b6ebda8d29faa567af5e59f50d398cad77c198d3
parent f808fd7a48ff09a27b4f1a4eecb15b3cdfea6419
Author: Sean Enck <sean@ttypty.com>
Date: Wed, 4 Jun 2025 22:47:06 -0400
support totp find
Diffstat:
11 files changed, 60 insertions(+), 6 deletions(-)
diff --git a/cmd/lb/main_test.go b/cmd/lb/main_test.go
@@ -228,6 +228,7 @@ func test(profile string) error {
r.run("echo 5ae472abqdekjqykoyxk7hvc2leklq5n |", "totp insert test/k")
r.run("echo 5ae472abqdekjqykoyxk7hvc2leklq5n |", "totp insert test/k/totp")
r.run("", "totp ls")
+ r.run("", "totp find test")
r.run("", "totp show test/k")
r.run("", "totp once test/k")
r.run("", "totp minimal test/k")
diff --git a/cmd/lb/tests/expected.log b/cmd/lb/tests/expected.log
@@ -39,6 +39,7 @@ test4
}
test/k
+test/k
XXXXXX
XXXXXX
XXXXXX
diff --git a/internal/app/commands/core.go b/internal/app/commands/core.go
@@ -38,6 +38,8 @@ const (
TOTPMinimal = "minimal"
// TOTPList will list the totp-enabled entries
TOTPList = List
+ // TOTPFind allows a filter search of TOTP listed entries
+ TOTPFind = Find
// TOTPOnce will perform like a normal totp request but not refresh
TOTPOnce = "once"
// CompletionsBash is the command to generate bash completions
diff --git a/internal/app/completions/core.go b/internal/app/completions/core.go
@@ -19,6 +19,7 @@ type (
Template struct {
InsertCommand string
TOTPListCommand string
+ TOTPFindCommand string
RemoveCommand string
ClipCommand string
ShowCommand string
@@ -104,6 +105,7 @@ func Generate(completionType, exe string) ([]string, error) {
InsertCommand: commands.Insert,
RemoveCommand: commands.Remove,
TOTPListCommand: commands.TOTPList,
+ TOTPFindCommand: commands.TOTPFind,
ClipCommand: commands.Clip,
ShowCommand: commands.Show,
MultiLineCommand: commands.MultiLine,
diff --git a/internal/app/completions/shell/bash.sh b/internal/app/completions/shell/bash.sh
@@ -40,6 +40,7 @@ _{{ $.Executable }}() {
;;
"{{ $.TOTPCommand }}")
opts="{{ $.TOTPListCommand }} "
+ opts="{{ $.TOTPFindCommand }} "
{{- range $key, $value := .TOTPSubCommands }}
if {{ $value.Conditional }}; then
opts="$opts {{ $value.Key }}"
diff --git a/internal/app/completions/shell/fish.sh b/internal/app/completions/shell/fish.sh
@@ -24,7 +24,8 @@ function {{ $.Executable }}-completion
end
end
if {{ $.Conditionals.Not.CanTOTP }}
- set -f totps ""
+ set -f totpbase "{{ $.TOTPFindCommand }} {{ $.TOTPListCommand }}"
+ set -f totps " $totpbase"
{{- range $idx, $value := $.TOTPSubCommands }}
{{- if gt $idx 0 }}
set -f totps " $totps"
@@ -35,7 +36,7 @@ function {{ $.Executable }}-completion
{{- end }}
complete -c {{ $.Executable }} -n "__fish_seen_subcommand_from {{ $.TOTPCommand }}; and not __fish_seen_subcommand_from $totps" -a "$totps"
if {{ $.Conditionals.Not.AskMode }}
- complete -c {{ $.Executable }} -n "__fish_seen_subcommand_from {{ $.TOTPCommand }}; and __fish_seen_subcommand_from $totps; and test (count (commandline -opc)) -lt 4" -a "({{ $.DoTOTPList }})"
+ complete -c {{ $.Executable }} -n "__fish_seen_subcommand_from {{ $.TOTPCommand }}; and __fish_seen_subcommand_from $totps; and not __fish_seen_subcommand_from $totpbase; and test (count (commandline -opc)) -lt 4" -a "({{ $.DoTOTPList }})"
end
end
if {{ $.Conditionals.Not.CanClip }}
diff --git a/internal/app/completions/shell/zsh.sh b/internal/app/completions/shell/zsh.sh
@@ -65,6 +65,7 @@ _{{ $.Executable }}() {
case "$len" in
3)
compadd "$@" {{ $.TOTPListCommand }}
+ compadd "$@" {{ $.TOTPFindCommand }}
{{- range $key, $value := .TOTPSubCommands }}
if {{ $value.Conditional }}; then
compadd "$@" {{ $value.Key }}
diff --git a/internal/app/help/core.go b/internal/app/help/core.go
@@ -107,6 +107,7 @@ func Usage(verbose bool, exe string) ([]string, error) {
results = append(results, subCommand(commands.TOTP, commands.TOTPOnce, isEntry, "display the first generated code"))
results = append(results, subCommand(commands.TOTP, commands.TOTPMinimal, isEntry, "display one generated code (no details)"))
results = append(results, subCommand(commands.TOTP, commands.TOTPShow, isEntry, "show the totp entry"))
+ results = append(results, subCommand(commands.TOTP, commands.TOTPFind, isFilter, "find matching entries with totp settings"))
results = append(results, command(commands.Version, "", "display version information"))
sort.Strings(results)
usage := []string{fmt.Sprintf("%s usage:", exe)}
diff --git a/internal/app/help/core_test.go b/internal/app/help/core_test.go
@@ -9,11 +9,11 @@ import (
func TestUsage(t *testing.T) {
u, _ := help.Usage(false, "lb")
- if len(u) != 28 {
+ if len(u) != 29 {
t.Errorf("invalid usage, out of date? %d", len(u))
}
u, _ = help.Usage(true, "lb")
- if len(u) != 102 {
+ if len(u) != 103 {
t.Errorf("invalid verbose usage, out of date? %d", len(u))
}
for _, usage := range u {
diff --git a/internal/app/totp.go b/internal/app/totp.go
@@ -4,6 +4,7 @@ package app
import (
"errors"
"fmt"
+ "regexp"
"slices"
"strconv"
"strings"
@@ -60,6 +61,8 @@ const (
MinimalTOTPMode
// ListTOTPMode lists the available tokens
ListTOTPMode
+ // FindTOTPMode is list but with a regexp filter
+ FindTOTPMode
// OnceTOTPMode will only show the token once and exit
OnceTOTPMode
)
@@ -222,17 +225,32 @@ func (args *TOTPArguments) Do(opts TOTPOptions) error {
if !opts.CanTOTP() {
return ErrNoTOTP
}
- if args.Mode == ListTOTPMode {
+ if args.Mode == ListTOTPMode || args.Mode == FindTOTPMode {
e, err := opts.app.Transaction().QueryCallback(backend.QueryOptions{Mode: backend.SuffixMode, Criteria: backend.NewSuffix(args.token)})
if err != nil {
return err
}
writer := opts.app.Writer()
+ printer := func(entity backend.Entity) {
+ fmt.Fprintf(writer, "%s\n", entity.Directory())
+ }
+ filter := printer
+ if args.Mode == FindTOTPMode {
+ re, err := regexp.Compile(args.Entry)
+ if err != nil {
+ return err
+ }
+ filter = func(entity backend.Entity) {
+ if re.MatchString(entity.Path) {
+ printer(entity)
+ }
+ }
+ }
for entry, err := range e {
if err != nil {
return err
}
- fmt.Fprintf(writer, "%s\n", entry.Directory())
+ filter(entry)
}
return nil
}
@@ -258,6 +276,8 @@ func NewTOTPArguments(args []string, tokenType string) (*TOTPArguments, error) {
return nil, errors.New("list takes no arguments")
}
opts.Mode = ListTOTPMode
+ case commands.TOTPFind:
+ opts.Mode = FindTOTPMode
case commands.TOTPInsert:
opts.Mode = InsertTOTPMode
case commands.TOTPShow:
diff --git a/internal/app/totp_test.go b/internal/app/totp_test.go
@@ -100,6 +100,10 @@ func TestNewTOTPArguments(t *testing.T) {
if args.Mode != app.ListTOTPMode || args.Entry != "" {
t.Error("invalid args")
}
+ args, _ = app.NewTOTPArguments([]string{"find", "tesst"}, "test")
+ if args.Mode != app.FindTOTPMode || args.Entry == "" {
+ t.Error("invalid args")
+ }
args, _ = app.NewTOTPArguments([]string{"show", "test"}, "test")
if args.Mode != app.ShowTOTPMode || args.Entry != "test" {
t.Error("invalid args")
@@ -268,3 +272,23 @@ func TestParseWindows(t *testing.T) {
t.Errorf("invalid error: %v", err)
}
}
+
+func TestTOTPFind(t *testing.T) {
+ setupTOTP(t)
+ args, _ := app.NewTOTPArguments([]string{"find", "test"}, "totp")
+ m, opts := newMock(t)
+ if err := args.Do(opts); err != nil {
+ t.Errorf("invalid error: %v", err)
+ }
+ if m.buf.String() != "test/test2\ntest/test3\n" {
+ t.Errorf("invalid list: %s", m.buf.String())
+ }
+ m.buf.Reset()
+ args, _ = app.NewTOTPArguments([]string{"find", "[zzzz]"}, "totp")
+ if err := args.Do(opts); err != nil {
+ t.Errorf("invalid error: %v", err)
+ }
+ if m.buf.String() != "" {
+ t.Errorf("invalid list: %s", m.buf.String())
+ }
+}