Files
teleport/lib/utils/prompt/confirmation.go
T
Alan Parra 900f11c3db Use a marker for Keychain labels (#12772)
Adding a "marker" to Keychain labels lets us detect unexpected entries (which I
did see happen) and gives us flexibility to change formats in the future.

A marker in the "label" field seems to be enough to achieve the purposes above,
so I stopped at that. Added a couple minor UX tweaks as well.

#9160

* Use a marker for Keychain labels
* Tweak: Rename "Key Handle" to "Credential ID" for consistency
* Tweak: Do not prompt MFA/OTP if doing Touch ID
* Use trace errors
2022-05-24 18:52:54 +00:00

111 lines
3.6 KiB
Go

/*
Copyright 2021 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package prompt implements CLI prompts to the user.
package prompt
import (
"context"
"fmt"
"io"
"strings"
"github.com/gravitational/trace"
)
// Reader is the interface for prompt readers.
type Reader interface {
// ReadContext reads from the underlying buffer, respecting context
// cancellation.
ReadContext(ctx context.Context) ([]byte, error)
}
// SecureReader is the interface for password readers.
type SecureReader interface {
// ReadPassword reads from the underlying buffer, respecting context
// cancellation.
ReadPassword(ctx context.Context) ([]byte, error)
}
// Confirmation prompts the user for a yes/no confirmation for question.
// The prompt is written to out and the answer is read from in.
//
// question should be a plain sentence without "[yes/no]"-type hints at the end.
//
// ctx can be canceled to abort the prompt.
func Confirmation(ctx context.Context, out io.Writer, in Reader, question string) (bool, error) {
fmt.Fprintf(out, "%s [y/N]: ", question)
answer, err := in.ReadContext(ctx)
if err != nil {
return false, trace.WrapWithMessage(err, "failed reading prompt response")
}
switch strings.ToLower(strings.TrimSpace(string(answer))) {
case "y", "yes":
return true, nil
default:
return false, nil
}
}
// PickOne prompts the user to pick one of the provided string options.
// The prompt is written to out and the answer is read from in.
//
// question should be a plain sentence without the list of provided options.
//
// ctx can be canceled to abort the prompt.
func PickOne(ctx context.Context, out io.Writer, in Reader, question string, options []string) (string, error) {
fmt.Fprintf(out, "%s [%s]: ", question, strings.Join(options, ", "))
answerOrig, err := in.ReadContext(ctx)
if err != nil {
return "", trace.Wrap(err, "failed reading prompt response")
}
answer := strings.ToLower(strings.TrimSpace(string(answerOrig)))
for _, opt := range options {
if strings.ToLower(opt) == answer {
return opt, nil
}
}
return "", trace.BadParameter(
"%q is not a valid option, please specify one of [%s]", strings.TrimSpace(string(answerOrig)), strings.Join(options, ", "))
}
// Input prompts the user for freeform text input.
// The prompt is written to out and the answer is read from in.
//
// ctx can be canceled to abort the prompt.
func Input(ctx context.Context, out io.Writer, in Reader, question string) (string, error) {
fmt.Fprintf(out, "%s: ", question)
answer, err := in.ReadContext(ctx)
if err != nil {
return "", trace.Wrap(err, "failed reading prompt response")
}
return strings.TrimSpace(string(answer)), nil
}
// Password prompts the user for a password. The prompt is written to out and
// the answer is read from in.
// The in reader has to be a terminal.
func Password(ctx context.Context, out io.Writer, in SecureReader, question string) (string, error) {
if question != "" {
fmt.Fprintf(out, "%s:\n", question)
}
answer, err := in.ReadPassword(ctx)
if err != nil {
return "", trace.Wrap(err, "failed reading prompt response")
}
return string(answer), nil // passwords not trimmed
}