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  }