Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding option to exit OTP progress bar #2041

Merged
merged 6 commits into from
Dec 17, 2021
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
applying code review comments
RELEASE_NOTES=n/a

Signed-off-by: Yolan Romailler <anomalroil@users.noreply.github.com>
  • Loading branch information
AnomalRoil committed Dec 1, 2021
commit d0eccf6471124d31c0900d94d9e6d3d8d3cc8765
102 changes: 50 additions & 52 deletions internal/action/otp.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,48 @@ func (s *Action) OTP(c *cli.Context) error {
return s.otp(ctx, name, qrf, clip, pw, true)
}

func tickingBar(ctx context.Context, cancel context.CancelFunc, 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) {
cancel()
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()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I wonder if this is not waiting until the next keystroke, and thus this goroutine isn't returning when it should... (basically it's always in iteration 1 of the for loop...)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the goroutine is leaking when the OTP Bar finishes on line 52, I think.
Related: https://benjamincongdon.me/blog/2020/04/23/Cancelable-Reads-in-Go/

Seems like there isn't an easy way out...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'll have to refactor to use a cancelable reader, using a io.Reader on os.Stdin and test it on Windows hoping it will work fine without all that boilerplate since we are just caring about key presses...

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 {
Expand All @@ -58,78 +100,34 @@ func (s *Action) otp(ctx context.Context, name, qrf string, clip, pw, recurse bo
}
}

done := make(chan bool)
skip := false
ctx, cancel := context.WithCancel(ctx)
// 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
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)
bar := termio.NewProgressBar(int64(secondsLeft))
defer bar.Done()
bar.Hidden = ctxutil.IsHidden(ctx)
if bar.Hidden {
skip = true
cancel()
} else {
cancel := make(chan bool)
bar.Set(0)
go func() {
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
cancelled := false
for tt := range ticker.C {
select {
case <-cancel:
cancelled = true
default:
// we don't want to block if not cancelled
}
if tt.After(expiresAt) || cancelled {
bar.Done()
done <- true
return
}
bar.Inc()
}
}()
go func() {
tty, err := tty.Open()
if err != nil {
out.Errorf(ctx, "Unexpected error opening tty: %v", err)
cancel <- true
}
defer tty.Close()

for {
r, err := tty.ReadRune()
if err != nil {
out.Errorf(ctx, "Unexpected error opening tty: %v", err)
}
if r == 'q' || r == 'x' || err != nil {
cancel <- true
return
}
}
}()
go tickingBar(ctx, cancel, expiresAt, bar)
go waitForKeyPress(ctx, cancel)
}
}

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

// 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
// we wait until our ticker is done or we got a cancelation
select {
case <-done:
return nil
case <-ctx.Done():
return termio.ErrAborted
return nil
}
}

Expand Down