github.com/jcarley/cli@v0.0.0-20180201210820-966d90434c30/lib/prompts/contract.go (about) 1 package prompts 2 3 import ( 4 "bufio" 5 "errors" 6 "fmt" 7 "os" 8 "runtime" 9 "strings" 10 11 "github.com/daticahealth/cli/config" 12 13 "golang.org/x/crypto/ssh/terminal" 14 ) 15 16 // IPrompts is the interface in which to interact with the user and accept 17 // input. 18 type IPrompts interface { 19 EmailPassword(existingEmail, existingPassword string) (string, string, error) 20 KeyPassphrase(string) string 21 Password(msg string) string 22 PHI() error 23 YesNo(msg, prompt string) error 24 OTP(string) string 25 GenericPrompt(msg, prompt string, validOptions []string) string 26 CaptureInput(msg string) string 27 } 28 29 // SPrompts is a concrete implementation of IPrompts 30 type SPrompts struct{} 31 32 // New returns a new instance of IPrompts 33 func New() IPrompts { 34 return &SPrompts{} 35 } 36 37 // EmailPassword prompts a user to enter their email and password. 38 func (p *SPrompts) EmailPassword(existingEmail, existingPassword string) (string, string, error) { 39 email := existingEmail 40 var err error 41 if email == "" { 42 fmt.Print("Email: ") 43 in := bufio.NewReader(os.Stdin) 44 email, err = in.ReadString('\n') 45 if err != nil { 46 return "", "", errors.New("Invalid email") 47 } 48 email = strings.TrimRight(email, "\n") 49 if runtime.GOOS == "windows" { 50 email = strings.TrimRight(email, "\r") 51 } 52 } else { 53 fmt.Printf("Using email from environment variable %s\n", config.DaticaEmailEnvVar) 54 } 55 password := existingPassword 56 if password == "" { 57 fmt.Print("Password: ") 58 bytes, _ := terminal.ReadPassword(int(os.Stdin.Fd())) 59 fmt.Println("") 60 password = string(bytes) 61 } else { 62 fmt.Printf("Using password from environment variable %s\n", config.DaticaPasswordEnvVar) 63 } 64 return email, password, nil 65 } 66 67 // KeyPassphrase prompts a user to enter a passphrase for a named key. 68 func (p *SPrompts) KeyPassphrase(filepath string) string { 69 fmt.Printf("Enter passphrase for %s: ", filepath) 70 bytes, _ := terminal.ReadPassword(int(os.Stdin.Fd())) 71 fmt.Println("") 72 return string(bytes) 73 } 74 75 // PHI prompts a user to accept liability for downloading PHI to their local 76 // machine. 77 func (p *SPrompts) PHI() error { 78 acceptAnswers := []string{"y", "yes"} 79 denyAnswers := []string{"n", "no"} 80 81 answer := p.GenericPrompt("This operation might result in PHI data being downloaded and decrypted to your local machine. By entering \"y\" at the prompt below, you warrant that you have the necessary privileges to view the data, have taken all necessary precautions to secure this data, and absolve Datica of any issues that might arise from its loss.", "Do you wish to proceed? (y/n) ", append(acceptAnswers, denyAnswers...)) 82 for _, denyAnswer := range denyAnswers { 83 if denyAnswer == strings.ToLower(answer) { 84 return fmt.Errorf("Exiting") 85 } 86 } 87 return nil 88 } 89 90 // YesNo outputs a given message and waits for a user to answer `y/n`. 91 // If yes, flow continues as normal. If no, an error is returned. The given 92 // message SHOULD contain the string "(y/n)" or some other form of y/n 93 // indicating that the user needs to type in y or n. This method does not do 94 // that for you. The message will not have a new line appended to it. If you 95 // require a newline, add this to the given message. 96 func (p *SPrompts) YesNo(msg, prompt string) error { 97 acceptAnswers := []string{"y", "yes"} 98 denyAnswers := []string{"n", "no"} 99 100 answer := p.GenericPrompt(msg, prompt, append(acceptAnswers, denyAnswers...)) 101 for _, denyAnswer := range denyAnswers { 102 if denyAnswer == strings.ToLower(answer) { 103 return fmt.Errorf("Exiting") 104 } 105 } 106 return nil 107 } 108 109 // Password prompts the user for a password displaying the given message. 110 // The password will be hidden while typed. A newline is not added to the given 111 // message. If a newline is required, it should be part of the passed in string. 112 func (p *SPrompts) Password(msg string) string { 113 fmt.Print(msg) 114 bytes, _ := terminal.ReadPassword(int(os.Stdin.Fd())) 115 fmt.Println("") 116 return string(bytes) 117 } 118 119 // OTP prompts for a one-time password and returns the value. 120 func (p *SPrompts) OTP(preferredMode string) string { 121 fmt.Println("This account has two-factor authentication enabled.") 122 prompt := "Your one-time password: " 123 if preferredMode == "authenticator" { 124 prompt = "Your authenticator one-time password: " 125 } else if preferredMode == "email" { 126 prompt = "One-time password (sent to your email): " 127 } 128 fmt.Print(prompt) 129 var token string 130 fmt.Scanln(&token) 131 return strings.TrimSpace(token) 132 } 133 134 // GenericPrompt prompts the user and validates the input against the list of 135 // given case-insensitive valid options. The user's choice is returned. 136 func (p *SPrompts) GenericPrompt(msg, prompt string, validOptions []string) string { 137 var answer string 138 fmt.Println(msg) 139 for { 140 fmt.Printf(prompt) 141 fmt.Scanln(&answer) 142 fmt.Println("") 143 valid := false 144 for _, choice := range validOptions { 145 if strings.ToLower(choice) == strings.ToLower(answer) { 146 valid = true 147 break 148 } 149 } 150 if !valid { 151 fmt.Printf("%s is not a valid option. Please enter one of %s\n", answer, strings.Join(validOptions, ", ")) 152 } else { 153 break 154 } 155 } 156 return answer 157 } 158 159 // CaptureInput prompts the user with the given msg and reads input until a newline is encountered. The input is 160 // returned with newlines stripped. The prompt and the input will be on the same line when shown to the user. 161 func (p *SPrompts) CaptureInput(msg string) string { 162 var answer string 163 fmt.Printf(msg) 164 fmt.Scanln(&answer) 165 fmt.Println("") 166 return answer 167 }