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 }