2025-09-07 14:30:04 +02:00
|
|
|
package terminal
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
2025-09-14 19:21:51 +02:00
|
|
|
"io"
|
2025-09-07 14:30:04 +02:00
|
|
|
|
|
|
|
|
"golang.org/x/term"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// ReadPassword reads the password from the given reader which must be a
|
|
|
|
|
// tty. Prompt is printed on the writer out before attempting to read the
|
|
|
|
|
// password. If the context is canceled, the function leaks the password reading
|
|
|
|
|
// goroutine.
|
2025-09-14 19:21:51 +02:00
|
|
|
func ReadPassword(ctx context.Context, inFd int, out io.Writer, prompt string) (password string, err error) {
|
|
|
|
|
state, err := term.GetState(inFd)
|
2025-09-07 14:30:04 +02:00
|
|
|
if err != nil {
|
2025-09-14 19:21:51 +02:00
|
|
|
_, _ = fmt.Fprintf(out, "unable to get terminal state: %v\n", err)
|
2025-09-07 14:30:04 +02:00
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
done := make(chan struct{})
|
|
|
|
|
var buf []byte
|
|
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
|
defer close(done)
|
|
|
|
|
_, err = fmt.Fprint(out, prompt)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-09-14 19:21:51 +02:00
|
|
|
buf, err = term.ReadPassword(inFd)
|
2025-09-07 14:30:04 +02:00
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
_, err = fmt.Fprintln(out)
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
select {
|
|
|
|
|
case <-ctx.Done():
|
2025-09-14 19:21:51 +02:00
|
|
|
err := term.Restore(inFd, state)
|
2025-09-07 14:30:04 +02:00
|
|
|
if err != nil {
|
2025-09-14 19:21:51 +02:00
|
|
|
_, _ = fmt.Fprintf(out, "unable to restore terminal state: %v\n", err)
|
2025-09-07 14:30:04 +02:00
|
|
|
}
|
|
|
|
|
return "", ctx.Err()
|
|
|
|
|
case <-done:
|
|
|
|
|
// clean shutdown, nothing to do
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", fmt.Errorf("ReadPassword: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return string(buf), nil
|
|
|
|
|
}
|