diff --git a/README.md b/README.md index c09fd3a..d551e6a 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ A local password manager ### SEE ALSO * [password-manager add](password-manager_add.md) - Add a new password +* [password-manager change](password-manager_change.md) - Change a password entry * [password-manager change-master-password](password-manager_change-master-password.md) - Change Master password * [password-manager generate-password](password-manager_generate-password.md) - Generate a secure password * [password-manager get](password-manager_get.md) - Get a password diff --git a/cmd/change.go b/cmd/change.go new file mode 100644 index 0000000..fb1e37d --- /dev/null +++ b/cmd/change.go @@ -0,0 +1,104 @@ +// Copyright © 2019 NAME HERE +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "github.com/ThilinaManamgoda/password-manager/pkg/inputs" + "github.com/ThilinaManamgoda/password-manager/pkg/passwords" + "github.com/pkg/errors" + + "github.com/spf13/cobra" +) + +// changeCmd represents the change command +var changeCmd = &cobra.Command{ + Use: "change [ID]", + Short: "Change a password entry", + Long: `Change a password entry`, + Args: inputs.HasProvidedValidID(), + RunE: func(cmd *cobra.Command, args []string) error { + id := args[0] + + mPassword, err := inputs.GetFlagStringVal(cmd, inputs.MasterPassword) + if err != nil { + return errors.Wrapf(err, inputs.ErrMSGCannotGetFlag, mPassword) + } + if mPassword == "" { + mPassword, err = inputs.PromptForMPassword() + if err != nil { + return errors.Wrap(err, "cannot prompt for Master password") + } + } + + passwordRepo, err := passwords.InitPasswordRepo(mPassword) + if err != nil { + return errors.Wrapf(err, "cannot initialize password repository") + } + + passwordEntry, err := passwordRepo.GetPasswordEntry(id) + if err != nil { + return errors.Wrapf(err, "cannot get password entry") + } + + isInteractiveMode, err := inputs.GetFlagBoolVal(cmd, InteractiveMode) + if err != nil { + return err + } + var uN, password string + if isInteractiveMode { + uN, err = inputs.PromptForUsernameWithDefault(passwordEntry.Username) + if err != nil { + return errors.Wrap(err, "cannot prompt for username") + } + password, err = inputs.PromptForUserPasswordWithDefault(passwordEntry.Password) + if err != nil { + return errors.Wrap(err, "cannot prompt for password") + } + } else { + err = inputs.FromFlags(cmd, &uN, &password, nil, nil) + if err != nil { + return errors.Wrapf(err, inputs.ErrMsgCannotGetInput) + } + } + newEntry := passwords.PasswordEntry{ + ID: id, + Username: uN, + Password: password, + } + err = passwordRepo.ChangePasswordEntry(id, newEntry) + if err != nil { + return errors.Wrapf(err, "cannot change password") + } + return nil + }, +} + +func init() { + rootCmd.AddCommand(changeCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // changeCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // changeCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + changeCmd.Flags().StringP(inputs.Password, "p", "", "Password") + changeCmd.Flags().StringP(inputs.Username, "u", "", "User Name") + changeCmd.Flags().BoolP(InteractiveMode, "i", false, "Enable interactive mode") + +} diff --git a/password-manager_add.md b/password-manager_add.md index 3f40fbe..1586c3d 100644 --- a/password-manager_add.md +++ b/password-manager_add.md @@ -30,4 +30,4 @@ password-manager add [ID] [flags] * [password-manager](password-manager.md) - A local Password Manager -###### Auto generated by spf13/cobra on 12-Dec-2019 +###### Auto generated by spf13/cobra on 15-Dec-2019 diff --git a/password-manager_change-master-password.md b/password-manager_change-master-password.md index 0e31b3f..ed460ea 100644 --- a/password-manager_change-master-password.md +++ b/password-manager_change-master-password.md @@ -26,4 +26,4 @@ password-manager change-master-password [flags] * [password-manager](password-manager.md) - A local Password Manager -###### Auto generated by spf13/cobra on 12-Dec-2019 +###### Auto generated by spf13/cobra on 15-Dec-2019 diff --git a/password-manager_change.md b/password-manager_change.md new file mode 100644 index 0000000..f553d93 --- /dev/null +++ b/password-manager_change.md @@ -0,0 +1,32 @@ +## password-manager change + +Change a password entry + +### Synopsis + +Change a password entry + +``` +password-manager change [ID] [flags] +``` + +### Options + +``` + -h, --help help for change + -i, --interactive Enable interactive mode + -p, --password string Password + -u, --username string User Name +``` + +### Options inherited from parent commands + +``` + -m, --masterPassword string Master password +``` + +### SEE ALSO + +* [password-manager](password-manager.md) - A local Password Manager + +###### Auto generated by spf13/cobra on 15-Dec-2019 diff --git a/password-manager_generate-password.md b/password-manager_generate-password.md index a124d64..ac193a7 100644 --- a/password-manager_generate-password.md +++ b/password-manager_generate-password.md @@ -27,4 +27,4 @@ password-manager generate-password [flags] * [password-manager](password-manager.md) - A local Password Manager -###### Auto generated by spf13/cobra on 12-Dec-2019 +###### Auto generated by spf13/cobra on 15-Dec-2019 diff --git a/password-manager_get.md b/password-manager_get.md index 4fba67d..4241f37 100644 --- a/password-manager_get.md +++ b/password-manager_get.md @@ -27,4 +27,4 @@ password-manager get [ID] [flags] * [password-manager](password-manager.md) - A local Password Manager -###### Auto generated by spf13/cobra on 12-Dec-2019 +###### Auto generated by spf13/cobra on 15-Dec-2019 diff --git a/password-manager_import.md b/password-manager_import.md index ba8bb06..83e4522 100644 --- a/password-manager_import.md +++ b/password-manager_import.md @@ -27,4 +27,4 @@ password-manager import [flags] * [password-manager](password-manager.md) - A local Password Manager -###### Auto generated by spf13/cobra on 12-Dec-2019 +###### Auto generated by spf13/cobra on 15-Dec-2019 diff --git a/password-manager_remove.md b/password-manager_remove.md index 37d7b80..e4eafe6 100644 --- a/password-manager_remove.md +++ b/password-manager_remove.md @@ -26,4 +26,4 @@ password-manager remove [ID] [flags] * [password-manager](password-manager.md) - A local Password Manager -###### Auto generated by spf13/cobra on 12-Dec-2019 +###### Auto generated by spf13/cobra on 15-Dec-2019 diff --git a/password-manager_search-id.md b/password-manager_search-id.md index f4a6e91..9d25f30 100644 --- a/password-manager_search-id.md +++ b/password-manager_search-id.md @@ -27,4 +27,4 @@ password-manager search-id [ID] [flags] * [password-manager](password-manager.md) - A local Password Manager -###### Auto generated by spf13/cobra on 12-Dec-2019 +###### Auto generated by spf13/cobra on 15-Dec-2019 diff --git a/password-manager_search-label.md b/password-manager_search-label.md index f187566..7d5fcd0 100644 --- a/password-manager_search-label.md +++ b/password-manager_search-label.md @@ -27,4 +27,4 @@ password-manager search-label [ID] [flags] * [password-manager](password-manager.md) - A local Password Manager -###### Auto generated by spf13/cobra on 12-Dec-2019 +###### Auto generated by spf13/cobra on 15-Dec-2019 diff --git a/pkg/inputs/inputs.go b/pkg/inputs/inputs.go index 7210547..ff0f993 100644 --- a/pkg/inputs/inputs.go +++ b/pkg/inputs/inputs.go @@ -16,6 +16,7 @@ package inputs import ( + "fmt" "github.com/manifoldco/promptui" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -36,8 +37,30 @@ const ( ErrMSGCannotGetFlag = "cannot get value of %s flag" MasterPassword = "masterPassword" + + MaxPasswordCharacters = 6 ) +var ( + userNameValidator = func(input string) error { + if len(input) < MaxPasswordCharacters { + return errors.New(fmt.Sprintf("username must have more than %d characters", MaxPasswordCharacters)) + } + return nil + } + + passwordValidator = func(errorMsg string) func(input string) error { + return func(input string) error { + if len(input) < MaxPasswordCharacters { + return errors.New(errorMsg) + } + return nil + } + } + masterPasswordValidator = passwordValidator(fmt.Sprintf("master password must have more than %d characters", MaxPasswordCharacters)) + userPasswordValidator = passwordValidator(fmt.Sprintf("password must have more than %d characters", MaxPasswordCharacters)) + newMasterPasswordValidator = passwordValidator(fmt.Sprintf("new master password must have more than %d characters", MaxPasswordCharacters)) +) // IsPasswordValid method check whether the Password is valid or not func IsPasswordValid(passphrase string) bool { return passphrase != "" @@ -61,7 +84,6 @@ func GetFlagIntVal(cmd *cobra.Command, flag string) (int, error) { return val, nil } - // GetFlagBoolVal method returns the Boolean flag value func GetFlagBoolVal(cmd *cobra.Command, flag string) (bool, error) { val, err := cmd.Flags().GetBool(flag) @@ -99,6 +121,29 @@ func PromptForString(label string, validate promptui.ValidateFunc) (string, erro return prompt.Run() } +func PromptForStringWithDefault(label, defaultVal string, validate promptui.ValidateFunc) (string, error) { + prompt := promptui.Prompt{ + Label: label, + Validate: validate, + Default: defaultVal, + } + return prompt.Run() +} + +func PromptForUserPasswordWithDefault(defaultVal string)(string, error){ + return PromptForPasswordWithDefault("Password ", defaultVal, userPasswordValidator) +} + +func PromptForPasswordWithDefault(label, defaultVal string, validate promptui.ValidateFunc) (string, error) { + prompt := promptui.Prompt{ + Label: label, + Validate: validate, + Default: defaultVal, + Mask: '*', + } + return prompt.Run() +} + func promptForPassword(label string, validate promptui.ValidateFunc) (string, error) { prompt := promptui.Prompt{ Label: label, @@ -132,26 +177,37 @@ func HasProvidedValidID() func(cmd *cobra.Command, args []string) error { } func FromFlags(cmd *cobra.Command, uN, password, mPassword *string, labels *[]string) error { - uNVal, err := GetFlagStringVal(cmd, Username) - if err != nil { - return errors.Wrapf(err, ErrMSGCannotGetFlag, Username) + if uN !=nil { + uNVal, err := GetFlagStringVal(cmd, Username) + if err != nil { + return errors.Wrapf(err, ErrMSGCannotGetFlag, Username) + } + *uN = uNVal } - *uN = uNVal - passwordVal, err := GetFlagStringVal(cmd, Password) - if err != nil { - return errors.Wrapf(err, ErrMSGCannotGetFlag, Password) + + if password != nil { + passwordVal, err := GetFlagStringVal(cmd, Password) + if err != nil { + return errors.Wrapf(err, ErrMSGCannotGetFlag, Password) + } + *password = passwordVal } - *password = passwordVal - labelsVal, err := GetFlagStringArrayVal(cmd, Labels) - if err != nil { - return errors.Wrapf(err, ErrMSGCannotGetFlag, Labels) + + if labels !=nil { + labelsVal, err := GetFlagStringArrayVal(cmd, Labels) + if err != nil { + return errors.Wrapf(err, ErrMSGCannotGetFlag, Labels) + } + *labels = labelsVal } - *labels = labelsVal - mPasswordVal, err := GetFlagStringVal(cmd, MasterPassword) - if err != nil { - return errors.Wrapf(err, ErrMSGCannotGetFlag, MasterPassword) + + if mPassword !=nil { + mPasswordVal, err := GetFlagStringVal(cmd, MasterPassword) + if err != nil { + return errors.Wrapf(err, ErrMSGCannotGetFlag, MasterPassword) + } + *mPassword = mPasswordVal } - *mPassword = mPasswordVal return nil } @@ -180,13 +236,11 @@ func FromPrompt(uN, password, mPassword *string, labels *[]string) error { } func PromptForUsername() (string, error) { - validate := func(input string) error { - if len(input) < 3 { - return errors.New("username must have more than 3 characters") - } - return nil - } - return PromptForString("Username ", validate) + return PromptForString("Username ", userNameValidator) +} + +func PromptForUsernameWithDefault(defaultVal string) (string, error) { + return PromptForStringWithDefault("Username ", defaultVal, userNameValidator) } func PromptForLabels() ([]string, error) { @@ -204,34 +258,15 @@ func PromptForLabels() ([]string, error) { return l, nil } - func PromptForNewMPassword() (string, error) { - validate := func(input string) error { - if len(input) < 6 { - return errors.New("new master password must have more than 6 characters") - } - return nil - } - return promptForPassword("New Master password ", validate) + return promptForPassword("New Master password ", newMasterPasswordValidator) } func PromptForMPassword() (string, error) { - validate := func(input string) error { - if len(input) < 6 { - return errors.New("master password must have more than 6 characters") - } - return nil - } - return promptForPassword("Master password ", validate) + return promptForPassword("Master password ", masterPasswordValidator) } // PromptForPassword function prompt for password and returns the input func PromptForPassword() (string, error) { - validate := func(input string) error { - if len(input) < 6 { - return errors.New("password must have more than 6 characters") - } - return nil - } - return promptForPassword("Password ", validate) + return promptForPassword("Password ", userPasswordValidator) } diff --git a/pkg/passwords/password_repo.go b/pkg/passwords/password_repo.go index 9723a29..c23a1f1 100644 --- a/pkg/passwords/password_repo.go +++ b/pkg/passwords/password_repo.go @@ -44,6 +44,7 @@ var ( ErrorCannotSavePasswordDB = func(err error) error { return errors.Wrap(err, "cannot save password") } + ErrorNoPasswords = errors.New("no passwords are available") ) // PasswordDB struct represents password db @@ -206,7 +207,7 @@ func (p *PasswordRepository) Add(id, uN, password string, labels []string) error err = p.savePasswordDB() if err != nil { - return errors.Wrap(err, "cannot save passoword") + return ErrorCannotSavePasswordDB(err) } return nil } @@ -223,21 +224,16 @@ func (p *PasswordRepository) isLabelExists(l string) bool { // GetPassword method retrieve password entry from Password db func (p *PasswordRepository) GetPassword(id string, showPassword bool) error { - passwordDB := p.db.Entries - if len(passwordDB) == 0 { - return errors.New("no passwords are available") - } - var result PasswordEntry - result, ok := passwordDB[id] - if !ok { - return ErrorInvalidID(id) + passwordEntry, err := p.GetPasswordEntry(id) + if err != nil { + return err } - fmt.Println(fmt.Sprintf("Username: %s", result.Username)) + fmt.Println(fmt.Sprintf("Username: %s", passwordEntry.Username)) if showPassword { - fmt.Println(fmt.Sprintf("Password: %s", result.Password)) + fmt.Println(fmt.Sprintf("Password: %s", passwordEntry.Password)) } else { fmt.Println("Password is copied to the clip board") - err := clipboard.WriteAll(result.Password) + err := clipboard.WriteAll(passwordEntry.Password) if err != nil { return errors.Wrapf(err, "cannot write to clip board") } @@ -245,10 +241,36 @@ func (p *PasswordRepository) GetPassword(id string, showPassword bool) error { return nil } +func (p *PasswordRepository) GetPasswordEntry(id string) (PasswordEntry, error) { + passwordDB := p.db.Entries + if len(passwordDB) == 0 { + return PasswordEntry{}, ErrorNoPasswords + } + var result PasswordEntry + result, ok := passwordDB[id] + if !ok { + return PasswordEntry{}, ErrorInvalidID(id) + } + return result, nil +} + +func (p *PasswordRepository) ChangePasswordEntry(id string, entry PasswordEntry) error { + passwordDB := p.db.Entries + if len(passwordDB) == 0 { + return ErrorNoPasswords + } + passwordDB[id] = entry + err := p.savePasswordDB() + if err != nil { + return ErrorCannotSavePasswordDB(err) + } + return nil +} + // SearchID will return the password entries if the password ID contains the provide key func (p *PasswordRepository) SearchID(id string, showPassword bool) ([]string, error) { if p.isDBEmpty() { - return nil, errors.New("no passwords are available") + return nil, ErrorNoPasswords } var result []string for key := range p.db.Entries { @@ -269,7 +291,7 @@ func (p *PasswordRepository) isDBEmpty() bool { // SearchLabel will return the password ids if the password labels contains the provide label func (p *PasswordRepository) SearchLabel(label string, showPassword bool) ([]string, error) { if p.isDBEmpty() { - return nil, errors.New("no passwords are available") + return nil, ErrorNoPasswords } var ids []string for key, val := range p.db.Labels { @@ -293,7 +315,7 @@ func (p *PasswordRepository) assignLabels(id string, labels []string) { func (p *PasswordRepository) Remove(id string) error { if p.isDBEmpty() { - return errors.New("no passwords are available") + return ErrorNoPasswords } if ! p.isIDExists(id) { return ErrorInvalidID(id)