Skip to content

Commit

Permalink
Better progress bar handling for OTP (gopasspw#2019)
Browse files Browse the repository at this point in the history
* Better progress bar handling for OTP

RELEASE_NOTES=[BUGFIX] Do not print OTP progress bar if not in terminal

RELEASE_NOTES=[UX] Use new progress bar for OTP expiry time

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

* Applying code review suggestions

RELEASE_NOTES=n/a

Signed-off-by: Yolan Romailler <anomalroil@users.noreply.github.com>
  • Loading branch information
AnomalRoil committed Nov 3, 2021
1 parent bb30465 commit fd1fe6f
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 16 deletions.
61 changes: 45 additions & 16 deletions internal/action/otp.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ package action
import (
"context"
"fmt"
"strings"
"time"

"github.com/gopasspw/gopass/internal/out"
"github.com/gopasspw/gopass/internal/store"
"github.com/gopasspw/gopass/pkg/clipboard"
"github.com/gopasspw/gopass/pkg/ctxutil"
"github.com/gopasspw/gopass/pkg/otp"
"github.com/gopasspw/gopass/pkg/termio"

"github.com/urfave/cli/v2"
)
Expand Down Expand Up @@ -48,32 +48,61 @@ func (s *Action) otp(ctx context.Context, name, qrf string, clip, pw, recurse bo
token := two.OTP()

now := time.Now()
t := now.Add(otpPeriod * time.Second)

expiresAt := time.Unix(t.Unix()+otpPeriod-(t.Unix()%otpPeriod), 0)
expiresAt := now.Add(otpPeriod * time.Second).Truncate(otpPeriod * time.Second)
secondsLeft := int(time.Until(expiresAt).Seconds())

if secondsLeft >= otpPeriod {
secondsLeft -= otpPeriod
}

if pw {
out.Printf(ctx, "%s", token)
} else {
out.Printf(ctx, "%s lasts %ds \t|%s%s|", token, secondsLeft, strings.Repeat("-", otpPeriod-secondsLeft), strings.Repeat("=", secondsLeft))
}

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)
}
return nil
}

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)
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()
}
}()
}
}

if qrf != "" {
return otp.WriteQRFile(two, label, qrf)
}
return nil

// we need to return if we are skipping, to avoid a deadlock in select
if skip {
return nil
}

// we wait until our ticker is done or we get a cancelation
select {
case <-done:
return nil
case <-ctx.Done():
return termio.ErrAborted
}
}

func (s *Action) otpHandleError(ctx context.Context, name, qrf string, clip, pw, recurse bool, err error) error {
Expand Down
6 changes: 6 additions & 0 deletions internal/out/print.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ func (s Secret) SafeStr() string {
return "(elided)"
}

// OutputIsRedirected returns true if the current os.Stdout is a pipe instead of a terminal
func OutputIsRedirected() bool {
o, _ := os.Stdout.Stat()
return (o.Mode() & os.ModeCharDevice) != os.ModeCharDevice
}

func newline(ctx context.Context) string {
if HasNewline(ctx) {
return "\n"
Expand Down

0 comments on commit fd1fe6f

Please sign in to comment.