Refactor error handling in prompt mode; add cred flag and reset func

This commit is contained in:
Mitchell Simon 2019-08-31 17:19:25 -04:00
parent 84ea1cacdf
commit 633945b1d1
8 changed files with 72 additions and 33 deletions

View File

@ -19,9 +19,10 @@ var errSourceNotFound = errors.New("source host not found")
type credentialFlagSet struct { type credentialFlagSet struct {
includePasswordFlags bool includePasswordFlags bool
includeHostFlag bool includeCredFlags bool
sourceHost string sourceHost string
primary string
noNumbers bool noNumbers bool
noSpecials bool noSpecials bool
length uint length uint
@ -32,35 +33,47 @@ func (set credentialFlagSet) withPasswordFlags() credentialFlagSet {
return set return set
} }
func (set credentialFlagSet) withHostFlag() credentialFlagSet { func (set credentialFlagSet) withCredFlags() credentialFlagSet {
set.includeHostFlag = true set.includeCredFlags = true
return set return set
} }
func (set *credentialFlagSet) register(cmd *cobra.Command) { func (set *credentialFlagSet) register(cmd *cobra.Command) {
if set.includeHostFlag { if set.includeCredFlags {
cmd.Flags().StringVarP(&set.sourceHost, "source-host", "s", "", "filter results to this source host") cmd.Flags().StringVarP(&set.sourceHost, "source-host", "s", "", "filter results to this source host")
cmd.Flags().StringVarP(&set.primary, "primary", "p", "", "specify a primary user key (must include tag if applicable)")
} }
if set.includePasswordFlags { if set.includePasswordFlags {
cmd.Flags().BoolVarP(&set.noNumbers, "no-numbers", "n", false, "do not use numbers in the generated password") cmd.Flags().BoolVarP(&set.noNumbers, "no-numbers", "n", false, "do not use numbers in the generated password")
cmd.Flags().BoolVarP(&set.noSpecials, "no-specials", "p", false, "do not use special characters in the generated password") cmd.Flags().BoolVarP(&set.noSpecials, "no-specials", "e", false, "do not use special characters in the generated password")
cmd.Flags().UintVarP(&set.length, "length", "l", 32, "length of the generated password") cmd.Flags().UintVarP(&set.length, "length", "l", 32, "length of the generated password")
} }
} }
func (set *credentialFlagSet) resetValues() {
set.sourceHost = ""
set.primary = ""
set.noNumbers = false
set.noSpecials = false
set.length = 0
}
var checkPromptMode = false
func check(err error) { func check(err error) {
if err != nil { if err != nil {
fmt.Println(err) if checkPromptMode {
panic(err)
}
fmt.Fprintln(os.Stdout, err)
os.Exit(1) os.Exit(1)
} }
} }
func selectCredential(client types.CredentialsClient, sourceHost string) types.Credential { func selectCredential(client types.CredentialsClient, sourceHost string, primary string) types.Credential {
var ( var prompt survey.Prompt
idKey string
prompt survey.Prompt
)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel() defer cancel()
@ -128,19 +141,24 @@ receive:
keyIDMap[key] = md.ID keyIDMap[key] = md.ID
} }
prompt = &survey.Select{ if primary == "" {
Message: "Primary user key (and tag):", var idKey string
Options: keys, prompt = &survey.Select{
PageSize: 20, Message: "Primary user key (and tag):",
VimMode: true, Options: keys,
} PageSize: 20,
VimMode: true,
}
check(survey.AskOne(prompt, &idKey, nil)) check(survey.AskOne(prompt, &idKey, nil))
primary = idKey
}
ctx, cancel = context.WithTimeout(context.Background(), time.Second*5) ctx, cancel = context.WithTimeout(context.Background(), time.Second*5)
defer cancel() defer cancel()
cred, err := client.Get(ctx, keyIDMap[idKey]) cred, err := client.Get(ctx, keyIDMap[primary])
check(err) check(err)
return cred return cred

View File

@ -27,6 +27,8 @@ func makeCreate(repo clitypes.ConfigRepo, initClient credentialsClientInit) *cob
password.`, password.`,
Run: func(_ *cobra.Command, args []string) { Run: func(_ *cobra.Command, args []string) {
defer flags.resetValues()
var ( var (
otp bool otp bool
cleancb bool cleancb bool

View File

@ -10,7 +10,7 @@ import (
) )
func makeDelete(initClient credentialsClientInit) *cobra.Command { func makeDelete(initClient credentialsClientInit) *cobra.Command {
flags := credentialFlagSet{}.withHostFlag() flags := credentialFlagSet{}.withCredFlags()
deleteCmd := &cobra.Command{ deleteCmd := &cobra.Command{
Use: "delete", Use: "delete",
@ -18,12 +18,14 @@ func makeDelete(initClient credentialsClientInit) *cobra.Command {
Long: `Delete a credential using the given ID, permanently. THERE IS NO UNDOING THIS ACTION.`, Long: `Delete a credential using the given ID, permanently. THERE IS NO UNDOING THIS ACTION.`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
defer flags.resetValues()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*60) ctx, cancel := context.WithTimeout(context.Background(), time.Second*60)
defer cancel() defer cancel()
client := initClient(ctx) client := initClient(ctx)
cred := selectCredential(client, flags.sourceHost) cred := selectCredential(client, flags.sourceHost, flags.primary)
fmt.Println(cred) fmt.Println(cred)

View File

@ -16,7 +16,7 @@ import (
) )
func makeGet(repo clitypes.ConfigRepo, initClient credentialsClientInit) *cobra.Command { func makeGet(repo clitypes.ConfigRepo, initClient credentialsClientInit) *cobra.Command {
flags := credentialFlagSet{}.withHostFlag() flags := credentialFlagSet{}.withCredFlags()
getCmd := &cobra.Command{ getCmd := &cobra.Command{
Use: "get", Use: "get",
@ -25,6 +25,8 @@ func makeGet(repo clitypes.ConfigRepo, initClient credentialsClientInit) *cobra.
decrypting password.`, decrypting password.`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
defer flags.resetValues()
var ( var (
copyPass bool copyPass bool
cleancb bool cleancb bool
@ -38,7 +40,7 @@ decrypting password.`,
masterpass, cfg, err := repo.OpenConfig() masterpass, cfg, err := repo.OpenConfig()
check(err) check(err)
cred := selectCredential(client, flags.sourceHost) cred := selectCredential(client, flags.sourceHost, flags.primary)
fmt.Println(cred) fmt.Println(cred)

View File

@ -12,7 +12,7 @@ import (
) )
func makeList(initClient credentialsClientInit) *cobra.Command { func makeList(initClient credentialsClientInit) *cobra.Command {
flags := credentialFlagSet{}.withHostFlag() flags := credentialFlagSet{}.withCredFlags()
listCmd := &cobra.Command{ listCmd := &cobra.Command{
Use: "list", Use: "list",
@ -21,6 +21,8 @@ func makeList(initClient credentialsClientInit) *cobra.Command {
includes almost all the information but the most sensitive.`, includes almost all the information but the most sensitive.`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
defer flags.resetValues()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*60) ctx, cancel := context.WithTimeout(context.Background(), time.Second*60)
defer cancel() defer cancel()

View File

@ -19,10 +19,13 @@ import (
func Execute() { func Execute() {
rootCmd := &cobra.Command{ rootCmd := &cobra.Command{
Use: "sp", Use: "sp",
Run: run, Run: runPromptMode,
Short: "This is the CLI client for Selfpass.", Short: "This is the CLI client for Selfpass.",
Long: `This is the CLI client for Selfpass, the self-hosted password manager. With this tool you Long: `This is the CLI client for Selfpass, the self-hosted password manager. With this tool you
can interact with the entire Selfpass API.`, can interact with the entire Selfpass API.
When run without a command specified sp enters prompt mode. All commands and flags are the same,
but your master pass only need be entered once until you exit the prompt.`,
Version: "v0.1.0", Version: "v0.1.0",
} }
@ -47,7 +50,7 @@ can interact with the entire Selfpass API.`,
check(rootCmd.Execute()) check(rootCmd.Execute())
} }
func run(cmd *cobra.Command, _ []string) { func runPromptMode(cmd *cobra.Command, _ []string) {
ss := []prompt.Suggest{ ss := []prompt.Suggest{
{Text: "exit", Description: "Exit selfpass prompt"}, {Text: "exit", Description: "Exit selfpass prompt"},
} }
@ -67,6 +70,8 @@ func run(cmd *cobra.Command, _ []string) {
return prompt.FilterHasPrefix(ss, d.TextBeforeCursor(), true) return prompt.FilterHasPrefix(ss, d.TextBeforeCursor(), true)
} }
checkPromptMode = true
executor := func(argstr string) { executor := func(argstr string) {
args := strings.Split(argstr, " ") args := strings.Split(argstr, " ")
@ -75,11 +80,15 @@ func run(cmd *cobra.Command, _ []string) {
os.Exit(0) os.Exit(0)
} }
defer func() {
if err := recover(); err != nil {
fmt.Fprint(os.Stderr, err, "\n\n")
}
}()
cmd.SetArgs(args) cmd.SetArgs(args)
err := cmd.Execute() cmd.Execute()
if err != nil {
fmt.Println(err)
}
fmt.Println() fmt.Println()
} }
@ -98,6 +107,7 @@ func run(cmd *cobra.Command, _ []string) {
prompt.OptionPreviewSuggestionTextColor(prompt.Red), prompt.OptionPreviewSuggestionTextColor(prompt.Red),
) )
fmt.Println("\nWelcome to the selfpass prompt.")
p.Run() p.Run()
} }

View File

@ -18,7 +18,7 @@ import (
) )
func makeUpdate(repo clitypes.ConfigRepo, initClient credentialsClientInit) *cobra.Command { func makeUpdate(repo clitypes.ConfigRepo, initClient credentialsClientInit) *cobra.Command {
flags := credentialFlagSet{}.withHostFlag().withPasswordFlags() flags := credentialFlagSet{}.withCredFlags().withPasswordFlags()
updateCmd := &cobra.Command{ updateCmd := &cobra.Command{
Use: "update", Use: "update",
@ -27,6 +27,8 @@ func makeUpdate(repo clitypes.ConfigRepo, initClient credentialsClientInit) *cob
password.`, password.`,
Run: func(_ *cobra.Command, args []string) { Run: func(_ *cobra.Command, args []string) {
defer flags.resetValues()
var ( var (
newpass bool newpass bool
otp bool otp bool
@ -43,7 +45,7 @@ password.`,
client := initClient(ctx) client := initClient(ctx)
cred := selectCredential(client, flags.sourceHost) cred := selectCredential(client, flags.sourceHost, flags.primary)
mdqs := []*survey.Question{ mdqs := []*survey.Question{
{ {

View File

@ -71,6 +71,7 @@ func (mgr *ConfigManager) OpenConfig() (output string, v *viper.Viper, err error
if err == errConfigDecrypted { if err == errConfigDecrypted {
configDecrypted = true configDecrypted = true
} else if err != nil && err.Error() == crypto.ErrAuthenticationFailed.Error() { } else if err != nil && err.Error() == crypto.ErrAuthenticationFailed.Error() {
mgr.masterpass = ""
return output, nil, errors.New("incorrect masterpass") return output, nil, errors.New("incorrect masterpass")
} else if err != nil { } else if err != nil {
return output, nil, err return output, nil, err