github.com/prysmaticlabs/prysm@v1.4.4/shared/promptutil/prompt.go (about)

     1  package promptutil
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"os"
     9  	"strings"
    10  
    11  	"github.com/logrusorgru/aurora"
    12  	"github.com/pkg/errors"
    13  	"github.com/prysmaticlabs/prysm/shared/fileutil"
    14  	log "github.com/sirupsen/logrus"
    15  	"github.com/urfave/cli/v2"
    16  	"golang.org/x/crypto/ssh/terminal"
    17  )
    18  
    19  var au = aurora.NewAurora(true)
    20  
    21  // PasswordReaderFunc takes in a *file and returns a password using the terminal package
    22  func passwordReaderFunc(file *os.File) ([]byte, error) {
    23  	pass, err := terminal.ReadPassword(int(file.Fd()))
    24  	return pass, err
    25  }
    26  
    27  // PasswordReader has passwordReaderFunc as the default but can be changed for testing purposes.
    28  var PasswordReader = passwordReaderFunc
    29  
    30  // ValidatePrompt requests the user for text and expects the user to fulfill the provided validation function.
    31  func ValidatePrompt(r io.Reader, promptText string, validateFunc func(string) error) (string, error) {
    32  	var responseValid bool
    33  	var response string
    34  	for !responseValid {
    35  		fmt.Printf("%s:\n", au.Bold(promptText))
    36  		scanner := bufio.NewScanner(r)
    37  		if ok := scanner.Scan(); ok {
    38  			item := scanner.Text()
    39  			response = strings.TrimRight(item, "\r\n")
    40  			if err := validateFunc(response); err != nil {
    41  				fmt.Printf("Entry not valid: %s\n", au.BrightRed(err))
    42  			} else {
    43  				responseValid = true
    44  			}
    45  		} else {
    46  			return "", errors.New("could not scan text input")
    47  		}
    48  	}
    49  	return response, nil
    50  }
    51  
    52  // DefaultPrompt prompts the user for any text and performs no validation. If nothing is entered it returns the default.
    53  func DefaultPrompt(promptText, defaultValue string) (string, error) {
    54  	var response string
    55  	if defaultValue != "" {
    56  		fmt.Printf("%s %s:\n", promptText, fmt.Sprintf("(%s: %s)", au.BrightGreen("default"), defaultValue))
    57  	} else {
    58  		fmt.Printf("%s:\n", promptText)
    59  	}
    60  	scanner := bufio.NewScanner(os.Stdin)
    61  	if ok := scanner.Scan(); ok {
    62  		item := scanner.Text()
    63  		response = strings.TrimRight(item, "\r\n")
    64  		if response == "" {
    65  			return defaultValue, nil
    66  		}
    67  		return response, nil
    68  	}
    69  	return "", errors.New("could not scan text input")
    70  }
    71  
    72  // DefaultAndValidatePrompt prompts the user for any text and expects it to fulfill a validation function. If nothing is entered
    73  // the default value is returned.
    74  func DefaultAndValidatePrompt(promptText, defaultValue string, validateFunc func(string) error) (string, error) {
    75  	var responseValid bool
    76  	var response string
    77  	for !responseValid {
    78  		fmt.Printf("%s %s:\n", promptText, fmt.Sprintf("(%s: %s)", au.BrightGreen("default"), defaultValue))
    79  		scanner := bufio.NewScanner(os.Stdin)
    80  		if ok := scanner.Scan(); ok {
    81  			item := scanner.Text()
    82  			response = strings.TrimRight(item, "\r\n")
    83  			if response == "" {
    84  				return defaultValue, nil
    85  			}
    86  			if err := validateFunc(response); err != nil {
    87  				fmt.Printf("Entry not valid: %s\n", au.BrightRed(err))
    88  			} else {
    89  				responseValid = true
    90  			}
    91  		} else {
    92  			return "", errors.New("could not scan text input")
    93  		}
    94  	}
    95  	return response, nil
    96  }
    97  
    98  // PasswordPrompt prompts the user for a password, that repeatedly requests the password until it qualifies the
    99  // passed in validation function.
   100  func PasswordPrompt(promptText string, validateFunc func(string) error) (string, error) {
   101  	var responseValid bool
   102  	var response string
   103  	for !responseValid {
   104  		fmt.Printf("%s: ", au.Bold(promptText))
   105  		bytePassword, err := PasswordReader(os.Stdin)
   106  		if err != nil {
   107  			return "", err
   108  		}
   109  		response = strings.TrimRight(string(bytePassword), "\r\n")
   110  		if err := validateFunc(response); err != nil {
   111  			fmt.Printf("\nEntry not valid: %s\n", au.BrightRed(err))
   112  		} else {
   113  			fmt.Println("")
   114  			responseValid = true
   115  		}
   116  	}
   117  	return response, nil
   118  }
   119  
   120  // InputPassword with a custom validator along capabilities of confirming
   121  // the password and reading it from disk if a specified flag is set.
   122  func InputPassword(
   123  	cliCtx *cli.Context,
   124  	passwordFileFlag *cli.StringFlag,
   125  	promptText, confirmText string,
   126  	shouldConfirmPassword bool,
   127  	passwordValidator func(input string) error,
   128  ) (string, error) {
   129  	if cliCtx.IsSet(passwordFileFlag.Name) {
   130  		passwordFilePathInput := cliCtx.String(passwordFileFlag.Name)
   131  		passwordFilePath, err := fileutil.ExpandPath(passwordFilePathInput)
   132  		if err != nil {
   133  			return "", errors.Wrap(err, "could not determine absolute path of password file")
   134  		}
   135  		data, err := ioutil.ReadFile(passwordFilePath)
   136  		if err != nil {
   137  			return "", errors.Wrap(err, "could not read password file")
   138  		}
   139  		enteredPassword := strings.TrimRight(string(data), "\r\n")
   140  		if err := passwordValidator(enteredPassword); err != nil {
   141  			return "", errors.Wrap(err, "password did not pass validation")
   142  		}
   143  		return enteredPassword, nil
   144  	}
   145  	if strings.Contains(strings.ToLower(promptText), "new wallet") {
   146  		fmt.Println("Password requirements: at least 8 characters including at least 1 alphabetical character, 1 number, and 1 unicode special character. " +
   147  			"Must not be a common password nor easy to guess")
   148  	}
   149  	var hasValidPassword bool
   150  	var password string
   151  	var err error
   152  	for !hasValidPassword {
   153  		password, err = PasswordPrompt(promptText, passwordValidator)
   154  		if err != nil {
   155  			return "", fmt.Errorf("could not read password: %w", err)
   156  		}
   157  		if shouldConfirmPassword {
   158  			passwordConfirmation, err := PasswordPrompt(confirmText, passwordValidator)
   159  			if err != nil {
   160  				return "", fmt.Errorf("could not read password confirmation: %w", err)
   161  			}
   162  			if password != passwordConfirmation {
   163  				log.Error("Passwords do not match")
   164  				continue
   165  			}
   166  			hasValidPassword = true
   167  		} else {
   168  			return password, nil
   169  		}
   170  	}
   171  	return password, nil
   172  }