From 5508434563f16ab06a7540dc34b6619ad5aaae35 Mon Sep 17 00:00:00 2001 From: quinton11 Date: Tue, 4 Apr 2023 16:27:47 +0000 Subject: [PATCH 1/9] feat: CLI support for multiple user accounts logins See #340 --- cli/packages/cmd/login.go | 31 +++++++++++-- cli/packages/cmd/switch.go | 93 +++++++++++++++++++++++++++++++++++++ cli/packages/models/cli.go | 5 +- cli/packages/util/common.go | 5 +- cli/packages/util/config.go | 22 +++++++-- cli/packages/util/helper.go | 9 ++++ 6 files changed, 152 insertions(+), 13 deletions(-) create mode 100644 cli/packages/cmd/switch.go diff --git a/cli/packages/cmd/login.go b/cli/packages/cmd/login.go index c93af44f5..2a4729abe 100644 --- a/cli/packages/cmd/login.go +++ b/cli/packages/cmd/login.go @@ -48,14 +48,24 @@ var loginCmd = &cobra.Command{ util.HandleError(err) } - if currentLoggedInUserDetails.IsUserLoggedIn && !currentLoggedInUserDetails.LoginExpired && len(currentLoggedInUserDetails.UserCredentials.PrivateKey) != 0 { - shouldOverride, err := shouldOverrideLoginPrompt(currentLoggedInUserDetails.UserCredentials.Email) + addUser := false + if currentLoggedInUserDetails.UserCredentials.Email != "" { + addUser, err = addNewUserPrompt() if err != nil { util.HandleError(err) } + } - if !shouldOverride { - return + if !addUser { + if currentLoggedInUserDetails.IsUserLoggedIn && !currentLoggedInUserDetails.LoginExpired && len(currentLoggedInUserDetails.UserCredentials.PrivateKey) != 0 { + shouldOverride, err := shouldOverrideLoginPrompt(currentLoggedInUserDetails.UserCredentials.Email) + if err != nil { + util.HandleError(err) + } + + if !shouldOverride { + return + } } } @@ -342,6 +352,19 @@ func getFreshUserCredentials(email string, password string) (*api.GetLoginOneV2R return &loginOneResponseResult, &loginTwoResponseResult, nil } +func addNewUserPrompt() (bool, error) { + prompt := promptui.Select{ + Label: "Infisical detects previous logged in users. Would you like to add a new user? Select[Yes/No]", + Items: []string{"No", "Yes"}, + } + + _, result, err := prompt.Run() + if err != nil { + return false, err + } + return result == "Yes", err +} + func shouldOverrideLoginPrompt(currentLoggedInUserEmail string) (bool, error) { prompt := promptui.Select{ Label: fmt.Sprintf("There seems to be a user already logged in with the email: %s. Would you like to override that login? Select[Yes/No]", currentLoggedInUserEmail), diff --git a/cli/packages/cmd/switch.go b/cli/packages/cmd/switch.go new file mode 100644 index 000000000..e32338765 --- /dev/null +++ b/cli/packages/cmd/switch.go @@ -0,0 +1,93 @@ +package cmd + +import ( + "errors" + + "github.com/Infisical/infisical-merge/packages/util" + "github.com/manifoldco/promptui" + "github.com/spf13/cobra" +) + +var switchCmd = &cobra.Command{ + Use: "switch", + Short: "Used to switch between Infisical profiles", + DisableFlagsInUseLine: true, + Example: "infisical switch", + Args: cobra.ExactArgs(0), + PreRun: func(cmd *cobra.Command, args []string) { + util.RequireLogin() + }, + Run: func(cmd *cobra.Command, args []string) { + //get previous logged in profiles + loggedInProfiles, err := getLoggedInUsers() + if err != nil { + util.HandleError(err, "[infisical switch]: Unable to get logged Profiles") + } + + //prompt user + profile, err := LoggedInUsersPrompt(loggedInProfiles) + if err != nil { + util.HandleError(err, "[infisical switch]: Prompt error") + } + + //write to config file + configFile, err := util.GetConfigFile() + if err != nil { + util.HandleError(err, "[infisical switch]: Unable to get config file") + } + + configFile.LoggedInUserEmail = profile + ok := util.Contains(configFile.LoggedInUsersEmail, profile) + if !ok { + configFile.LoggedInUsersEmail = append(configFile.LoggedInUsersEmail, profile) + } + + err = util.WriteConfigFile(&configFile) + if err != nil { + util.HandleError(err, "") + } + }, +} + +func init() { + rootCmd.AddCommand(switchCmd) +} + +func getLoggedInUsers() ([]string, error) { + loggedInProfiles := []string{} + + if util.ConfigFileExists() { + configFile, err := util.GetConfigFile() + if err != nil { + return loggedInProfiles, err + } + + //get logged in profiles + // + if configFile.LoggedInUsersEmail == nil { + loggedInProfiles = append(loggedInProfiles, configFile.LoggedInUserEmail) + } else { + if len(configFile.LoggedInUsersEmail) > 0 { + loggedInProfiles = append(loggedInProfiles, configFile.LoggedInUsersEmail...) + } + } + return loggedInProfiles, nil + } else { + //empty + return loggedInProfiles, errors.New("couldn't retrieve config file") + } +} + +func LoggedInUsersPrompt(profiles []string) (string, error) { + prompt := promptui.Select{Label: "Which of your Infisical profiles would you like to use", + Items: profiles, + Size: 7, + } + + idx, _, err := prompt.Run() + if err != nil { + return "", err + } + + return profiles[idx], nil +} diff --git a/cli/packages/models/cli.go b/cli/packages/models/cli.go index 5e969a9c0..ce5838f43 100644 --- a/cli/packages/models/cli.go +++ b/cli/packages/models/cli.go @@ -12,8 +12,9 @@ type UserCredentials struct { // The file struct for Infisical config file type ConfigFile struct { - LoggedInUserEmail string `json:"loggedInUserEmail"` - VaultBackendType keyring.BackendType `json:"vaultBackendType"` + LoggedInUserEmail string `json:"loggedInUserEmail"` + VaultBackendType keyring.BackendType `json:"vaultBackendType"` + LoggedInUsersEmail []string `json:"loggedInUsersEmail,omitempty"` } type SingleEnvironmentVariable struct { diff --git a/cli/packages/util/common.go b/cli/packages/util/common.go index 13e76046d..2b57383ef 100644 --- a/cli/packages/util/common.go +++ b/cli/packages/util/common.go @@ -23,8 +23,5 @@ func WriteToFile(fileName string, dataToWrite []byte, filePerm os.FileMode) erro func CheckIsConnectedToInternet() (ok bool) { _, err := http.Get("http://clients3.google.com/generate_204") - if err != nil { - return false - } - return true + return err == nil } diff --git a/cli/packages/util/config.go b/cli/packages/util/config.go index e7b6c8d97..f23d24d8a 100644 --- a/cli/packages/util/config.go +++ b/cli/packages/util/config.go @@ -31,9 +31,25 @@ func WriteInitalConfig(userCredentials *models.UserCredentials) error { return fmt.Errorf("writeInitalConfig: unable to write config file because [err=%s]", err) } + //if empty + if existingConfigFile.LoggedInUsersEmail == nil { + existingConfigFile.LoggedInUsersEmail = []string{} + } + + //if profile exists + if len(existingConfigFile.LoggedInUsersEmail) > 0 { + ok := Contains(existingConfigFile.LoggedInUsersEmail, userCredentials.Email) + if !ok { + existingConfigFile.LoggedInUsersEmail = append(existingConfigFile.LoggedInUsersEmail, userCredentials.Email) + } + } else { + existingConfigFile.LoggedInUsersEmail = append(existingConfigFile.LoggedInUsersEmail, userCredentials.Email) + } + configFile := models.ConfigFile{ - LoggedInUserEmail: userCredentials.Email, - VaultBackendType: existingConfigFile.VaultBackendType, + LoggedInUserEmail: userCredentials.Email, + VaultBackendType: existingConfigFile.VaultBackendType, + LoggedInUsersEmail: existingConfigFile.LoggedInUsersEmail, } configFileMarshalled, err := json.Marshal(configFile) @@ -176,7 +192,7 @@ func GetConfigFile() (models.ConfigFile, error) { return configFile, nil } -// Write a ConfigFile to disk. Raise error if unable to save the model to ask +// Write a ConfigFile to disk. Raise error if unable to save the model to disk func WriteConfigFile(configFile *models.ConfigFile) error { fullConfigFilePath, fullConfigFileDirPath, err := GetFullConfigFilePath() if err != nil { diff --git a/cli/packages/util/helper.go b/cli/packages/util/helper.go index 76b6e3e46..336b368df 100644 --- a/cli/packages/util/helper.go +++ b/cli/packages/util/helper.go @@ -61,6 +61,15 @@ func IsSecretTypeValid(s string) bool { return false } +func Contains(iter []string, elem string) bool { + for _, value := range iter { + if value == elem { + return true + } + } + return false +} + func RequireLogin() { currentUserDetails, err := GetCurrentLoggedInUserDetails() From 00442992d2df994fe32a049eeb281ff522c2a897 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Fri, 14 Apr 2023 20:43:58 -0700 Subject: [PATCH 2/9] Add user main command and add switch as sub command --- cli/packages/cmd/login.go | 3 ++- cli/packages/cmd/{switch.go => user.go} | 12 +++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) rename cli/packages/cmd/{switch.go => user.go} (86%) diff --git a/cli/packages/cmd/login.go b/cli/packages/cmd/login.go index 2a4729abe..da16f196d 100644 --- a/cli/packages/cmd/login.go +++ b/cli/packages/cmd/login.go @@ -354,7 +354,7 @@ func getFreshUserCredentials(email string, password string) (*api.GetLoginOneV2R func addNewUserPrompt() (bool, error) { prompt := promptui.Select{ - Label: "Infisical detects previous logged in users. Would you like to add a new user? Select[Yes/No]", + Label: "A user is already logged in. Would you like to add a new user? Select[Yes/No]", Items: []string{"No", "Yes"}, } @@ -362,6 +362,7 @@ func addNewUserPrompt() (bool, error) { if err != nil { return false, err } + return result == "Yes", err } diff --git a/cli/packages/cmd/switch.go b/cli/packages/cmd/user.go similarity index 86% rename from cli/packages/cmd/switch.go rename to cli/packages/cmd/user.go index e32338765..49c2db6b5 100644 --- a/cli/packages/cmd/switch.go +++ b/cli/packages/cmd/user.go @@ -8,6 +8,15 @@ import ( "github.com/spf13/cobra" ) +var userCmd = &cobra.Command{ + Use: "user", + Short: "Used manage user credentials", + DisableFlagsInUseLine: true, + Example: "infisical user", + Args: cobra.ExactArgs(0), + Run: func(cmd *cobra.Command, args []string) {}, +} + var switchCmd = &cobra.Command{ Use: "switch", Short: "Used to switch between Infisical profiles", @@ -50,7 +59,8 @@ var switchCmd = &cobra.Command{ } func init() { - rootCmd.AddCommand(switchCmd) + userCmd.AddCommand(switchCmd) + rootCmd.AddCommand(userCmd) } func getLoggedInUsers() ([]string, error) { From fe115a7476bdc5a993ce52999be98703af18c98d Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Fri, 14 Apr 2023 21:37:32 -0700 Subject: [PATCH 3/9] Update user menu --- cli/packages/cmd/login.go | 40 ++++++++++++++++++++----------------- cli/packages/util/config.go | 4 ++-- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/cli/packages/cmd/login.go b/cli/packages/cmd/login.go index da16f196d..d7db04b21 100644 --- a/cli/packages/cmd/login.go +++ b/cli/packages/cmd/login.go @@ -33,6 +33,10 @@ type params struct { keyLength uint32 } +const ADD_USER = "Add a new account login" +const REPLACE_USER = "Override current logged in user" +const EXIT_USER_MENU = "Exit" + // loginCmd represents the login command var loginCmd = &cobra.Command{ Use: "login", @@ -48,26 +52,26 @@ var loginCmd = &cobra.Command{ util.HandleError(err) } - addUser := false - if currentLoggedInUserDetails.UserCredentials.Email != "" { - addUser, err = addNewUserPrompt() + // addUser := false + // if currentLoggedInUserDetails.UserCredentials.Email != "" { + // addUser, err = addNewUserPrompt() + // if err != nil { + // util.HandleError(err) + // } + // } + + // if !addUser { + if currentLoggedInUserDetails.IsUserLoggedIn && !currentLoggedInUserDetails.LoginExpired && len(currentLoggedInUserDetails.UserCredentials.PrivateKey) != 0 { + shouldOverride, err := userLoginMenu(currentLoggedInUserDetails.UserCredentials.Email) if err != nil { util.HandleError(err) } - } - if !addUser { - if currentLoggedInUserDetails.IsUserLoggedIn && !currentLoggedInUserDetails.LoginExpired && len(currentLoggedInUserDetails.UserCredentials.PrivateKey) != 0 { - shouldOverride, err := shouldOverrideLoginPrompt(currentLoggedInUserDetails.UserCredentials.Email) - if err != nil { - util.HandleError(err) - } - - if !shouldOverride { - return - } + if !shouldOverride { + return } } + // } email, password, err := askForLoginCredentials() if err != nil { @@ -366,16 +370,16 @@ func addNewUserPrompt() (bool, error) { return result == "Yes", err } -func shouldOverrideLoginPrompt(currentLoggedInUserEmail string) (bool, error) { +func userLoginMenu(currentLoggedInUserEmail string) (bool, error) { prompt := promptui.Select{ - Label: fmt.Sprintf("There seems to be a user already logged in with the email: %s. Would you like to override that login? Select[Yes/No]", currentLoggedInUserEmail), - Items: []string{"No", "Yes"}, + Label: fmt.Sprintf("Current logged in user email: %s", currentLoggedInUserEmail), + Items: []string{ADD_USER, REPLACE_USER, EXIT_USER_MENU}, } _, result, err := prompt.Run() if err != nil { return false, err } - return result == "Yes", err + return result != EXIT_USER_MENU, err } func generateFromPassword(password string, salt []byte, p *params) (hash []byte, err error) { diff --git a/cli/packages/util/config.go b/cli/packages/util/config.go index f23d24d8a..c1b57b67d 100644 --- a/cli/packages/util/config.go +++ b/cli/packages/util/config.go @@ -31,12 +31,12 @@ func WriteInitalConfig(userCredentials *models.UserCredentials) error { return fmt.Errorf("writeInitalConfig: unable to write config file because [err=%s]", err) } - //if empty + //if empty, initialize if existingConfigFile.LoggedInUsersEmail == nil { existingConfigFile.LoggedInUsersEmail = []string{} } - //if profile exists + //if profiles exists if len(existingConfigFile.LoggedInUsersEmail) > 0 { ok := Contains(existingConfigFile.LoggedInUsersEmail, userCredentials.Email) if !ok { From 954335bd3714a6769eb630d2b2b7ede0adb770eb Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Fri, 14 Apr 2023 21:37:57 -0700 Subject: [PATCH 4/9] remove addNewUserPrompt --- cli/packages/cmd/login.go | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/cli/packages/cmd/login.go b/cli/packages/cmd/login.go index d7db04b21..ebdb86cd1 100644 --- a/cli/packages/cmd/login.go +++ b/cli/packages/cmd/login.go @@ -356,20 +356,6 @@ func getFreshUserCredentials(email string, password string) (*api.GetLoginOneV2R return &loginOneResponseResult, &loginTwoResponseResult, nil } -func addNewUserPrompt() (bool, error) { - prompt := promptui.Select{ - Label: "A user is already logged in. Would you like to add a new user? Select[Yes/No]", - Items: []string{"No", "Yes"}, - } - - _, result, err := prompt.Run() - if err != nil { - return false, err - } - - return result == "Yes", err -} - func userLoginMenu(currentLoggedInUserEmail string) (bool, error) { prompt := promptui.Select{ Label: fmt.Sprintf("Current logged in user email: %s", currentLoggedInUserEmail), From be2cf54d6e138b8d82a5310be941f43310b9823f Mon Sep 17 00:00:00 2001 From: quinton11 Date: Tue, 18 Apr 2023 12:03:03 +0000 Subject: [PATCH 5/9] host API support for login and switch commands --- cli/packages/cmd/login.go | 53 +++++++++++++ cli/packages/cmd/user.go | 129 ++++++++++++++++++++++++++++--- cli/packages/models/cli.go | 8 +- cli/packages/util/config.go | 21 ++--- cli/packages/util/credentials.go | 7 ++ cli/packages/util/helper.go | 6 +- 6 files changed, 201 insertions(+), 23 deletions(-) diff --git a/cli/packages/cmd/login.go b/cli/packages/cmd/login.go index ebdb86cd1..93f5bf5e1 100644 --- a/cli/packages/cmd/login.go +++ b/cli/packages/cmd/login.go @@ -10,9 +10,11 @@ import ( "errors" "fmt" + "net/url" "regexp" "github.com/Infisical/infisical-merge/packages/api" + "github.com/Infisical/infisical-merge/packages/config" "github.com/Infisical/infisical-merge/packages/crypto" "github.com/Infisical/infisical-merge/packages/models" "github.com/Infisical/infisical-merge/packages/srp" @@ -73,6 +75,12 @@ var loginCmd = &cobra.Command{ } // } + //prompt user to select domain between Infisical cloud and self hosting + err = askForDomain() + if err != nil { + util.HandleError(err, "Unable to parse domain url") + } + email, password, err := askForLoginCredentials() if err != nil { util.HandleError(err, "Unable to parse email and password for authentication") @@ -266,6 +274,51 @@ func init() { rootCmd.AddCommand(loginCmd) } +func askForDomain() error { + //query user to choose between Infisical cloud or self hosting + options := []string{"Infisical Cloud", "Self Hosting"} + optionsPrompt := promptui.Select{ + Label: "Select your hosting option", + Items: options, + Size: 2, + } + + idx, _, err := optionsPrompt.Run() + if err != nil { + return err + } + + if idx == 0 { + //cloud option + config.INFISICAL_URL = util.INFISICAL_DEFAULT_API_URL + return nil + } + + urlValidation := func(input string) error { + _, err := url.ParseRequestURI(input) + if err != nil { + return errors.New("this is an invalid url") + } + return nil + } + + //else run prompt to enter domain + domainPrompt := promptui.Prompt{ + Label: "Domain", + Validate: urlValidation, + } + + domain, err := domainPrompt.Run() + if err != nil { + return err + } + + //set api url + config.INFISICAL_URL = domain + //return nil + return nil +} + func askForLoginCredentials() (email string, password string, err error) { validateEmail := func(input string) error { matched, err := regexp.MatchString("^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$", input) diff --git a/cli/packages/cmd/user.go b/cli/packages/cmd/user.go index 49c2db6b5..93b32eb3f 100644 --- a/cli/packages/cmd/user.go +++ b/cli/packages/cmd/user.go @@ -2,7 +2,10 @@ package cmd import ( "errors" + "net/url" + "github.com/Infisical/infisical-merge/packages/config" + "github.com/Infisical/infisical-merge/packages/models" "github.com/Infisical/infisical-merge/packages/util" "github.com/manifoldco/promptui" "github.com/spf13/cobra" @@ -30,25 +33,43 @@ var switchCmd = &cobra.Command{ //get previous logged in profiles loggedInProfiles, err := getLoggedInUsers() if err != nil { - util.HandleError(err, "[infisical switch]: Unable to get logged Profiles") + util.HandleError(err, "[infisical user switch]: Unable to get logged Profiles") } //prompt user profile, err := LoggedInUsersPrompt(loggedInProfiles) if err != nil { - util.HandleError(err, "[infisical switch]: Prompt error") + util.HandleError(err, "[infisical user switch]: Prompt error") } //write to config file configFile, err := util.GetConfigFile() if err != nil { - util.HandleError(err, "[infisical switch]: Unable to get config file") + util.HandleError(err, "[infisical user switch]: Unable to get config file") } configFile.LoggedInUserEmail = profile - ok := util.Contains(configFile.LoggedInUsersEmail, profile) + + //set logged in user domain + ok := util.ConfigContainsEmail(configFile.LoggedInUsers, profile) + if !ok { - configFile.LoggedInUsersEmail = append(configFile.LoggedInUsersEmail, profile) + //profile not in loggedInUsers + configFile.LoggedInUsers = append(configFile.LoggedInUsers, models.LoggedInUser{ + Email: profile, + Domain: config.INFISICAL_URL, + }) + //set logged in user domain + configFile.LoggedInUserDomain = config.INFISICAL_URL + + } else { + //exists, set logged in user domain + for _, v := range configFile.LoggedInUsers { + if profile == v.Email { + configFile.LoggedInUserDomain = v.Domain + break + } + } } err = util.WriteConfigFile(&configFile) @@ -58,7 +79,71 @@ var switchCmd = &cobra.Command{ }, } +var domainCmd = &cobra.Command{ + Use: "domain", + Short: "Used to update the domain of an Infisical profile", + DisableFlagsInUseLine: true, + Example: "infisical user domain", + Args: cobra.ExactArgs(0), + PreRun: func(cmd *cobra.Command, args []string) { + util.RequireLogin() + }, + Run: func(cmd *cobra.Command, args []string) { + //prompt for profiles selection + loggedInProfiles, err := getLoggedInUsers() + if err != nil { + util.HandleError(err, "[infisical user domain]: Unable to get logged Profiles") + } + + //prompt user + profile, err := LoggedInUsersPrompt(loggedInProfiles) + if err != nil { + util.HandleError(err, "[infisical user domain]: Prompt error") + } + + //prompt to update domain + domain, err := NewDomainPrompt() + if err != nil { + util.HandleError(err, "[infisical user domain]: Prompt error") + } + + //write to config file + configFile, err := util.GetConfigFile() + if err != nil { + util.HandleError(err, "[infisical user]: Unable to get config file") + } + + //check if profile in logged in profiles + + //if not add new profile loggedInUsers + //else update profile from loggedinUsers list + ok := util.ConfigContainsEmail(configFile.LoggedInUsers, profile) + if !ok { + configFile.LoggedInUsers = append(configFile.LoggedInUsers, models.LoggedInUser{ + Email: profile, + Domain: domain, + }) + } else { + //exists, set logged in user domain + for _, v := range configFile.LoggedInUsers { + if profile == v.Email { + v.Domain = domain + break + } + } + + } + //check if current loggedinuser is selected profile + //if yes set current domain to changed domain + if configFile.LoggedInUserEmail == profile { + configFile.LoggedInUserDomain = domain + } + + }, +} + func init() { + userCmd.AddCommand(domainCmd) userCmd.AddCommand(switchCmd) rootCmd.AddCommand(userCmd) } @@ -74,12 +159,13 @@ func getLoggedInUsers() ([]string, error) { //get logged in profiles // - if configFile.LoggedInUsersEmail == nil { - loggedInProfiles = append(loggedInProfiles, configFile.LoggedInUserEmail) - } else { - if len(configFile.LoggedInUsersEmail) > 0 { - loggedInProfiles = append(loggedInProfiles, configFile.LoggedInUsersEmail...) + if len(configFile.LoggedInUsers) > 0 { + for _, v := range configFile.LoggedInUsers { + loggedInProfiles = append(loggedInProfiles, v.Email) } + } else { + + loggedInProfiles = append(loggedInProfiles, configFile.LoggedInUserEmail) } return loggedInProfiles, nil } else { @@ -88,6 +174,29 @@ func getLoggedInUsers() ([]string, error) { } } +func NewDomainPrompt() (string, error) { + urlValidation := func(input string) error { + _, err := url.ParseRequestURI(input) + if err != nil { + return errors.New("this is an invalid url") + } + return nil + } + + //else run prompt to enter domain + domainPrompt := promptui.Prompt{ + Label: "New Domain", + Validate: urlValidation, + } + + domain, err := domainPrompt.Run() + if err != nil { + return "", err + } + + return domain, nil +} + func LoggedInUsersPrompt(profiles []string) (string, error) { prompt := promptui.Select{Label: "Which of your Infisical profiles would you like to use", Items: profiles, diff --git a/cli/packages/models/cli.go b/cli/packages/models/cli.go index ce5838f43..b9b0ab7b5 100644 --- a/cli/packages/models/cli.go +++ b/cli/packages/models/cli.go @@ -13,8 +13,14 @@ type UserCredentials struct { // The file struct for Infisical config file type ConfigFile struct { LoggedInUserEmail string `json:"loggedInUserEmail"` + LoggedInUserDomain string `json:"LoggedInUserDomain,omitempty"` VaultBackendType keyring.BackendType `json:"vaultBackendType"` - LoggedInUsersEmail []string `json:"loggedInUsersEmail,omitempty"` + LoggedInUsers []LoggedInUser `json:"loggedInUsers,omitempty"` +} + +type LoggedInUser struct { + Email string `json:"email"` + Domain string `json:"domain"` } type SingleEnvironmentVariable struct { diff --git a/cli/packages/util/config.go b/cli/packages/util/config.go index c1b57b67d..ef039443c 100644 --- a/cli/packages/util/config.go +++ b/cli/packages/util/config.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" + "github.com/Infisical/infisical-merge/packages/config" "github.com/Infisical/infisical-merge/packages/models" log "github.com/sirupsen/logrus" ) @@ -31,25 +32,25 @@ func WriteInitalConfig(userCredentials *models.UserCredentials) error { return fmt.Errorf("writeInitalConfig: unable to write config file because [err=%s]", err) } - //if empty, initialize - if existingConfigFile.LoggedInUsersEmail == nil { - existingConfigFile.LoggedInUsersEmail = []string{} - } - //if profiles exists - if len(existingConfigFile.LoggedInUsersEmail) > 0 { - ok := Contains(existingConfigFile.LoggedInUsersEmail, userCredentials.Email) + loggedInUser := models.LoggedInUser{ + Email: userCredentials.Email, + Domain: config.INFISICAL_URL, + } + if len(existingConfigFile.LoggedInUsers) > 0 { + ok := ConfigContainsEmail(existingConfigFile.LoggedInUsers, userCredentials.Email) if !ok { - existingConfigFile.LoggedInUsersEmail = append(existingConfigFile.LoggedInUsersEmail, userCredentials.Email) + existingConfigFile.LoggedInUsers = append(existingConfigFile.LoggedInUsers, loggedInUser) } } else { - existingConfigFile.LoggedInUsersEmail = append(existingConfigFile.LoggedInUsersEmail, userCredentials.Email) + existingConfigFile.LoggedInUsers = append(existingConfigFile.LoggedInUsers, loggedInUser) } configFile := models.ConfigFile{ LoggedInUserEmail: userCredentials.Email, + LoggedInUserDomain: config.INFISICAL_URL, VaultBackendType: existingConfigFile.VaultBackendType, - LoggedInUsersEmail: existingConfigFile.LoggedInUsersEmail, + LoggedInUsers: existingConfigFile.LoggedInUsers, } configFileMarshalled, err := json.Marshal(configFile) diff --git a/cli/packages/util/credentials.go b/cli/packages/util/credentials.go index 1766f04c7..b06ed4ef6 100644 --- a/cli/packages/util/credentials.go +++ b/cli/packages/util/credentials.go @@ -6,6 +6,7 @@ import ( "github.com/99designs/keyring" "github.com/Infisical/infisical-merge/packages/api" + "github.com/Infisical/infisical-merge/packages/config" "github.com/Infisical/infisical-merge/packages/models" "github.com/go-resty/resty/v2" ) @@ -87,6 +88,12 @@ func GetCurrentLoggedInUserDetails() (LoggedInUserDetails, error) { SetAuthToken(userCreds.JTWToken). SetHeader("Accept", "application/json") + //configFile.LoggedInUserDomain + //if not empty set as infisical url + if configFile.LoggedInUserDomain != "" { + config.INFISICAL_URL = configFile.LoggedInUserDomain + } + isAuthenticated := api.CallIsAuthenticated(httpClient) if !isAuthenticated { return LoggedInUserDetails{ diff --git a/cli/packages/util/helper.go b/cli/packages/util/helper.go index 336b368df..46928fbc4 100644 --- a/cli/packages/util/helper.go +++ b/cli/packages/util/helper.go @@ -9,6 +9,8 @@ import ( "os/exec" "path" "strings" + + "github.com/Infisical/infisical-merge/packages/models" ) type DecodedSymmetricEncryptionDetails = struct { @@ -61,9 +63,9 @@ func IsSecretTypeValid(s string) bool { return false } -func Contains(iter []string, elem string) bool { +func ConfigContainsEmail(iter []models.LoggedInUser, elem string) bool { for _, value := range iter { - if value == elem { + if value.Email == elem { return true } } From c1b97841cfcdc988c3bf71ba94bb00105b30e102 Mon Sep 17 00:00:00 2001 From: quinton11 Date: Wed, 19 Apr 2023 11:29:04 +0000 Subject: [PATCH 6/9] resolved review concerns --- cli/packages/cmd/login.go | 32 ++++++++++++++++---------------- cli/packages/cmd/user.go | 37 +++++++++++++++++++++++++++++-------- cli/packages/util/config.go | 16 ++++++++++------ cli/packages/util/helper.go | 7 ++++--- 4 files changed, 59 insertions(+), 33 deletions(-) diff --git a/cli/packages/cmd/login.go b/cli/packages/cmd/login.go index 93f5bf5e1..58d66b17f 100644 --- a/cli/packages/cmd/login.go +++ b/cli/packages/cmd/login.go @@ -54,15 +54,6 @@ var loginCmd = &cobra.Command{ util.HandleError(err) } - // addUser := false - // if currentLoggedInUserDetails.UserCredentials.Email != "" { - // addUser, err = addNewUserPrompt() - // if err != nil { - // util.HandleError(err) - // } - // } - - // if !addUser { if currentLoggedInUserDetails.IsUserLoggedIn && !currentLoggedInUserDetails.LoginExpired && len(currentLoggedInUserDetails.UserCredentials.PrivateKey) != 0 { shouldOverride, err := userLoginMenu(currentLoggedInUserDetails.UserCredentials.Email) if err != nil { @@ -73,7 +64,6 @@ var loginCmd = &cobra.Command{ return } } - // } //prompt user to select domain between Infisical cloud and self hosting err = askForDomain() @@ -276,19 +266,24 @@ func init() { func askForDomain() error { //query user to choose between Infisical cloud or self hosting - options := []string{"Infisical Cloud", "Self Hosting"} + var ( + INFISICAL_CLOUD = "Infisical Cloud" + SELF_HOSTING = "Self Hosting" + ) + + options := []string{INFISICAL_CLOUD, SELF_HOSTING} optionsPrompt := promptui.Select{ Label: "Select your hosting option", Items: options, Size: 2, } - idx, _, err := optionsPrompt.Run() + _, selectedHostingOption, err := optionsPrompt.Run() if err != nil { return err } - if idx == 0 { + if selectedHostingOption == INFISICAL_CLOUD { //cloud option config.INFISICAL_URL = util.INFISICAL_DEFAULT_API_URL return nil @@ -302,10 +297,10 @@ func askForDomain() error { return nil } - //else run prompt to enter domain domainPrompt := promptui.Prompt{ Label: "Domain", Validate: urlValidation, + Default: "Example - https://my-domain-example.com", } domain, err := domainPrompt.Run() @@ -314,7 +309,7 @@ func askForDomain() error { } //set api url - config.INFISICAL_URL = domain + config.INFISICAL_URL = fmt.Sprintf("%s/api", domain) //return nil return nil } @@ -410,8 +405,13 @@ func getFreshUserCredentials(email string, password string) (*api.GetLoginOneV2R } func userLoginMenu(currentLoggedInUserEmail string) (bool, error) { + label := fmt.Sprintf("Current logged in user email: %s", currentLoggedInUserEmail) + if config.INFISICAL_URL != "" { + label = fmt.Sprintf("%s on domain: %s", label, config.INFISICAL_URL) + } + prompt := promptui.Select{ - Label: fmt.Sprintf("Current logged in user email: %s", currentLoggedInUserEmail), + Label: label, Items: []string{ADD_USER, REPLACE_USER, EXIT_USER_MENU}, } _, result, err := prompt.Run() diff --git a/cli/packages/cmd/user.go b/cli/packages/cmd/user.go index 93b32eb3f..5cdd56d93 100644 --- a/cli/packages/cmd/user.go +++ b/cli/packages/cmd/user.go @@ -2,6 +2,7 @@ package cmd import ( "errors" + "fmt" "net/url" "github.com/Infisical/infisical-merge/packages/config" @@ -79,11 +80,20 @@ var switchCmd = &cobra.Command{ }, } +var updateCmd = &cobra.Command{ + Use: "update", + Short: "Used to update properties of an Infisical profile", + DisableFlagsInUseLine: true, + Example: "infisical user update", + Args: cobra.ExactArgs(0), + Run: func(cmd *cobra.Command, args []string) {}, +} + var domainCmd = &cobra.Command{ Use: "domain", Short: "Used to update the domain of an Infisical profile", DisableFlagsInUseLine: true, - Example: "infisical user domain", + Example: "infisical user update domain", Args: cobra.ExactArgs(0), PreRun: func(cmd *cobra.Command, args []string) { util.RequireLogin() @@ -92,25 +102,25 @@ var domainCmd = &cobra.Command{ //prompt for profiles selection loggedInProfiles, err := getLoggedInUsers() if err != nil { - util.HandleError(err, "[infisical user domain]: Unable to get logged Profiles") + util.HandleError(err, "[infisical user update domain]: Unable to get logged Profiles") } //prompt user profile, err := LoggedInUsersPrompt(loggedInProfiles) if err != nil { - util.HandleError(err, "[infisical user domain]: Prompt error") + util.HandleError(err, "[infisical user update domain]: Prompt error") } //prompt to update domain domain, err := NewDomainPrompt() if err != nil { - util.HandleError(err, "[infisical user domain]: Prompt error") + util.HandleError(err, "[infisical user update domain]: Prompt error") } //write to config file configFile, err := util.GetConfigFile() if err != nil { - util.HandleError(err, "[infisical user]: Unable to get config file") + util.HandleError(err, "[infisical user update domain]: Unable to get config file") } //check if profile in logged in profiles @@ -125,9 +135,9 @@ var domainCmd = &cobra.Command{ }) } else { //exists, set logged in user domain - for _, v := range configFile.LoggedInUsers { + for idx, v := range configFile.LoggedInUsers { if profile == v.Email { - v.Domain = domain + configFile.LoggedInUsers[idx].Domain = domain //inplace break } } @@ -139,15 +149,23 @@ var domainCmd = &cobra.Command{ configFile.LoggedInUserDomain = domain } + err = util.WriteConfigFile(&configFile) + if err != nil { + util.HandleError(err, "") + } + }, } func init() { - userCmd.AddCommand(domainCmd) + updateCmd.AddCommand(domainCmd) + userCmd.AddCommand(updateCmd) userCmd.AddCommand(switchCmd) rootCmd.AddCommand(userCmd) } +// This returns all logged in user emails from the config file. +// If none, it returns the current logged in user in a slice func getLoggedInUsers() ([]string, error) { loggedInProfiles := []string{} @@ -187,6 +205,7 @@ func NewDomainPrompt() (string, error) { domainPrompt := promptui.Prompt{ Label: "New Domain", Validate: urlValidation, + Default: "Example - https://my-domain-example.com", } domain, err := domainPrompt.Run() @@ -194,6 +213,8 @@ func NewDomainPrompt() (string, error) { return "", err } + domain = fmt.Sprintf("%s/api", domain) + return domain, nil } diff --git a/cli/packages/util/config.go b/cli/packages/util/config.go index ef039443c..56623c603 100644 --- a/cli/packages/util/config.go +++ b/cli/packages/util/config.go @@ -37,13 +37,17 @@ func WriteInitalConfig(userCredentials *models.UserCredentials) error { Email: userCredentials.Email, Domain: config.INFISICAL_URL, } - if len(existingConfigFile.LoggedInUsers) > 0 { - ok := ConfigContainsEmail(existingConfigFile.LoggedInUsers, userCredentials.Email) - if !ok { - existingConfigFile.LoggedInUsers = append(existingConfigFile.LoggedInUsers, loggedInUser) - } - } else { + //if empty or if email not in loggedinUsers + if len(existingConfigFile.LoggedInUsers) == 0 || !ConfigContainsEmail(existingConfigFile.LoggedInUsers, userCredentials.Email) { + existingConfigFile.LoggedInUsers = append(existingConfigFile.LoggedInUsers, loggedInUser) + } else { + //if exists update domain of loggedin users + for idx, user := range existingConfigFile.LoggedInUsers { + if user.Email == userCredentials.Email { + existingConfigFile.LoggedInUsers[idx] = loggedInUser + } + } } configFile := models.ConfigFile{ diff --git a/cli/packages/util/helper.go b/cli/packages/util/helper.go index 46928fbc4..8527c4059 100644 --- a/cli/packages/util/helper.go +++ b/cli/packages/util/helper.go @@ -63,9 +63,10 @@ func IsSecretTypeValid(s string) bool { return false } -func ConfigContainsEmail(iter []models.LoggedInUser, elem string) bool { - for _, value := range iter { - if value.Email == elem { +// Checks if the passed in email already exists in the users slice +func ConfigContainsEmail(users []models.LoggedInUser, email string) bool { + for _, value := range users { + if value.Email == email { return true } } From d4206cdbd82707d5ed3e655bf5e1012c1241d619 Mon Sep 17 00:00:00 2001 From: quinton11 Date: Wed, 19 Apr 2023 19:41:14 +0000 Subject: [PATCH 7/9] login and user update commands support for existing domain override methods --- cli/packages/cmd/login.go | 51 +++++++++++++++++++++++++++----- cli/packages/cmd/user.go | 26 +++++++++++++--- cli/packages/config/config.go | 1 + cli/packages/util/credentials.go | 1 + 4 files changed, 68 insertions(+), 11 deletions(-) diff --git a/cli/packages/cmd/login.go b/cli/packages/cmd/login.go index 58d66b17f..dd4018e77 100644 --- a/cli/packages/cmd/login.go +++ b/cli/packages/cmd/login.go @@ -65,10 +65,29 @@ var loginCmd = &cobra.Command{ } } + //override domain + domainQuery := true + if config.INFISICAL_URL_MANUAL_OVERRIDE != util.INFISICAL_DEFAULT_API_URL { + overrideDomain, err := DomainOverridePrompt() + if err != nil { + util.HandleError(err) + } + + //if not override set INFISICAL_URL to exported var + //set domainQuery to false + if !overrideDomain { + domainQuery = false + config.INFISICAL_URL = config.INFISICAL_URL_MANUAL_OVERRIDE + } + + } + //prompt user to select domain between Infisical cloud and self hosting - err = askForDomain() - if err != nil { - util.HandleError(err, "Unable to parse domain url") + if domainQuery { + err = askForDomain() + if err != nil { + util.HandleError(err, "Unable to parse domain url") + } } email, password, err := askForLoginCredentials() @@ -264,6 +283,27 @@ func init() { rootCmd.AddCommand(loginCmd) } +func DomainOverridePrompt() (bool, error) { + var ( + PRESET = "Use Domain" + OVERRIDE = "Change Domain" + ) + + options := []string{PRESET, OVERRIDE} + optionsPrompt := promptui.Select{ + Label: fmt.Sprintf("Current INFISICAL_API_URL Domain Override: %s", config.INFISICAL_URL_MANUAL_OVERRIDE), + Items: options, + Size: 2, + } + + _, selectedOption, err := optionsPrompt.Run() + if err != nil { + return false, err + } + + return selectedOption == OVERRIDE, err +} + func askForDomain() error { //query user to choose between Infisical cloud or self hosting var ( @@ -405,10 +445,7 @@ func getFreshUserCredentials(email string, password string) (*api.GetLoginOneV2R } func userLoginMenu(currentLoggedInUserEmail string) (bool, error) { - label := fmt.Sprintf("Current logged in user email: %s", currentLoggedInUserEmail) - if config.INFISICAL_URL != "" { - label = fmt.Sprintf("%s on domain: %s", label, config.INFISICAL_URL) - } + label := fmt.Sprintf("Current logged in user email: %s on domain: %s", currentLoggedInUserEmail, config.INFISICAL_URL) prompt := promptui.Select{ Label: label, diff --git a/cli/packages/cmd/user.go b/cli/packages/cmd/user.go index 5cdd56d93..a9b54ba3a 100644 --- a/cli/packages/cmd/user.go +++ b/cli/packages/cmd/user.go @@ -111,10 +111,28 @@ var domainCmd = &cobra.Command{ util.HandleError(err, "[infisical user update domain]: Prompt error") } - //prompt to update domain - domain, err := NewDomainPrompt() - if err != nil { - util.HandleError(err, "[infisical user update domain]: Prompt error") + domain := "" + domainQuery := true + if config.INFISICAL_URL_MANUAL_OVERRIDE != util.INFISICAL_DEFAULT_API_URL { + + override, err := DomainOverridePrompt() + if err != nil { + util.HandleError(err, "[infisical user update domain]: Domain override prompt error") + } + + if !override { + domainQuery = false + domain = config.INFISICAL_URL_MANUAL_OVERRIDE + } + + } + + if domainQuery { + //prompt to update domain + domain, err = NewDomainPrompt() + if err != nil { + util.HandleError(err, "[infisical user update domain]: Prompt error") + } } //write to config file diff --git a/cli/packages/config/config.go b/cli/packages/config/config.go index 6c56858e6..e27bc8f68 100644 --- a/cli/packages/config/config.go +++ b/cli/packages/config/config.go @@ -1,3 +1,4 @@ package config var INFISICAL_URL string +var INFISICAL_URL_MANUAL_OVERRIDE string diff --git a/cli/packages/util/credentials.go b/cli/packages/util/credentials.go index b06ed4ef6..96a6b378a 100644 --- a/cli/packages/util/credentials.go +++ b/cli/packages/util/credentials.go @@ -91,6 +91,7 @@ func GetCurrentLoggedInUserDetails() (LoggedInUserDetails, error) { //configFile.LoggedInUserDomain //if not empty set as infisical url if configFile.LoggedInUserDomain != "" { + config.INFISICAL_URL_MANUAL_OVERRIDE = config.INFISICAL_URL config.INFISICAL_URL = configFile.LoggedInUserDomain } From 3add40bfbdd01691a8f313fa564ca6cb4e59dc06 Mon Sep 17 00:00:00 2001 From: quinton11 Date: Wed, 19 Apr 2023 19:49:14 +0000 Subject: [PATCH 8/9] Minor changes --- cli/packages/cmd/login.go | 4 ++-- cli/packages/cmd/user.go | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/cli/packages/cmd/login.go b/cli/packages/cmd/login.go index dd4018e77..9bd973a9a 100644 --- a/cli/packages/cmd/login.go +++ b/cli/packages/cmd/login.go @@ -340,7 +340,7 @@ func askForDomain() error { domainPrompt := promptui.Prompt{ Label: "Domain", Validate: urlValidation, - Default: "Example - https://my-domain-example.com", + Default: "Example - https://my-self-hosted-instance.com/api", } domain, err := domainPrompt.Run() @@ -349,7 +349,7 @@ func askForDomain() error { } //set api url - config.INFISICAL_URL = fmt.Sprintf("%s/api", domain) + config.INFISICAL_URL = domain //return nil return nil } diff --git a/cli/packages/cmd/user.go b/cli/packages/cmd/user.go index a9b54ba3a..77c1b037f 100644 --- a/cli/packages/cmd/user.go +++ b/cli/packages/cmd/user.go @@ -2,7 +2,6 @@ package cmd import ( "errors" - "fmt" "net/url" "github.com/Infisical/infisical-merge/packages/config" @@ -223,7 +222,7 @@ func NewDomainPrompt() (string, error) { domainPrompt := promptui.Prompt{ Label: "New Domain", Validate: urlValidation, - Default: "Example - https://my-domain-example.com", + Default: "Example - https://my-self-hosted-instance.com/api", } domain, err := domainPrompt.Run() @@ -231,8 +230,6 @@ func NewDomainPrompt() (string, error) { return "", err } - domain = fmt.Sprintf("%s/api", domain) - return domain, nil } From fa9a7301d9db38df9d92e4f6e3326415e8a805cf Mon Sep 17 00:00:00 2001 From: quinton11 Date: Wed, 19 Apr 2023 23:21:26 +0000 Subject: [PATCH 9/9] minor changes --- cli/packages/cmd/user.go | 4 ++-- cli/packages/util/credentials.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/packages/cmd/user.go b/cli/packages/cmd/user.go index 77c1b037f..12d700464 100644 --- a/cli/packages/cmd/user.go +++ b/cli/packages/cmd/user.go @@ -13,7 +13,7 @@ import ( var userCmd = &cobra.Command{ Use: "user", - Short: "Used manage user credentials", + Short: "Used to manage user credentials", DisableFlagsInUseLine: true, Example: "infisical user", Args: cobra.ExactArgs(0), @@ -143,7 +143,7 @@ var domainCmd = &cobra.Command{ //check if profile in logged in profiles //if not add new profile loggedInUsers - //else update profile from loggedinUsers list + //else update profile from loggedinUsers slice ok := util.ConfigContainsEmail(configFile.LoggedInUsers, profile) if !ok { configFile.LoggedInUsers = append(configFile.LoggedInUsers, models.LoggedInUser{ diff --git a/cli/packages/util/credentials.go b/cli/packages/util/credentials.go index 96a6b378a..9147a717a 100644 --- a/cli/packages/util/credentials.go +++ b/cli/packages/util/credentials.go @@ -88,10 +88,10 @@ func GetCurrentLoggedInUserDetails() (LoggedInUserDetails, error) { SetAuthToken(userCreds.JTWToken). SetHeader("Accept", "application/json") + config.INFISICAL_URL_MANUAL_OVERRIDE = config.INFISICAL_URL //configFile.LoggedInUserDomain //if not empty set as infisical url if configFile.LoggedInUserDomain != "" { - config.INFISICAL_URL_MANUAL_OVERRIDE = config.INFISICAL_URL config.INFISICAL_URL = configFile.LoggedInUserDomain }