diff --git a/go.mod b/go.mod index ffae81424..97a25535a 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,6 @@ require ( github.com/RoaringBitmap/roaring v0.4.23 // indirect github.com/bgentry/speakeasy v0.1.0 // indirect github.com/blevesearch/bleve v1.0.7 - github.com/boombuler/barcode v0.0.0-20161226211916-fe0f26ff6d26 // indirect github.com/couchbase/gomemcached v0.0.0-20191004160342-7b5da2ec40b2 // indirect github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d // indirect github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 // indirect @@ -80,7 +79,7 @@ require ( github.com/oliamb/cutter v0.2.2 github.com/olivere/elastic/v7 v7.0.9 github.com/pkg/errors v0.9.1 - github.com/pquerna/otp v0.0.0-20160912161815-54653902c20e + github.com/pquerna/otp v1.2.0 github.com/prometheus/client_golang v1.1.0 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 // indirect github.com/prometheus/procfs v0.0.4 // indirect diff --git a/go.sum b/go.sum index 61afab822..48df6cbcb 100644 --- a/go.sum +++ b/go.sum @@ -101,8 +101,8 @@ github.com/blevesearch/zap/v11 v11.0.7 h1:nnmAOP6eXBkqEa1Srq1eqA5Wmn4w+BZjLdjynN github.com/blevesearch/zap/v11 v11.0.7/go.mod h1:bJoY56fdU2m/IP4LLz/1h4jY2thBoREvoqbuJ8zhm9k= github.com/blevesearch/zap/v12 v12.0.7 h1:y8FWSAYkdc4p1dn4YLxNNr1dxXlSUsakJh2Fc/r6cj4= github.com/blevesearch/zap/v12 v12.0.7/go.mod h1:70DNK4ZN4tb42LubeDbfpp6xnm8g3ROYVvvZ6pEoXD8= -github.com/boombuler/barcode v0.0.0-20161226211916-fe0f26ff6d26 h1:NGpwhs9FOwddM6TptNrq2ycby4s24TcppSe5uG4DA/Q= -github.com/boombuler/barcode v0.0.0-20161226211916-fe0f26ff6d26/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668 h1:U/lr3Dgy4WK+hNk4tyD+nuGjpVLPEHuJSFXMw11/HPA= github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -520,8 +520,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE 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/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= -github.com/pquerna/otp v0.0.0-20160912161815-54653902c20e h1:ApqncJ84HYN8x8x5WV1T1YWDuPRF/0aXZhr91LnRMCQ= -github.com/pquerna/otp v0.0.0-20160912161815-54653902c20e/go.mod h1:Zad1CMQfSQZI5KLpahDiSUX4tMMREnXw98IvL1nhgMk= +github.com/pquerna/otp v1.2.0 h1:/A3+Jn+cagqayeR3iHs/L62m5ue7710D35zl1zJ1kok= +github.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= diff --git a/vendor/github.com/boombuler/barcode/.gitignore b/vendor/github.com/boombuler/barcode/.gitignore new file mode 100644 index 000000000..1d74e2196 --- /dev/null +++ b/vendor/github.com/boombuler/barcode/.gitignore @@ -0,0 +1 @@ +.vscode/ diff --git a/vendor/github.com/boombuler/barcode/README.md b/vendor/github.com/boombuler/barcode/README.md index 85c34d639..2a988db39 100644 --- a/vendor/github.com/boombuler/barcode/README.md +++ b/vendor/github.com/boombuler/barcode/README.md @@ -1,18 +1,53 @@ -##Introduction## +[![Join the chat at https://gitter.im/golang-barcode/Lobby](https://badges.gitter.im/golang-barcode/Lobby.svg)](https://gitter.im/golang-barcode/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +## Introduction ## + This is a package for GO which can be used to create different types of barcodes. -##Supported Barcode Types## +## Supported Barcode Types ## +* 2 of 5 * Aztec Code * Codabar * Code 128 * Code 39 -* EAN 8 -* EAN 13 +* Code 93 * Datamatrix -* QR Codes -* 2 of 5 +* EAN 13 +* EAN 8 +* PDF 417 +* QR Code + +## Example ## + +This is a simple example on how to create a QR-Code and write it to a png-file +```go +package main + +import ( + "image/png" + "os" + + "github.com/boombuler/barcode" + "github.com/boombuler/barcode/qr" +) + +func main() { + // Create the barcode + qrCode, _ := qr.Encode("Hello World", qr.M, qr.Auto) + + // Scale the barcode to 200x200 pixels + qrCode, _ = barcode.Scale(qrCode, 200, 200) + + // create the output file + file, _ := os.Create("qrcode.png") + defer file.Close() + + // encode the barcode as png + png.Encode(file, qrCode) +} +``` -##Documentation## +## Documentation ## See [GoDoc](https://godoc.org/github.com/boombuler/barcode) To create a barcode use the Encode function from one of the subpackages. diff --git a/vendor/github.com/boombuler/barcode/barcode.go b/vendor/github.com/boombuler/barcode/barcode.go index 7e147f22f..25f4a693d 100644 --- a/vendor/github.com/boombuler/barcode/barcode.go +++ b/vendor/github.com/boombuler/barcode/barcode.go @@ -2,6 +2,21 @@ package barcode import "image" +const ( + TypeAztec = "Aztec" + TypeCodabar = "Codabar" + TypeCode128 = "Code 128" + TypeCode39 = "Code 39" + TypeCode93 = "Code 93" + TypeDataMatrix = "DataMatrix" + TypeEAN8 = "EAN 8" + TypeEAN13 = "EAN 13" + TypePDF = "PDF417" + TypeQR = "QR Code" + Type2of5 = "2 of 5" + Type2of5Interleaved = "2 of 5 (interleaved)" +) + // Contains some meta information about a barcode type Metadata struct { // the name of the barcode kind @@ -17,5 +32,11 @@ type Barcode interface { Metadata() Metadata // the data that was encoded in this barcode Content() string +} + +// Additional interface that some barcodes might implement to provide +// the value of its checksum. +type BarcodeIntCS interface { + Barcode CheckSum() int } diff --git a/vendor/github.com/boombuler/barcode/go.mod b/vendor/github.com/boombuler/barcode/go.mod new file mode 100644 index 000000000..ed53593b9 --- /dev/null +++ b/vendor/github.com/boombuler/barcode/go.mod @@ -0,0 +1 @@ +module github.com/boombuler/barcode diff --git a/vendor/github.com/boombuler/barcode/qr/qrcode.go b/vendor/github.com/boombuler/barcode/qr/qrcode.go index ab123ce8f..13607604b 100644 --- a/vendor/github.com/boombuler/barcode/qr/qrcode.go +++ b/vendor/github.com/boombuler/barcode/qr/qrcode.go @@ -20,7 +20,7 @@ func (qr *qrcode) Content() string { } func (qr *qrcode) Metadata() barcode.Metadata { - return barcode.Metadata{"QR Code", 2} + return barcode.Metadata{barcode.TypeQR, 2} } func (qr *qrcode) ColorModel() color.Model { @@ -46,10 +46,6 @@ func (qr *qrcode) Set(x, y int, val bool) { qr.data.SetBit(x*qr.dimension+y, val) } -func (qr *qrcode) CheckSum() int { - return 0 -} - func (qr *qrcode) calcPenalty() uint { return qr.calcPenaltyRule1() + qr.calcPenaltyRule2() + qr.calcPenaltyRule3() + qr.calcPenaltyRule4() } diff --git a/vendor/github.com/boombuler/barcode/scaledbarcode.go b/vendor/github.com/boombuler/barcode/scaledbarcode.go index c59c9fe7b..152b18017 100644 --- a/vendor/github.com/boombuler/barcode/scaledbarcode.go +++ b/vendor/github.com/boombuler/barcode/scaledbarcode.go @@ -16,6 +16,10 @@ type scaledBarcode struct { rect image.Rectangle } +type intCSscaledBC struct { + scaledBarcode +} + func (bc *scaledBarcode) Content() string { return bc.wrapped.Content() } @@ -36,8 +40,11 @@ func (bc *scaledBarcode) At(x, y int) color.Color { return bc.wrapperFunc(x, y) } -func (bc *scaledBarcode) CheckSum() int { - return bc.wrapped.CheckSum() +func (bc *intCSscaledBC) CheckSum() int { + if cs, ok := bc.wrapped.(BarcodeIntCS); ok { + return cs.CheckSum() + } + return 0 } // Scale returns a resized barcode with the given width and height. @@ -52,6 +59,19 @@ func Scale(bc Barcode, width, height int) (Barcode, error) { return nil, errors.New("unsupported barcode format") } +func newScaledBC(wrapped Barcode, wrapperFunc wrapFunc, rect image.Rectangle) Barcode { + result := &scaledBarcode{ + wrapped: wrapped, + wrapperFunc: wrapperFunc, + rect: rect, + } + + if _, ok := wrapped.(BarcodeIntCS); ok { + return &intCSscaledBC{*result} + } + return result +} + func scale2DCode(bc Barcode, width, height int) (Barcode, error) { orgBounds := bc.Bounds() orgWidth := orgBounds.Max.X - orgBounds.Min.X @@ -59,7 +79,7 @@ func scale2DCode(bc Barcode, width, height int) (Barcode, error) { factor := int(math.Min(float64(width)/float64(orgWidth), float64(height)/float64(orgHeight))) if factor <= 0 { - return nil, fmt.Errorf("can not scale barcode to an image smaller then %dx%d", orgWidth, orgHeight) + return nil, fmt.Errorf("can not scale barcode to an image smaller than %dx%d", orgWidth, orgHeight) } offsetX := (width - (orgWidth * factor)) / 2 @@ -77,11 +97,11 @@ func scale2DCode(bc Barcode, width, height int) (Barcode, error) { return bc.At(x, y) } - return &scaledBarcode{ + return newScaledBC( bc, wrap, image.Rect(0, 0, width, height), - }, nil + ), nil } func scale1DCode(bc Barcode, width, height int) (Barcode, error) { @@ -90,7 +110,7 @@ func scale1DCode(bc Barcode, width, height int) (Barcode, error) { factor := int(float64(width) / float64(orgWidth)) if factor <= 0 { - return nil, fmt.Errorf("can not scale barcode to an image smaller then %dx1", orgWidth) + return nil, fmt.Errorf("can not scale barcode to an image smaller than %dx1", orgWidth) } offsetX := (width - (orgWidth * factor)) / 2 @@ -106,10 +126,9 @@ func scale1DCode(bc Barcode, width, height int) (Barcode, error) { return bc.At(x, 0) } - return &scaledBarcode{ + return newScaledBC( bc, wrap, image.Rect(0, 0, width, height), - }, nil - + ), nil } diff --git a/vendor/github.com/boombuler/barcode/utils/base1dcode.go b/vendor/github.com/boombuler/barcode/utils/base1dcode.go index 22a2e3020..a335c0c74 100644 --- a/vendor/github.com/boombuler/barcode/utils/base1dcode.go +++ b/vendor/github.com/boombuler/barcode/utils/base1dcode.go @@ -10,8 +10,12 @@ import ( type base1DCode struct { *BitList - kind string - content string + kind string + content string +} + +type base1DCodeIntCS struct { + base1DCode checksum int } @@ -38,11 +42,16 @@ func (c *base1DCode) At(x, y int) color.Color { return color.White } -func (c *base1DCode) CheckSum() int { +func (c *base1DCodeIntCS) CheckSum() int { return c.checksum } +// New1DCodeIntCheckSum creates a new 1D barcode where the bars are represented by the bits in the bars BitList +func New1DCodeIntCheckSum(codeKind, content string, bars *BitList, checksum int) barcode.BarcodeIntCS { + return &base1DCodeIntCS{base1DCode{bars, codeKind, content}, checksum} +} + // New1DCode creates a new 1D barcode where the bars are represented by the bits in the bars BitList -func New1DCode(codeKind, content string, bars *BitList, checksum int) barcode.Barcode { - return &base1DCode{bars, codeKind, content, checksum} +func New1DCode(codeKind, content string, bars *BitList) barcode.Barcode { + return &base1DCode{bars, codeKind, content} } diff --git a/vendor/github.com/pquerna/otp/.travis.yml b/vendor/github.com/pquerna/otp/.travis.yml index 73d38b192..5a9ed93af 100644 --- a/vendor/github.com/pquerna/otp/.travis.yml +++ b/vendor/github.com/pquerna/otp/.travis.yml @@ -1,6 +1,7 @@ language: go +env: + - GO111MODULE=on + go: - - 1.5 - - 1.6 - - tip + - "1.12" diff --git a/vendor/github.com/pquerna/otp/go.mod b/vendor/github.com/pquerna/otp/go.mod new file mode 100644 index 000000000..77f2d6ccf --- /dev/null +++ b/vendor/github.com/pquerna/otp/go.mod @@ -0,0 +1,8 @@ +module github.com/pquerna/otp + +go 1.12 + +require ( + github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc + github.com/stretchr/testify v1.3.0 +) diff --git a/vendor/github.com/pquerna/otp/go.sum b/vendor/github.com/pquerna/otp/go.sum new file mode 100644 index 000000000..6848b56f6 --- /dev/null +++ b/vendor/github.com/pquerna/otp/go.sum @@ -0,0 +1,11 @@ +github.com/boombuler/barcode v1.0.0 h1:s1TvRnXwL2xJRaccrdcBQMZxq6X7DvsMogtmJeHDdrc= +github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= diff --git a/vendor/github.com/pquerna/otp/hotp/hotp.go b/vendor/github.com/pquerna/otp/hotp/hotp.go index 62cdc87f4..5e99e2218 100644 --- a/vendor/github.com/pquerna/otp/hotp/hotp.go +++ b/vendor/github.com/pquerna/otp/hotp/hotp.go @@ -70,6 +70,17 @@ func GenerateCode(secret string, counter uint64) (string, error) { // GenerateCodeCustom uses a counter and secret value and options struct to // create a passcode. func GenerateCodeCustom(secret string, counter uint64, opts ValidateOpts) (passcode string, err error) { + // As noted in issue #10 and #17 this adds support for TOTP secrets that are + // missing their padding. + secret = strings.TrimSpace(secret) + if n := len(secret) % 8; n != 0 { + secret = secret + strings.Repeat("=", 8-n) + } + + // As noted in issue #24 Google has started producing base32 in lower case, + // but the StdEncoding (and the RFC), expect a dictionary of only upper case letters. + secret = strings.ToUpper(secret) + secretBytes, err := base32.StdEncoding.DecodeString(secret) if err != nil { return "", otp.ErrValidateSecretInvalidBase32 @@ -135,12 +146,16 @@ type GenerateOpts struct { AccountName string // Size in size of the generated Secret. Defaults to 10 bytes. SecretSize uint + // Secret to store. Defaults to a randomly generated secret of SecretSize. You should generally leave this empty. + Secret []byte // Digits to request. Defaults to 6. Digits otp.Digits // Algorithm to use for HMAC. Defaults to SHA1. Algorithm otp.Algorithm } +var b32NoPadding = base32.StdEncoding.WithPadding(base32.NoPadding) + // Generate creates a new HOTP Key. func Generate(opts GenerateOpts) (*otp.Key, error) { // url encode the Issuer/AccountName @@ -156,16 +171,24 @@ func Generate(opts GenerateOpts) (*otp.Key, error) { opts.SecretSize = 10 } + if opts.Digits == 0 { + opts.Digits = otp.DigitsSix + } + // otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example v := url.Values{} - secret := make([]byte, opts.SecretSize) - _, err := rand.Read(secret) - if err != nil { - return nil, err + if len(opts.Secret) != 0 { + v.Set("secret", b32NoPadding.EncodeToString(opts.Secret)) + } else { + secret := make([]byte, opts.SecretSize) + _, err := rand.Read(secret) + if err != nil { + return nil, err + } + v.Set("secret", b32NoPadding.EncodeToString(secret)) } - v.Set("secret", base32.StdEncoding.EncodeToString(secret)) v.Set("issuer", opts.Issuer) v.Set("algorithm", opts.Algorithm.String()) v.Set("digits", opts.Digits.String()) diff --git a/vendor/github.com/pquerna/otp/otp.go b/vendor/github.com/pquerna/otp/otp.go index 1573ab018..5db93029c 100644 --- a/vendor/github.com/pquerna/otp/otp.go +++ b/vendor/github.com/pquerna/otp/otp.go @@ -54,17 +54,19 @@ type Key struct { // NewKeyFromURL creates a new Key from an TOTP or HOTP url. // // The URL format is documented here: -// https://code.google.com/p/google-authenticator/wiki/KeyUriFormat +// https://github.com/google/google-authenticator/wiki/Key-Uri-Format // func NewKeyFromURL(orig string) (*Key, error) { - u, err := url.Parse(orig) + s := strings.TrimSpace(orig) + + u, err := url.Parse(s) if err != nil { return nil, err } return &Key{ - orig: orig, + orig: s, url: u, }, nil } @@ -136,6 +138,11 @@ func (k *Key) Secret() string { return q.Get("secret") } +// URL returns the OTP URL as a string +func (k *Key) URL() string { + return k.url.String() +} + // Algorithm represents the hashing function to use in the HMAC // operation needed for OTPs. type Algorithm int diff --git a/vendor/github.com/pquerna/otp/totp/totp.go b/vendor/github.com/pquerna/otp/totp/totp.go index af5ab8296..b46fa567e 100644 --- a/vendor/github.com/pquerna/otp/totp/totp.go +++ b/vendor/github.com/pquerna/otp/totp/totp.go @@ -134,14 +134,18 @@ type GenerateOpts struct { AccountName string // Number of seconds a TOTP hash is valid for. Defaults to 30 seconds. Period uint - // Size in size of the generated Secret. Defaults to 10 bytes. + // Size in size of the generated Secret. Defaults to 20 bytes. SecretSize uint + // Secret to store. Defaults to a randomly generated secret of SecretSize. You should generally leave this empty. + Secret []byte // Digits to request. Defaults to 6. Digits otp.Digits // Algorithm to use for HMAC. Defaults to SHA1. Algorithm otp.Algorithm } +var b32NoPadding = base32.StdEncoding.WithPadding(base32.NoPadding) + // Generate a new TOTP Key. func Generate(opts GenerateOpts) (*otp.Key, error) { // url encode the Issuer/AccountName @@ -158,7 +162,7 @@ func Generate(opts GenerateOpts) (*otp.Key, error) { } if opts.SecretSize == 0 { - opts.SecretSize = 10 + opts.SecretSize = 20 } if opts.Digits == 0 { @@ -168,13 +172,17 @@ func Generate(opts GenerateOpts) (*otp.Key, error) { // otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example v := url.Values{} - secret := make([]byte, opts.SecretSize) - _, err := rand.Read(secret) - if err != nil { - return nil, err + if len(opts.Secret) != 0 { + v.Set("secret", b32NoPadding.EncodeToString(opts.Secret)) + } else { + secret := make([]byte, opts.SecretSize) + _, err := rand.Read(secret) + if err != nil { + return nil, err + } + v.Set("secret", b32NoPadding.EncodeToString(secret)) } - v.Set("secret", base32.StdEncoding.EncodeToString(secret)) v.Set("issuer", opts.Issuer) v.Set("period", strconv.FormatUint(uint64(opts.Period), 10)) v.Set("algorithm", opts.Algorithm.String()) diff --git a/vendor/modules.txt b/vendor/modules.txt index d17988fb1..503c845f5 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -128,8 +128,7 @@ github.com/blevesearch/snowballstem/english github.com/blevesearch/zap/v11 # github.com/blevesearch/zap/v12 v12.0.7 github.com/blevesearch/zap/v12 -# github.com/boombuler/barcode v0.0.0-20161226211916-fe0f26ff6d26 -## explicit +# github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc github.com/boombuler/barcode github.com/boombuler/barcode/qr github.com/boombuler/barcode/utils @@ -538,7 +537,7 @@ github.com/philhofer/fwd github.com/pkg/errors # github.com/pmezard/go-difflib v1.0.0 github.com/pmezard/go-difflib/difflib -# github.com/pquerna/otp v0.0.0-20160912161815-54653902c20e +# github.com/pquerna/otp v1.2.0 ## explicit github.com/pquerna/otp github.com/pquerna/otp/hotp