Skip to content

Commit

Permalink
Adding option to exit OTP progress bar (gopasspw#2041)
Browse files Browse the repository at this point in the history
* RELEASE_NOTES=[ENHANCEMENT] Adding option to exit OTP progress bar

Signed-off-by: Yolan Romailler <yolan@romailler.ch>

* applying code review comments

RELEASE_NOTES=n/a

Signed-off-by: Yolan Romailler <anomalroil@users.noreply.github.com>

* patching CI

RELEASE_NOTES=n/a

Signed-off-by: Yolan Romailler <yolan@romailler.ch>

* Adding a loop for OTP

RELEASE_NOTES=[UX] OTP code now runs in loop until canceled or used with -o

Signed-off-by: Yolan Romailler <anomalroil@users.noreply.github.com>
  • Loading branch information
AnomalRoil authored Dec 17, 2021
1 parent a3c7747 commit 3e97a29
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 55 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,13 @@ require (
github.com/martinhoefling/goxkcdpwgen v0.0.0-20190331205820-7dc3d102eca3
github.com/mattn/go-colorable v0.1.12
github.com/mattn/go-isatty v0.0.14
github.com/mattn/go-tty v0.0.3
github.com/mitchellh/go-homedir v1.1.0
github.com/mitchellh/go-ps v1.0.0
github.com/muesli/crunchy v0.4.0
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354
github.com/pkg/errors v0.9.1 // indirect
github.com/rogpeppe/go-internal v1.8.0 // indirect
github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451 // indirect
github.com/schollz/closestmatch v0.0.0-20190308193919-1fbe626be92e
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/stretchr/testify v1.7.0
Expand Down
13 changes: 11 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -164,12 +164,18 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/martinhoefling/goxkcdpwgen v0.0.0-20190331205820-7dc3d102eca3 h1:fvQLuMSKU08pIM+I7I8pjbbPjW6Nx4sf7jOx/Pjc0qI=
github.com/martinhoefling/goxkcdpwgen v0.0.0-20190331205820-7dc3d102eca3/go.mod h1:4HvZROUEazha3RDnoBcxQlwcIbQfwx035roFOMnICSE=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI=
github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
Expand All @@ -187,8 +193,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451 h1:d1PiN4RxzIFXCJTvRkvSkKqwtRAl5ZV4lATKtQI0B7I=
github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
Expand Down Expand Up @@ -305,6 +311,7 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand All @@ -313,6 +320,8 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down
152 changes: 100 additions & 52 deletions internal/action/otp.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/gopasspw/gopass/pkg/otp"
"github.com/gopasspw/gopass/pkg/termio"

"github.com/mattn/go-tty"
"github.com/urfave/cli/v2"
)

Expand All @@ -36,72 +37,119 @@ func (s *Action) OTP(c *cli.Context) error {
return s.otp(ctx, name, qrf, clip, pw, true)
}

func tickingBar(ctx context.Context, expiresAt time.Time, bar *termio.ProgressBar) {
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for tt := range ticker.C {
select {
case <-ctx.Done():
return // returning not to leak the goroutine
default:
// we don't want to block if not cancelled
}
if tt.After(expiresAt) {
return
}
bar.Inc()
}
}

func waitForKeyPress(ctx context.Context, cancel context.CancelFunc) {
tty, err := tty.Open()
if err != nil {
out.Errorf(ctx, "Unexpected error opening tty: %v", err)
cancel()
}
defer tty.Close()
for {
select {
case <-ctx.Done():
return // returning not to leak the goroutine
default:
}
r, err := tty.ReadRune()
if err != nil {
out.Errorf(ctx, "Unexpected error opening tty: %v", err)
}
if r == 'q' || r == 'x' || err != nil {
cancel()
return
}
}
}

func (s *Action) otp(ctx context.Context, name, qrf string, clip, pw, recurse bool) error {
sec, err := s.Store.Get(ctx, name)
if err != nil {
return s.otpHandleError(ctx, name, qrf, clip, pw, recurse, err)
}
two, label, err := otp.Calculate(name, sec)
if err != nil {
return ExitError(ExitUnknown, err, "No OTP entry found for %s: %s", name, err)
}
token := two.OTP()

now := time.Now()
expiresAt := now.Add(otpPeriod * time.Second).Truncate(otpPeriod * time.Second)
secondsLeft := int(time.Until(expiresAt).Seconds())
ctx, cancel := context.WithCancel(ctx)
defer cancel()
skip := ctxutil.IsHidden(ctx) || pw || qrf != "" || out.OutputIsRedirected()
if !skip {
// let us monitor key presses for cancellation:
go waitForKeyPress(ctx, cancel)
}

if clip {
if err := clipboard.CopyTo(ctx, fmt.Sprintf("token for %s", name), []byte(token), s.cfg.ClipTimeout); err != nil {
return ExitError(ExitIO, err, "failed to copy to clipboard: %s", err)
for {
select {
case <-ctx.Done():
return nil
default:
}
}
two, label, err := otp.Calculate(name, sec)
if err != nil {
return ExitError(ExitUnknown, err, "No OTP entry found for %s: %s", name, err)
}
token := two.OTP()

done := make(chan bool)
skip := false
// check if we are in "password only" or in "qr code" mode or being redirected to a pipe
if pw || qrf != "" || out.OutputIsRedirected() {
out.Printf(ctx, "%s", token)
skip = true
} else { // if not then we want to print a progress bar with the expiry time
out.Printf(ctx, "%s", token)
out.Warningf(ctx, "This OTP password still lasts for:", nil)
now := time.Now()
expiresAt := now.Add(otpPeriod * time.Second).Truncate(otpPeriod * time.Second)
secondsLeft := int(time.Until(expiresAt).Seconds())
bar := termio.NewProgressBar(int64(secondsLeft))
bar.Hidden = ctxutil.IsHidden(ctx)
if bar.Hidden {
skip = true
} else {
bar.Set(0)
go func() {
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for tt := range ticker.C {
if tt.After(expiresAt) {
bar.Done()
done <- true
return
}
bar.Inc()
}
}()
bar.Hidden = skip

if clip {
if err := clipboard.CopyTo(ctx, fmt.Sprintf("token for %s", name), []byte(token), s.cfg.ClipTimeout); err != nil {
return ExitError(ExitIO, err, "failed to copy to clipboard: %s", err)
}
}
}

if qrf != "" {
return otp.WriteQRFile(two, label, qrf)
}
// check if we are in "password only" or in "qr code" mode or being redirected to a pipe
if pw || qrf != "" || out.OutputIsRedirected() {
out.Printf(ctx, "%s", token)
cancel()
} else { // if not then we want to print a progress bar with the expiry time
out.Printf(ctx, "%s", token)
out.Warningf(ctx, "([q] to stop. -o flag to avoid.) This OTP password still lasts for:", nil)

// we need to return if we are skipping, to avoid a deadlock in select
if skip {
return nil
}
if bar.Hidden {
cancel()
} else {
bar.Set(0)
go tickingBar(ctx, expiresAt, bar)
}
}

// we wait until our ticker is done or we get a cancelation
select {
case <-done:
return nil
case <-ctx.Done():
return termio.ErrAborted
if qrf != "" {
return otp.WriteQRFile(two, label, qrf)
}

// let us wait until next OTP code:
for {
select {
case <-ctx.Done():
bar.Done()
return nil
default:
time.Sleep(time.Millisecond * 500)
}
if time.Now().After(expiresAt) {
bar.Done()
break
}
}
}
}

Expand Down

0 comments on commit 3e97a29

Please sign in to comment.