go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/cli/components/password.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package components
     5  
     6  import (
     7  	"errors"
     8  	"os"
     9  
    10  	input "github.com/charmbracelet/bubbles/textinput"
    11  	tea "github.com/charmbracelet/bubbletea"
    12  	"github.com/mattn/go-isatty"
    13  )
    14  
    15  type passwordErrMsg error
    16  
    17  type password struct {
    18  	textInput  input.Model
    19  	err        error
    20  	onComplete func(res string, aborted bool)
    21  }
    22  
    23  // A bubbletea password input component
    24  //
    25  //	passwordModel := components.NewPasswordModel("root@192.168.178.141's password: ", func(res string, aborted bool) {
    26  //		 // output the pwd
    27  //		 fmt.Println(password)
    28  //	})
    29  //
    30  // p := tea.NewProgram(passwordModel)
    31  //
    32  //	if err := p.Start(); err != nil {
    33  //		 panic(err)
    34  //	}
    35  func NewPasswordModel(prompt string, onComplete func(res string, aborted bool)) password {
    36  	inputModel := input.NewModel()
    37  	inputModel.Prompt = prompt
    38  	inputModel.Focus()
    39  	inputModel.EchoMode = input.EchoNone
    40  	inputModel.CharLimit = 156
    41  	inputModel.Width = 20
    42  
    43  	return password{
    44  		textInput:  inputModel,
    45  		err:        nil,
    46  		onComplete: onComplete,
    47  	}
    48  }
    49  
    50  func (m password) Init() tea.Cmd {
    51  	return input.Blink
    52  }
    53  
    54  func (m password) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    55  	var cmd tea.Cmd
    56  
    57  	switch msg := msg.(type) {
    58  	case tea.KeyMsg:
    59  		aborted := false
    60  		switch msg.Type {
    61  		case tea.KeyCtrlC:
    62  			aborted = true
    63  			fallthrough
    64  		case tea.KeyEsc:
    65  			aborted = true
    66  			fallthrough
    67  		case tea.KeyEnter:
    68  			m.onComplete(m.textInput.Value(), aborted)
    69  			return m, tea.Quit
    70  		}
    71  
    72  	// We handle errors just like any other message
    73  	case passwordErrMsg:
    74  		m.err = msg
    75  		return m, nil
    76  	}
    77  
    78  	m.textInput, cmd = m.textInput.Update(msg)
    79  	return m, cmd
    80  }
    81  
    82  func (m password) View() string {
    83  	return m.textInput.View() + "\n"
    84  }
    85  
    86  // AskPassword will only prompt the user for a password if they are on a TTY.
    87  func AskPassword(prompt string) (string, error) {
    88  	// check if password is set
    89  	if !isatty.IsTerminal(os.Stdout.Fd()) {
    90  		return "", errors.New("asking passwords is only supported when used with an interactive terminal (TTY)")
    91  	}
    92  
    93  	// ask user for password
    94  	var res string = ""
    95  	passwordModel := NewPasswordModel(prompt, func(userPassword string, aborted bool) {
    96  		res = userPassword
    97  		if aborted {
    98  			os.Exit(1)
    99  		}
   100  	})
   101  
   102  	p := tea.NewProgram(passwordModel, tea.WithInputTTY())
   103  	if _, err := p.Run(); err != nil {
   104  		return res, err
   105  	}
   106  
   107  	return res, nil
   108  }