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