lockbox

password manager
Log | Files | Refs | README | LICENSE

commit 54a6f8b24311d4760ffb22210c323a605f0bcc02
parent 539bb4bbaf05c7e03b8adfbec1506a5407ffc866
Author: Sean Enck <sean@ttypty.com>
Date:   Sat,  7 Jun 2025 19:33:30 -0400

include a TOTP url output command

Diffstat:
Mcmd/lb/main_test.go | 1+
Mcmd/lb/tests/expected.log | 5+++++
Minternal/app/commands/core.go | 3++-
Minternal/app/totp.go | 16++++++++++++++--
Minternal/app/totp_test.go | 18+++++++++++++++++-
5 files changed, 39 insertions(+), 4 deletions(-)

diff --git a/cmd/lb/main_test.go b/cmd/lb/main_test.go @@ -226,6 +226,7 @@ func test(profile string) error { r.run("", "totp show test6/multiline/otp") r.run("", "totp once test6/multiline/otp") r.run("", "totp minimal test6/multiline/otp") + r.run("", "totp url 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 @@ -117,6 +117,11 @@ test7/deeper/rooted/otp XXXXXX XXXXXX XXXXXX +url: otpauth://totp/lbissuer:lbaccount?algorithm=SHA1&digits=6&issuer=lbissuer&period=30&secret=5ae472abqdekjqykoyxk7hvc2leklq5n +seed: 5ae472abqdekjqykoyxk7hvc2leklq5n +digits: 6 +algorithm: SHA1 +period: 30 "test1/key1": { "modtime": "XXXX-XX-XX", "password": "521b9ccefbcd14d179e7a1bb877752870a6d620938b28a66a107eac6e6805b9d0989f45b5730508041aa5e710847d439ea74cd312c9355f1f2dae08d40e41d50" diff --git a/internal/app/commands/core.go b/internal/app/commands/core.go @@ -55,7 +55,8 @@ const ( // Unset indicates a value should be unset (removed) from an entity Unset = "unset" // Groups handles getting a list of groups - Groups = "groups" + Groups = "groups" + TOTPURL = "url" ) var ( diff --git a/internal/app/totp.go b/internal/app/totp.go @@ -13,8 +13,8 @@ import ( otp "github.com/pquerna/otp/totp" "git.sr.ht/~enckse/lockbox/internal/app/commands" - "git.sr.ht/~enckse/lockbox/internal/kdbx" "git.sr.ht/~enckse/lockbox/internal/config" + "git.sr.ht/~enckse/lockbox/internal/kdbx" "git.sr.ht/~enckse/lockbox/internal/platform/clip" ) @@ -59,6 +59,8 @@ const ( ListTOTPMode // OnceTOTPMode will only show the token once and exit OnceTOTPMode + // URLTOTPMode will dump the URL details + URLTOTPMode ) // NewDefaultTOTPOptions gets the default option set @@ -89,7 +91,7 @@ func (w totpWrapper) generateCode() (string, error) { func (args *TOTPArguments) display(opts TOTPOptions) error { interactive := opts.IsInteractive() - if args.Mode == MinimalTOTPMode { + if args.Mode == MinimalTOTPMode || args.Mode == URLTOTPMode { interactive = false } once := args.Mode == OnceTOTPMode @@ -115,6 +117,14 @@ 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 { + 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) + fmt.Fprintf(writer, "algorithm: %s\n", wrapper.opts.Algorithm) + fmt.Fprintf(writer, "period: %d\n", wrapper.opts.Period) + return nil + } if !interactive { code, err := wrapper.generateCode() if err != nil { @@ -243,6 +253,8 @@ func NewTOTPArguments(args []string) (*TOTPArguments, error) { } } opts.Mode = ListTOTPMode + case commands.TOTPURL: + opts.Mode = URLTOTPMode case commands.TOTPShow: opts.Mode = ShowTOTPMode case commands.TOTPClip: diff --git a/internal/app/totp_test.go b/internal/app/totp_test.go @@ -8,8 +8,8 @@ import ( "testing" "git.sr.ht/~enckse/lockbox/internal/app" - "git.sr.ht/~enckse/lockbox/internal/kdbx" "git.sr.ht/~enckse/lockbox/internal/config/store" + "git.sr.ht/~enckse/lockbox/internal/kdbx" ) type ( @@ -116,6 +116,10 @@ func TestNewTOTPArguments(t *testing.T) { if args.Mode != app.OnceTOTPMode || args.Entry != "test" { t.Error("invalid args") } + args, _ = app.NewTOTPArguments([]string{"url", "test"}) + if args.Mode != app.URLTOTPMode || args.Entry != "test" { + t.Error("invalid args") + } } func TestDoErrors(t *testing.T) { @@ -185,6 +189,18 @@ func TestNonListError(t *testing.T) { } } +func TestURL(t *testing.T) { + setupTOTP(t) + args, _ := app.NewTOTPArguments([]string{"url", "test/test3/totp/otp"}) + m, opts := newMock(t) + if err := args.Do(opts); err != nil { + t.Errorf("invalid error: %v", err) + } + if !strings.Contains(m.buf.String(), "url") { + t.Errorf("invalid short: %s", m.buf.String()) + } +} + func TestMinimal(t *testing.T) { setupTOTP(t) args, _ := app.NewTOTPArguments([]string{"minimal", "test/test3/totp/otp"})