Skip to content

Commit

Permalink
Partition API keys by test mode/live mode (#138)
Browse files Browse the repository at this point in the history
* Partition API keys by test mode/live mode and add some namespacing for it

* global api key flag should be just --api-key

* Fix test I missed

* Return a pointer for polling instead of the object
  • Loading branch information
tomer-stripe committed Aug 30, 2019
1 parent abcabcd commit e3eb35d
Show file tree
Hide file tree
Showing 11 changed files with 108 additions and 80 deletions.
2 changes: 1 addition & 1 deletion pkg/cmd/listen.go
Expand Up @@ -90,7 +90,7 @@ func (lc *listenCmd) runListenCmd(cmd *cobra.Command, args []string) error {

endpointRoutes := make([]proxy.EndpointRoute, 0)

key, err := Config.Profile.GetAPIKey()
key, err := Config.Profile.GetAPIKey(false)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/logs/tail.go
Expand Up @@ -132,7 +132,7 @@ func (tailCmd *TailCmd) runTailCmd(cmd *cobra.Command, args []string) error {
return err
}

key, err := tailCmd.cfg.Profile.GetAPIKey()
key, err := tailCmd.cfg.Profile.GetAPIKey(false)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/resource/operation.go
Expand Up @@ -32,7 +32,7 @@ type OperationCmd struct {
}

func (oc *OperationCmd) runOperationCmd(cmd *cobra.Command, args []string) error {
apiKey, err := oc.Profile.GetAPIKey()
apiKey, err := oc.Profile.GetAPIKey(false)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/trigger.go
Expand Up @@ -81,7 +81,7 @@ needed to create the triggered event.
}

func (tc *triggerCmd) runTriggerCmd(cmd *cobra.Command, args []string) error {
apiKey, err := Config.Profile.GetAPIKey()
apiKey, err := Config.Profile.GetAPIKey(false)
if err != nil {
return err
}
Expand Down
75 changes: 52 additions & 23 deletions pkg/config/profile.go
Expand Up @@ -13,10 +13,13 @@ import (

// Profile handles all things related to managing the project specific configurations
type Profile struct {
DeviceName string
ProfileName string
APIKey string
PublishableKey string
DeviceName string
ProfileName string
APIKey string
LiveModeAPIKey string
LiveModePublishableKey string
TestModeAPIKey string
TestModePublishableKey string
}

// CreateProfile creates a profile when logging in
Expand Down Expand Up @@ -64,7 +67,7 @@ func (p *Profile) GetDeviceName() (string, error) {
}

// GetAPIKey will return the existing key for the given profile
func (p *Profile) GetAPIKey() (string, error) {
func (p *Profile) GetAPIKey(livemode bool) (string, error) {
if p.APIKey != "" {
err := validators.APIKey(p.APIKey)
if err != nil {
Expand All @@ -75,13 +78,17 @@ func (p *Profile) GetAPIKey() (string, error) {

// If the user doesn't have an api_key field set, they might be using an
// old configuration so try to read from secret_key
if !viper.IsSet(p.GetConfigField("api_key")) {
p.RegisterAlias("api_key", "secret_key")
if !livemode {
if !viper.IsSet(p.GetConfigField("api_key")) {
p.RegisterAlias("api_key", "secret_key")
} else {
p.RegisterAlias("test_mode_api_key", "api_key")
}
}

// Try to fetch the API key from the configuration file
if err := viper.ReadInConfig(); err == nil {
key := viper.GetString(p.GetConfigField("api_key"))
key := viper.GetString(p.GetConfigField(livemodeKeyField(livemode)))
err := validators.APIKey(key)
if err != nil {
return "", err
Expand Down Expand Up @@ -129,27 +136,28 @@ func (p *Profile) writeProfile(runtimeViper *viper.Viper) error {
if p.DeviceName != "" {
runtimeViper.Set(p.GetConfigField("device_name"), strings.TrimSpace(p.DeviceName))
}
if p.APIKey != "" {
runtimeViper.Set(p.GetConfigField("api_key"), strings.TrimSpace(p.APIKey))
if p.LiveModeAPIKey != "" {
runtimeViper.Set(p.GetConfigField("live_mode_api_key"), strings.TrimSpace(p.LiveModeAPIKey))
}
if p.LiveModePublishableKey != "" {
runtimeViper.Set(p.GetConfigField("live_mode_publishable_key"), strings.TrimSpace(p.LiveModePublishableKey))
}
if p.TestModeAPIKey != "" {
runtimeViper.Set(p.GetConfigField("test_mode_api_key"), strings.TrimSpace(p.TestModeAPIKey))
}
if p.PublishableKey != "" {
runtimeViper.Set(p.GetConfigField("publishable_key"), strings.TrimSpace(p.PublishableKey))
if p.TestModePublishableKey != "" {
runtimeViper.Set(p.GetConfigField("test_mode_publishable_key"), strings.TrimSpace(p.TestModePublishableKey))
}

runtimeViper.MergeInConfig()

// Do this after we merge the old configs in
if p.APIKey != "" {
if runtimeViper.IsSet(p.GetConfigField("secret_key")) {
newViper, err := removeKey(runtimeViper, p.GetConfigField("secret_key"))
if err == nil {
// I don't want to fail the entire login process on not being able to remove
// the old secret_key field so if there's no error
runtimeViper = newViper
} else {
fmt.Println(err)
}
}
if p.TestModeAPIKey != "" {
runtimeViper = p.safeRemove(runtimeViper, "secret_key")
runtimeViper = p.safeRemove(runtimeViper, "api_key")
}
if p.TestModePublishableKey != "" {
runtimeViper = p.safeRemove(runtimeViper, "publishable_key")
}

runtimeViper.SetConfigFile(profilesFile)
Expand All @@ -163,3 +171,24 @@ func (p *Profile) writeProfile(runtimeViper *viper.Viper) error {

return nil
}

func (p *Profile) safeRemove(v *viper.Viper, key string) *viper.Viper {
if v.IsSet(p.GetConfigField(key)) {
newViper, err := removeKey(v, p.GetConfigField(key))
if err == nil {
// I don't want to fail the entire login process on not being able to remove
// the old secret_key field so if there's no error
return newViper
}
}

return v
}

func livemodeKeyField(livemode bool) string {
if livemode {
return "live_mode_api_key"
}

return "test_mode_api_key"
}
18 changes: 9 additions & 9 deletions pkg/config/profile_test.go
Expand Up @@ -14,9 +14,9 @@ import (
func TestWriteProfile(t *testing.T) {
profilesFile := filepath.Join(os.TempDir(), "stripe", "config.toml")
p := Profile{
DeviceName: "st-testing",
ProfileName: "tests",
APIKey: "sk_test_123",
DeviceName: "st-testing",
ProfileName: "tests",
TestModeAPIKey: "sk_test_123",
}

c := &Config{
Expand All @@ -38,8 +38,8 @@ func TestWriteProfile(t *testing.T) {
configValues := helperLoadBytes(t, c.ProfilesFile)
expectedConfig := `
[tests]
api_key = "sk_test_123"
device_name = "st-testing"
test_mode_api_key = "sk_test_123"
`
require.EqualValues(t, expectedConfig, string(configValues))

Expand All @@ -49,9 +49,9 @@ func TestWriteProfile(t *testing.T) {
func TestWriteProfilesMerge(t *testing.T) {
profilesFile := filepath.Join(os.TempDir(), "stripe", "config.toml")
p := Profile{
ProfileName: "tests",
DeviceName: "st-testing",
APIKey: "sk_test_123",
ProfileName: "tests",
DeviceName: "st-testing",
TestModeAPIKey: "sk_test_123",
}

c := &Config{
Expand All @@ -76,12 +76,12 @@ func TestWriteProfilesMerge(t *testing.T) {
configValues := helperLoadBytes(t, c.ProfilesFile)
expectedConfig := `
[tests]
api_key = "sk_test_123"
device_name = "st-testing"
test_mode_api_key = "sk_test_123"
[tests-merge]
api_key = "sk_test_123"
device_name = "st-testing"
test_mode_api_key = "sk_test_123"
`

require.EqualValues(t, expectedConfig, string(configValues))
Expand Down
10 changes: 5 additions & 5 deletions pkg/login/client_login.go
Expand Up @@ -56,24 +56,24 @@ func Login(baseURL string, config *config.Config, input io.Reader) error {
}

//Call poll function
apiKey, publishableKey, account, err := PollForKey(links.PollURL, 0, 0)
response, account, err := PollForKey(links.PollURL, 0, 0)
if err != nil {
return err
}

validateErr := validators.APIKey(apiKey)
validateErr := validators.APIKey(response.TestModeAPIKey)
if validateErr != nil {
return validateErr
}

config.Profile.APIKey = apiKey
config.Profile.PublishableKey = publishableKey
config.Profile.TestModeAPIKey = response.TestModeAPIKey
config.Profile.TestModePublishableKey = response.TestModePublishableKey
profileErr := config.Profile.CreateProfile()
if profileErr != nil {
return profileErr
}

message, err := SuccessMessage(account, stripe.DefaultAPIBaseURL, apiKey)
message, err := SuccessMessage(account, stripe.DefaultAPIBaseURL, response.TestModeAPIKey)
if err != nil {
fmt.Println(fmt.Sprintf("> Error verifying the CLI was set up successfully: %s", err))
} else {
Expand Down
2 changes: 1 addition & 1 deletion pkg/login/interactive_login.go
Expand Up @@ -26,7 +26,7 @@ func InteractiveLogin(config *config.Config) error {
}

config.Profile.DeviceName = getConfigureDeviceName(os.Stdin)
config.Profile.APIKey = apiKey
config.Profile.TestModeAPIKey = apiKey

profileErr := config.Profile.CreateProfile()
if profileErr != nil {
Expand Down
32 changes: 17 additions & 15 deletions pkg/login/poll.go
Expand Up @@ -15,16 +15,19 @@ import (
const maxAttemptsDefault = 2 * 60
const intervalDefault = 1 * time.Second

type pollAPIKeyResponse struct {
Redeemed bool `json:"redeemed"`
AccountID string `json:"account_id"`
AccountDisplayName string `json:"account_display_name"`
APIKey string `json:"testmode_key_secret"`
PublishableKey string `json:"testmode_key_publishable"`
// PollAPIKeyResponse returns the data of the polling client login
type PollAPIKeyResponse struct {
Redeemed bool `json:"redeemed"`
AccountID string `json:"account_id"`
AccountDisplayName string `json:"account_display_name"`
TestModeAPIKey string `json:"testmode_key_secret"`
TestModePublishableKey string `json:"testmode_key_publishable"`
}

// PollForKey polls Stripe at the specified interval until either the API key is available or we've reached the max attempts.
func PollForKey(pollURL string, interval time.Duration, maxAttempts int) (string, string, *Account, error) {
func PollForKey(pollURL string, interval time.Duration, maxAttempts int) (*PollAPIKeyResponse, *Account, error) {
var response PollAPIKeyResponse

if maxAttempts == 0 {
maxAttempts = maxAttemptsDefault
}
Expand All @@ -35,7 +38,7 @@ func PollForKey(pollURL string, interval time.Duration, maxAttempts int) (string

parsedURL, err := url.Parse(pollURL)
if err != nil {
return "", "", nil, err
return nil, nil, err
}

baseURL := &url.URL{Scheme: parsedURL.Scheme, Host: parsedURL.Host}
Expand All @@ -48,23 +51,22 @@ func PollForKey(pollURL string, interval time.Duration, maxAttempts int) (string
for count < maxAttempts {
res, err := client.PerformRequest(http.MethodGet, parsedURL.Path, parsedURL.Query().Encode(), nil)
if err != nil {
return "", "", nil, err
return nil, nil, err
}
defer res.Body.Close()

bodyBytes, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", "", nil, err
return nil, nil, err
}

if res.StatusCode != http.StatusOK {
return "", "", nil, fmt.Errorf("unexpected http status code: %d %s", res.StatusCode, string(bodyBytes))
return nil, nil, fmt.Errorf("unexpected http status code: %d %s", res.StatusCode, string(bodyBytes))
}

var response pollAPIKeyResponse
jsonErr := json.Unmarshal(bodyBytes, &response)
if jsonErr != nil {
return "", "", nil, jsonErr
return nil, nil, jsonErr
}

if response.Redeemed {
Expand All @@ -74,13 +76,13 @@ func PollForKey(pollURL string, interval time.Duration, maxAttempts int) (string

account.Settings.Dashboard.DisplayName = response.AccountDisplayName

return response.APIKey, response.PublishableKey, account, nil
return &response, account, nil
}

count++
time.Sleep(interval)

}

return "", "", nil, errors.New("exceeded max attempts")
return nil, nil, errors.New("exceeded max attempts")
}

0 comments on commit e3eb35d

Please sign in to comment.