lockbox

password manager
Log | Files | Refs | README | LICENSE

commit cb17aeefad0a95a2c94825ba33a87ef7b982352a
parent 54a6f8b24311d4760ffb22210c323a605f0bcc02
Author: Sean Enck <sean@ttypty.com>
Date:   Sat,  7 Jun 2025 19:41:26 -0400

seed and url are both new commands

Diffstat:
Mcmd/lb/main_test.go | 1+
Mcmd/lb/tests/expected.log | 1+
Minternal/app/commands/core.go | 5+++--
Minternal/app/completions/core.go | 2+-
Minternal/app/help/core.go | 2++
Minternal/app/help/core_test.go | 4++--
Minternal/app/totp.go | 11+++++++++--
Minternal/app/totp_test.go | 18+++++++++++++++++-
8 files changed, 36 insertions(+), 8 deletions(-)

diff --git a/cmd/lb/main_test.go b/cmd/lb/main_test.go @@ -227,6 +227,7 @@ func test(profile string) error { r.run("", "totp once test6/multiline/otp") r.run("", "totp minimal test6/multiline/otp") r.run("", "totp url test6/multiline/otp") + r.run("", "totp seed test6/multiline/otp") r.run("", fmt.Sprintf("conv \"%s\"", r.store)) r.run("echo y |", "rm test7/deeper") r.run("echo y |", "rm test7/deeper/ro") diff --git a/cmd/lb/tests/expected.log b/cmd/lb/tests/expected.log @@ -122,6 +122,7 @@ seed: 5ae472abqdekjqykoyxk7hvc2leklq5n digits: 6 algorithm: SHA1 period: 30 +5ae472abqdekjqykoyxk7hvc2leklq5n "test1/key1": { "modtime": "XXXX-XX-XX", "password": "521b9ccefbcd14d179e7a1bb877752870a6d620938b28a66a107eac6e6805b9d0989f45b5730508041aa5e710847d439ea74cd312c9355f1f2dae08d40e41d50" diff --git a/internal/app/commands/core.go b/internal/app/commands/core.go @@ -55,8 +55,9 @@ const ( // Unset indicates a value should be unset (removed) from an entity Unset = "unset" // Groups handles getting a list of groups - Groups = "groups" - TOTPURL = "url" + Groups = "groups" + TOTPURL = "url" + TOTPSeed = "seed" ) var ( diff --git a/internal/app/completions/core.go b/internal/app/completions/core.go @@ -129,7 +129,7 @@ func Generate(completionType, exe string) ([]string, error) { commands.Insert: c.Conditionals.Not.ReadOnly, commands.Unset: c.Conditionals.Not.ReadOnly, }) - c.TOTPSubCommands = c.newGenOptions([]string{commands.TOTPMinimal, commands.TOTPOnce, commands.TOTPShow}, + c.TOTPSubCommands = c.newGenOptions([]string{commands.TOTPMinimal, commands.TOTPOnce, commands.TOTPShow, commands.TOTPURL, commands.TOTPSeed}, map[string]string{ commands.TOTPClip: c.Conditionals.Not.CanClip, }) diff --git a/internal/app/help/core.go b/internal/app/help/core.go @@ -97,6 +97,8 @@ func Usage(verbose bool, exe string) ([]string, error) { results = append(results, subCommand(commands.TOTP, commands.TOTPList, isFilter, "list entries with totp settings")) 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.TOTPURL, isEntry, "display TOTP url information")) + results = append(results, subCommand(commands.TOTP, commands.TOTPSeed, isEntry, "show the TOTP seed (only)")) results = append(results, subCommand(commands.TOTP, commands.TOTPShow, isEntry, "show the totp entry")) results = append(results, command(commands.Version, "", "display version information")) sort.Strings(results) 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) != 25 { + if len(u) != 27 { t.Errorf("invalid usage, out of date? %d", len(u)) } u, _ = help.Usage(true, "lb") - if len(u) != 106 { + if len(u) != 108 { 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 @@ -61,6 +61,7 @@ const ( OnceTOTPMode // URLTOTPMode will dump the URL details URLTOTPMode + SeedTOTPMode ) // NewDefaultTOTPOptions gets the default option set @@ -91,7 +92,7 @@ func (w totpWrapper) generateCode() (string, error) { func (args *TOTPArguments) display(opts TOTPOptions) error { interactive := opts.IsInteractive() - if args.Mode == MinimalTOTPMode || args.Mode == URLTOTPMode { + if args.Mode == MinimalTOTPMode || args.Mode == URLTOTPMode || args.Mode == SeedTOTPMode { interactive = false } once := args.Mode == OnceTOTPMode @@ -117,7 +118,11 @@ func (args *TOTPArguments) display(opts TOTPOptions) error { wrapper.opts.Algorithm = k.Algorithm() wrapper.opts.Period = uint(k.Period()) writer := opts.app.Writer() - if args.Mode == URLTOTPMode { + switch args.Mode { + case SeedTOTPMode: + fmt.Fprintln(writer, wrapper.code) + return nil + case URLTOTPMode: fmt.Fprintf(writer, "url: %s\n", k.URL()) fmt.Fprintf(writer, "seed: %s\n", wrapper.code) fmt.Fprintf(writer, "digits: %s\n", wrapper.opts.Digits) @@ -255,6 +260,8 @@ func NewTOTPArguments(args []string) (*TOTPArguments, error) { opts.Mode = ListTOTPMode case commands.TOTPURL: opts.Mode = URLTOTPMode + case commands.TOTPSeed: + opts.Mode = SeedTOTPMode case commands.TOTPShow: opts.Mode = ShowTOTPMode case commands.TOTPClip: diff --git a/internal/app/totp_test.go b/internal/app/totp_test.go @@ -120,6 +120,10 @@ func TestNewTOTPArguments(t *testing.T) { if args.Mode != app.URLTOTPMode || args.Entry != "test" { t.Error("invalid args") } + args, _ = app.NewTOTPArguments([]string{"seed", "test"}) + if args.Mode != app.SeedTOTPMode || args.Entry != "test" { + t.Error("invalid args") + } } func TestDoErrors(t *testing.T) { @@ -189,6 +193,18 @@ func TestNonListError(t *testing.T) { } } +func TestSeed(t *testing.T) { + setupTOTP(t) + args, _ := app.NewTOTPArguments([]string{"seed", "test/test3/totp/otp"}) + m, opts := newMock(t) + if err := args.Do(opts); err != nil { + t.Errorf("invalid error: %v", err) + } + if m.buf.String() != "5ae472abqdekjqykoyxk7hvc2leklq5n\n" { + t.Errorf("invalid seed: %s", m.buf.String()) + } +} + func TestURL(t *testing.T) { setupTOTP(t) args, _ := app.NewTOTPArguments([]string{"url", "test/test3/totp/otp"}) @@ -197,7 +213,7 @@ func TestURL(t *testing.T) { t.Errorf("invalid error: %v", err) } if !strings.Contains(m.buf.String(), "url") { - t.Errorf("invalid short: %s", m.buf.String()) + t.Errorf("invalid url dump: %s", m.buf.String()) } }