commit b98485ce8bb10045f22beb251344a94943ff4d26
parent 36a46c18348c5addaf5c312fa8df4b9859187bba
Author: Sean Enck <sean@ttypty.com>
Date: Mon, 24 Nov 2025 12:48:12 -0500
switch totp providers
Diffstat:
5 files changed, 25 insertions(+), 27 deletions(-)
diff --git a/cmd/lb/tests/expected.log b/cmd/lb/tests/expected.log
@@ -4,7 +4,7 @@ setting up tests
'test3' is not an allowed field name
'' is not an allowed field name
'still' is not an allowed field name
-Decoding of secret as base32 failed.
+illegal base32 data at input byte 16
password can NOT be multi-line
testing5
testing5
diff --git a/go.mod b/go.mod
@@ -4,12 +4,11 @@ go 1.24.4
require (
github.com/BurntSushi/toml v1.5.0
- github.com/pquerna/otp v1.5.0
+ github.com/ja7ad/otp v1.3.3
github.com/tobischo/gokeepasslib/v3 v3.6.1
)
require (
- github.com/boombuler/barcode v1.1.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/tobischo/argon2 v0.1.0 // indirect
golang.org/x/crypto v0.45.0 // indirect
diff --git a/go.sum b/go.sum
@@ -1,19 +1,13 @@
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
-github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
-github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo=
-github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+github.com/ja7ad/otp v1.3.3 h1:C/IbBXLT7BaShm7eicuAtmQaum3+0s2ms3GXCoiivls=
+github.com/ja7ad/otp v1.3.3/go.mod h1:6Da7VUaUpJmzaRz5IZ6v9Yei62eaepYoGCb6jsMDkUs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
-github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tobischo/argon2 v0.1.0 h1:mwAx/9DK/4rP0xzNifb/XMAf43dU3eG1B3aeF88qu4Y=
diff --git a/internal/app/insert_test.go b/internal/app/insert_test.go
@@ -183,7 +183,7 @@ func TestInsertTOTP(t *testing.T) {
}
m.command.buf = bytes.Buffer{}
m.command.args = []string{"test/test2/test1/otp"}
- if err := app.Insert(m); err == nil || err.Error() != "Decoding of secret as base32 failed." {
+ if err := app.Insert(m); err == nil || err.Error() != "illegal base32 data at input byte 1" {
t.Errorf("invalid error: %v", err)
}
store.SetBool("LOCKBOX_TOTP_CHECK_ON_INSERT", false)
diff --git a/internal/app/totp/core.go b/internal/app/totp/core.go
@@ -4,10 +4,10 @@ package totp
import (
"fmt"
"io"
+ "net/url"
"time"
- coreotp "github.com/pquerna/otp"
- otp "github.com/pquerna/otp/totp"
+ "github.com/ja7ad/otp"
"github.com/enckse/lockbox/internal/config"
)
@@ -15,23 +15,23 @@ import (
type (
// Generator is used to generate TOTP codes
Generator struct {
- key *coreotp.Key
secret string
- opts otp.ValidateOpts
+ opts *otp.Param
+ url *url.URL
}
)
// Code will generate a new code for the specified TOTP object
func (g Generator) Code() (string, error) {
- return otp.GenerateCodeCustom(g.secret, time.Now(), g.opts)
+ return otp.GenerateTOTP(g.secret, time.Now(), g.opts)
}
// Print will print information about the generator to the writer
func (g Generator) Print(w io.Writer, details bool) {
if details {
- fmt.Fprintf(w, "url: %s\n", g.key.URL())
+ fmt.Fprintf(w, "url: %s\n", g.url)
fmt.Fprintf(w, "seed: %s\n", g.secret)
- fmt.Fprintf(w, "digits: %s\n", g.opts.Digits)
+ fmt.Fprintf(w, "digits: %d\n", g.opts.Digits)
fmt.Fprintf(w, "algorithm: %s\n", g.opts.Algorithm)
fmt.Fprintf(w, "period: %d\n", g.opts.Period)
return
@@ -40,17 +40,22 @@ func (g Generator) Print(w io.Writer, details bool) {
}
// New will create a new generator
-func New(code string) (Generator, error) {
- k, err := coreotp.NewKeyFromURL(config.EnvTOTPFormat.Get(code))
+func New(input string) (Generator, error) {
+ u, err := url.Parse(config.EnvTOTPFormat.Get(input))
+ if err != nil {
+ return Generator{}, err
+ }
+
+ obj, err := otp.ParseOTPAuthURL(u)
if err != nil {
return Generator{}, err
}
wrapper := Generator{}
- wrapper.secret = k.Secret()
- wrapper.opts = otp.ValidateOpts{}
- wrapper.opts.Digits = k.Digits()
- wrapper.opts.Algorithm = k.Algorithm()
- wrapper.opts.Period = uint(k.Period())
- wrapper.key = k
+ wrapper.secret = obj.Secret
+ wrapper.opts = &otp.Param{}
+ wrapper.opts.Algorithm = obj.Algorithm
+ wrapper.opts.Digits = obj.Digits
+ wrapper.opts.Period = obj.Period
+ wrapper.url = u
return wrapper, nil
}