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