From e18e8eb72cdd6226d04e6aa5561a80ffab823c77 Mon Sep 17 00:00:00 2001 From: Fred Hatfull Date: Wed, 14 Jun 2023 10:51:06 -0700 Subject: [PATCH 1/8] Adds a struct for holding API key-related information --- pkg/config/api_key.go | 34 ++++++++++++++++++++++++++++++++++ pkg/config/api_key_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 pkg/config/api_key.go create mode 100644 pkg/config/api_key_test.go diff --git a/pkg/config/api_key.go b/pkg/config/api_key.go new file mode 100644 index 000000000..0edf128b7 --- /dev/null +++ b/pkg/config/api_key.go @@ -0,0 +1,34 @@ +package config + +import ( + "strings" + "time" +) + +type APIKey struct { + Key string + Livemode bool + Expiration time.Time + profile *Profile +} + +func NewAPIKey(key string, expiration time.Time, livemode bool, profile *Profile) *APIKey { + return &APIKey{ + Key: key, + Livemode: livemode, + Expiration: expiration, + profile: profile, + } +} + +func NewAPIKeyFromString(key string, profile *Profile) *APIKey { + return &APIKey{ + Key: key, + // Not guaranteed to be right, but we'll try our best to infer live/test mode + // via a heuristic + Livemode: strings.Contains(key, "live"), + // Expiration intentionally omitted to leave it as the zero value, since + // it's not known when e.g. a key is passed using an environment variable. + profile: profile, + } +} diff --git a/pkg/config/api_key_test.go b/pkg/config/api_key_test.go new file mode 100644 index 000000000..a1c490d33 --- /dev/null +++ b/pkg/config/api_key_test.go @@ -0,0 +1,24 @@ +package config + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestNewAPIKeyFromString(t *testing.T) { + sampleLivemodeKeyString := "rk_live_1234" + sampleTestmodeKeyString := "rk_test_1234" + + livemodeKey := NewAPIKeyFromString(sampleLivemodeKeyString, nil) + testmodeKey := NewAPIKeyFromString(sampleTestmodeKeyString, nil) + + assert.Equal(t, sampleLivemodeKeyString, livemodeKey.Key) + assert.True(t, livemodeKey.Livemode) + assert.Zero(t, livemodeKey.Expiration) + assert.Nil(t, livemodeKey.profile) + + assert.Equal(t, sampleTestmodeKeyString, testmodeKey.Key) + assert.False(t, testmodeKey.Livemode) + assert.Zero(t, testmodeKey.Expiration) + assert.Nil(t, testmodeKey.profile) +} From 1c71974f691b5aeb187fb72c03d44fec298c0ab0 Mon Sep 17 00:00:00 2001 From: Fred Hatfull Date: Wed, 14 Jun 2023 10:59:20 -0700 Subject: [PATCH 2/8] Refactors the CLI to use new API key struct --- pkg/cmd/resource/terminal_quickstart.go | 2 +- pkg/config/api_key.go | 8 ++++ pkg/config/profile.go | 60 +++++++++++++++--------- pkg/config/profile_test.go | 21 +++++---- pkg/fixtures/fixtures.go | 7 +-- pkg/fixtures/fixtures_test.go | 4 +- pkg/fixtures/triggers.go | 7 +-- pkg/login/acct/retrieve_account.go | 3 +- pkg/login/acct/retrieve_account_test.go | 5 +- pkg/login/client_login.go | 4 +- pkg/login/interactive_login.go | 14 +++--- pkg/login/interactive_login_test.go | 13 +++-- pkg/login/keys/configurer.go | 11 ++++- pkg/login/login_message.go | 3 +- pkg/login/login_message_test.go | 17 +++++-- pkg/logout/logout.go | 2 +- pkg/requests/base.go | 4 +- pkg/requests/base_test.go | 12 +++-- pkg/requests/plugin.go | 2 +- pkg/requests/webhook_endpoints.go | 4 +- pkg/rpcservice/fixtures.go | 2 +- pkg/samples/samples.go | 2 +- pkg/stripe/client.go | 13 +++-- pkg/stripe/client_test.go | 3 +- pkg/stripeauth/client_test.go | 9 ++-- pkg/terminal/p400/rabbit_service_test.go | 3 +- pkg/terminal/p400/reader_methods.go | 3 +- pkg/terminal/p400/stripe_requests.go | 3 +- 28 files changed, 157 insertions(+), 84 deletions(-) diff --git a/pkg/cmd/resource/terminal_quickstart.go b/pkg/cmd/resource/terminal_quickstart.go index 7bb269126..5ea771612 100644 --- a/pkg/cmd/resource/terminal_quickstart.go +++ b/pkg/cmd/resource/terminal_quickstart.go @@ -43,7 +43,7 @@ func (cc *QuickstartCmd) runQuickstartCmd(cmd *cobra.Command, args []string) err return fmt.Errorf(err.Error()) } - err = validators.APIKeyNotRestricted(key) + err = validators.APIKeyNotRestricted(key.Key) if err != nil { return fmt.Errorf(err.Error()) diff --git a/pkg/config/api_key.go b/pkg/config/api_key.go index 0edf128b7..5c350d64f 100644 --- a/pkg/config/api_key.go +++ b/pkg/config/api_key.go @@ -13,6 +13,10 @@ type APIKey struct { } func NewAPIKey(key string, expiration time.Time, livemode bool, profile *Profile) *APIKey { + if key == "" { + return nil + } + return &APIKey{ Key: key, Livemode: livemode, @@ -22,6 +26,10 @@ func NewAPIKey(key string, expiration time.Time, livemode bool, profile *Profile } func NewAPIKeyFromString(key string, profile *Profile) *APIKey { + if key == "" { + return nil + } + return &APIKey{ Key: key, // Not guaranteed to be right, but we'll try our best to infer live/test mode diff --git a/pkg/config/profile.go b/pkg/config/profile.go index 61156cd55..024a4a158 100644 --- a/pkg/config/profile.go +++ b/pkg/config/profile.go @@ -21,9 +21,9 @@ type Profile struct { DeviceName string ProfileName string APIKey string - LiveModeAPIKey string + LiveModeAPIKey *APIKey LiveModePublishableKey string - TestModeAPIKey string + TestModeAPIKey *APIKey TestModePublishableKey string TerminalPOSDeviceID string DisplayName string @@ -136,27 +136,28 @@ func (p *Profile) GetAccountID() (string, error) { } // GetAPIKey will return the existing key for the given profile -func (p *Profile) GetAPIKey(livemode bool) (string, error) { +func (p *Profile) GetAPIKey(livemode bool) (*APIKey, error) { envKey := os.Getenv("STRIPE_API_KEY") if envKey != "" { err := validators.APIKey(envKey) if err != nil { - return "", err + return nil, err } - return envKey, nil + return NewAPIKeyFromString(envKey, p), nil } if p.APIKey != "" { err := validators.APIKey(p.APIKey) if err != nil { - return "", err + return nil, err } - return p.APIKey, nil + return NewAPIKeyFromString(p.APIKey, p), nil } var key string + var keyExpiry time.Time var err error // Try to fetch the API key from the configuration file @@ -171,24 +172,40 @@ func (p *Profile) GetAPIKey(livemode bool) (string, error) { if err := viper.ReadInConfig(); err == nil { key = viper.GetString(p.GetConfigField(TestModeAPIKeyName)) + + keyExpiry, err = p.GetExpiresAt(false) + if err != nil { + // It's OK if we can't detect the expiry of the key right now, + // since we only grab it to show a convenience warning if expiry + // is happening soon. + keyExpiry = time.Time{} + } } } else { p.redactAllLivemodeValues() key, err = p.retrieveLivemodeValue(LiveModeAPIKeyName) if err != nil { - return "", err + return nil, err + } + + keyExpiry, err = p.GetExpiresAt(true) + if err != nil { + // It's OK if we can't detect the expiry of the key right now, + // since we only grab it to show a convenience warning if expiry + // is happening soon. + keyExpiry = time.Time{} } } if key != "" { err = validators.APIKey(key) if err != nil { - return "", err + return nil, err } - return key, nil + return NewAPIKey(key, keyExpiry, livemode, p), nil } - return "", validators.ErrAPIKeyNotConfigured + return nil, validators.ErrAPIKeyNotConfigured } // GetExpiresAt returns the API key expirary date @@ -307,24 +324,23 @@ func (p *Profile) writeProfile(runtimeViper *viper.Viper) error { runtimeViper.Set(p.GetConfigField(DeviceNameName), strings.TrimSpace(p.DeviceName)) } - if p.LiveModeAPIKey != "" { - expiresAt := getKeyExpiresAt() - runtimeViper.Set(p.GetConfigField(LiveModeKeyExpiresAtName), expiresAt) + if p.LiveModeAPIKey != nil { + runtimeViper.Set(p.GetConfigField(LiveModeKeyExpiresAtName), formatExpirationDate(p.LiveModeAPIKey.Expiration)) // // store redacted key in config - runtimeViper.Set(p.GetConfigField(LiveModeAPIKeyName), RedactAPIKey(strings.TrimSpace(p.LiveModeAPIKey))) + runtimeViper.Set(p.GetConfigField(LiveModeAPIKeyName), RedactAPIKey(strings.TrimSpace(p.LiveModeAPIKey.Key))) // // store actual key in secure keyring - p.saveLivemodeValue(LiveModeAPIKeyName, strings.TrimSpace(p.LiveModeAPIKey), "Live mode API key") + p.saveLivemodeValue(LiveModeAPIKeyName, strings.TrimSpace(p.LiveModeAPIKey.Key), "Live mode API key") } if p.LiveModePublishableKey != "" { runtimeViper.Set(p.GetConfigField(LiveModePubKeyName), strings.TrimSpace(p.LiveModePublishableKey)) } - if p.TestModeAPIKey != "" { - runtimeViper.Set(p.GetConfigField(TestModeAPIKeyName), strings.TrimSpace(p.TestModeAPIKey)) - runtimeViper.Set(p.GetConfigField(TestModeKeyExpiresAtName), getKeyExpiresAt()) + if p.TestModeAPIKey != nil { + runtimeViper.Set(p.GetConfigField(TestModeAPIKeyName), strings.TrimSpace(p.TestModeAPIKey.Key)) + runtimeViper.Set(p.GetConfigField(TestModeKeyExpiresAtName), formatExpirationDate(p.TestModeAPIKey.Expiration)) } if p.TestModePublishableKey != "" { @@ -342,7 +358,7 @@ func (p *Profile) writeProfile(runtimeViper *viper.Viper) error { runtimeViper.MergeInConfig() // Do this after we merge the old configs in - if p.TestModeAPIKey != "" { + if p.TestModeAPIKey != nil { runtimeViper = p.safeRemove(runtimeViper, "secret_key") runtimeViper = p.safeRemove(runtimeViper, "api_key") } @@ -433,8 +449,8 @@ func isRedactedAPIKey(apiKey string) bool { return true } -func getKeyExpiresAt() string { - return time.Now().AddDate(0, 0, KeyValidInDays).UTC().Format(DateStringFormat) +func formatExpirationDate(expirationDate time.Time) string { + return expirationDate.UTC().Format(DateStringFormat) } // saveLivemodeValue saves livemode value of given key in keyring diff --git a/pkg/config/profile_test.go b/pkg/config/profile_test.go index da890f9bf..aaeb652dc 100644 --- a/pkg/config/profile_test.go +++ b/pkg/config/profile_test.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" "testing" + "time" "github.com/spf13/viper" "github.com/stretchr/testify/require" @@ -12,10 +13,11 @@ import ( func TestWriteProfile(t *testing.T) { profilesFile := filepath.Join(os.TempDir(), "stripe", "config.toml") + expirationTime := time.Now().Add(24 * time.Hour) p := Profile{ DeviceName: "st-testing", ProfileName: "tests", - TestModeAPIKey: "sk_test_123", + TestModeAPIKey: NewAPIKey("sk_test_123", expirationTime, false, nil), DisplayName: "test-account-display-name", } @@ -37,7 +39,7 @@ func TestWriteProfile(t *testing.T) { require.FileExists(t, c.ProfilesFile) configValues := helperLoadBytes(t, c.ProfilesFile) - expiresAt := getKeyExpiresAt() + expiresAt := formatExpirationDate(expirationTime) expectedConfig := `[tests] device_name = 'st-testing' display_name = 'test-account-display-name' @@ -53,10 +55,11 @@ test_mode_key_expires_at = '` + expiresAt + `' func TestWriteProfilesMerge(t *testing.T) { profilesFile := filepath.Join(os.TempDir(), "stripe", "config.toml") + expirationTime := time.Now().Add(24 * time.Hour) p := Profile{ ProfileName: "tests", DeviceName: "st-testing", - TestModeAPIKey: "sk_test_123", + TestModeAPIKey: NewAPIKey("sk_test_123", expirationTime, false, nil), DisplayName: "test-account-display-name", } @@ -80,7 +83,7 @@ func TestWriteProfilesMerge(t *testing.T) { require.FileExists(t, c.ProfilesFile) configValues := helperLoadBytes(t, c.ProfilesFile) - expiresAt := getKeyExpiresAt() + expiresAt := formatExpirationDate(expirationTime) expectedConfig := `[tests] device_name = 'st-testing' display_name = 'test-account-display-name' @@ -102,10 +105,11 @@ test_mode_key_expires_at = '` + expiresAt + `' func TestExperimentalFields(t *testing.T) { profilesFile := filepath.Join(os.TempDir(), "stripe", "config.toml") + expirationTime := time.Now().Add(24 * time.Hour) p := Profile{ ProfileName: "tests", DeviceName: "st-testing", - TestModeAPIKey: "sk_test_123", + TestModeAPIKey: NewAPIKey("sk_test_123", expirationTime, false, nil), DisplayName: "test-account-display-name", } c := &Config{ @@ -145,10 +149,11 @@ func TestExperimentalFields(t *testing.T) { func TestOldProfileDeleted(t *testing.T) { profilesFile := filepath.Join(os.TempDir(), "stripe", "config.toml") + expirationTime := time.Now().Add(24 * time.Hour) p := Profile{ ProfileName: "test", DeviceName: "device-before-test", - TestModeAPIKey: "sk_test_123", + TestModeAPIKey: NewAPIKey("sk_test_123", expirationTime, false, nil), DisplayName: "display-name-before-test", } c := &Config{ @@ -170,7 +175,7 @@ func TestOldProfileDeleted(t *testing.T) { untouchedProfile := Profile{ ProfileName: "foo", DeviceName: "foo-device-name", - TestModeAPIKey: "foo_test_123", + TestModeAPIKey: NewAPIKey("foo_test_123", expirationTime, false, nil), } err = untouchedProfile.writeProfile(v) require.NoError(t, err) @@ -178,7 +183,7 @@ func TestOldProfileDeleted(t *testing.T) { p = Profile{ ProfileName: "test", DeviceName: "device-after-test", - TestModeAPIKey: "sk_test_456", + TestModeAPIKey: NewAPIKey("sk_test_456", expirationTime, false, nil), DisplayName: "", } diff --git a/pkg/fixtures/fixtures.go b/pkg/fixtures/fixtures.go index 6ed738b1f..9d555064f 100644 --- a/pkg/fixtures/fixtures.go +++ b/pkg/fixtures/fixtures.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/stripe/stripe-cli/pkg/config" "io" "os" "path/filepath" @@ -50,7 +51,7 @@ type FixtureRequest struct { // Fixture contains a mapping of an individual fixtures responses for querying type Fixture struct { Fs afero.Fs - APIKey string + APIKey *config.APIKey StripeAccount string Skip []string Overrides map[string]interface{} @@ -62,7 +63,7 @@ type Fixture struct { } // NewFixtureFromFile creates a to later run steps for populating test data -func NewFixtureFromFile(fs afero.Fs, apiKey, stripeAccount, baseURL, file string, skip, override, add, remove []string, edit bool) (*Fixture, error) { +func NewFixtureFromFile(fs afero.Fs, apiKey *config.APIKey, stripeAccount, baseURL, file string, skip, override, add, remove []string, edit bool) (*Fixture, error) { fxt := Fixture{ Fs: fs, APIKey: apiKey, @@ -130,7 +131,7 @@ func NewFixtureFromFile(fs afero.Fs, apiKey, stripeAccount, baseURL, file string } // NewFixtureFromRawString creates fixtures from user inputted string -func NewFixtureFromRawString(fs afero.Fs, apiKey, stripeAccount, baseURL, raw string) (*Fixture, error) { +func NewFixtureFromRawString(fs afero.Fs, apiKey *config.APIKey, stripeAccount, baseURL, raw string) (*Fixture, error) { fxt := Fixture{ Fs: fs, APIKey: apiKey, diff --git a/pkg/fixtures/fixtures_test.go b/pkg/fixtures/fixtures_test.go index 8559e38a5..ed0d96ccd 100644 --- a/pkg/fixtures/fixtures_test.go +++ b/pkg/fixtures/fixtures_test.go @@ -3,6 +3,7 @@ package fixtures import ( "context" "errors" + "github.com/stripe/stripe-cli/pkg/config" "io" "net/http" "net/http/httptest" @@ -80,7 +81,8 @@ const failureTestFixture = ` ] }` -const apiKey = "sk_test_1234" +var apiKey = config.NewAPIKeyFromString("sk_test_1234", nil) + const file = "test_fixture.json" const customersPath = "/v1/customers" const chargePath = "/v1/charges" diff --git a/pkg/fixtures/triggers.go b/pkg/fixtures/triggers.go index 49c4227c7..277a9a7b1 100644 --- a/pkg/fixtures/triggers.go +++ b/pkg/fixtures/triggers.go @@ -4,6 +4,7 @@ import ( "context" "embed" "fmt" + "github.com/stripe/stripe-cli/pkg/config" "sort" "github.com/spf13/afero" @@ -94,7 +95,7 @@ var Events = map[string]string{ } // BuildFromFixtureFile creates a new fixture struct for a file -func BuildFromFixtureFile(fs afero.Fs, apiKey, stripeAccount, apiBaseURL, jsonFile string, skip, override, add, remove []string, edit bool) (*Fixture, error) { +func BuildFromFixtureFile(fs afero.Fs, apiKey *config.APIKey, stripeAccount, apiBaseURL, jsonFile string, skip, override, add, remove []string, edit bool) (*Fixture, error) { fixture, err := NewFixtureFromFile( fs, apiKey, @@ -115,7 +116,7 @@ func BuildFromFixtureFile(fs afero.Fs, apiKey, stripeAccount, apiBaseURL, jsonFi } // BuildFromFixtureString creates a new fixture from a string -func BuildFromFixtureString(fs afero.Fs, apiKey, stripeAccount, apiBaseURL, raw string) (*Fixture, error) { +func BuildFromFixtureString(fs afero.Fs, apiKey *config.APIKey, stripeAccount, apiBaseURL, raw string) (*Fixture, error) { fixture, err := NewFixtureFromRawString(fs, apiKey, stripeAccount, apiBaseURL, raw) if err != nil { return nil, err @@ -146,7 +147,7 @@ func EventNames() []string { } // Trigger triggers a Stripe event. -func Trigger(ctx context.Context, event string, stripeAccount string, baseURL string, apiKey string, skip, override, add, remove []string, raw string, apiVersion string, edit bool) ([]string, error) { +func Trigger(ctx context.Context, event string, stripeAccount string, baseURL string, apiKey *config.APIKey, skip, override, add, remove []string, raw string, apiVersion string, edit bool) ([]string, error) { var fixture *Fixture var err error fs := afero.NewOsFs() diff --git a/pkg/login/acct/retrieve_account.go b/pkg/login/acct/retrieve_account.go index 3753960ed..94d627f01 100644 --- a/pkg/login/acct/retrieve_account.go +++ b/pkg/login/acct/retrieve_account.go @@ -3,6 +3,7 @@ package acct import ( "context" "encoding/json" + "github.com/stripe/stripe-cli/pkg/config" "net/url" "github.com/stripe/stripe-cli/pkg/stripe" @@ -25,7 +26,7 @@ type Dashboard struct { } // GetUserAccount retrieves the account information -func GetUserAccount(ctx context.Context, baseURL string, apiKey string) (*Account, error) { +func GetUserAccount(ctx context.Context, baseURL string, apiKey *config.APIKey) (*Account, error) { parsedBaseURL, err := url.Parse(baseURL) if err != nil { return nil, err diff --git a/pkg/login/acct/retrieve_account_test.go b/pkg/login/acct/retrieve_account_test.go index c7471275f..1d7a7b71f 100644 --- a/pkg/login/acct/retrieve_account_test.go +++ b/pkg/login/acct/retrieve_account_test.go @@ -3,6 +3,7 @@ package acct import ( "context" "encoding/json" + "github.com/stripe/stripe-cli/pkg/config" "net/http" "net/http/httptest" "testing" @@ -27,7 +28,7 @@ func TestGetAccount(t *testing.T) { })) defer ts.Close() - acc, err := GetUserAccount(context.Background(), ts.URL, "sk_test_123") + acc, err := GetUserAccount(context.Background(), ts.URL, config.NewAPIKeyFromString("sk_test_123", nil)) require.NoError(t, err) require.Equal( t, @@ -55,7 +56,7 @@ func TestGetAccountNoDisplayName(t *testing.T) { })) defer ts.Close() - acc, err := GetUserAccount(context.Background(), ts.URL, "sk_test_123") + acc, err := GetUserAccount(context.Background(), ts.URL, config.NewAPIKeyFromString("sk_test_123", nil)) require.NoError(t, err) require.Equal( t, diff --git a/pkg/login/client_login.go b/pkg/login/client_login.go index 894f1d937..a6d824408 100644 --- a/pkg/login/client_login.go +++ b/pkg/login/client_login.go @@ -3,6 +3,7 @@ package login import ( "context" "fmt" + "github.com/stripe/stripe-cli/pkg/config" "os" "github.com/briandowns/spinner" @@ -77,7 +78,8 @@ func (a *Authenticator) Login(ctx context.Context, links *Links) error { return res.Err } - message, err := SuccessMessage(ctx, res.Account, stripe.DefaultAPIBaseURL, res.TestModeAPIKey) + apiKey := config.NewAPIKeyFromString(res.TestModeAPIKey, nil) + message, err := SuccessMessage(ctx, res.Account, stripe.DefaultAPIBaseURL, apiKey) if err != nil { fmt.Printf("> Error verifying the CLI was set up successfully: %s\n", err) return err diff --git a/pkg/login/interactive_login.go b/pkg/login/interactive_login.go index 9f6382a2b..a64f4ce3c 100644 --- a/pkg/login/interactive_login.go +++ b/pkg/login/interactive_login.go @@ -14,21 +14,23 @@ import ( "golang.org/x/term" "github.com/stripe/stripe-cli/pkg/ansi" - "github.com/stripe/stripe-cli/pkg/config" + config_pkg "github.com/stripe/stripe-cli/pkg/config" "github.com/stripe/stripe-cli/pkg/login/acct" "github.com/stripe/stripe-cli/pkg/stripe" "github.com/stripe/stripe-cli/pkg/validators" ) // InteractiveLogin lets the user set configuration on the command line -func InteractiveLogin(ctx context.Context, config *config.Config) error { - apiKey, err := getConfigureAPIKey(os.Stdin) +func InteractiveLogin(ctx context.Context, config *config_pkg.Config) error { + apiKeyString, err := getConfigureAPIKey(os.Stdin) if err != nil { return err } + apiKey := config_pkg.NewAPIKeyFromString(apiKeyString, nil) + config.Profile.DeviceName = getConfigureDeviceName(os.Stdin) - config.Profile.TestModeAPIKey = apiKey + config.Profile.TestModeAPIKey = config_pkg.NewAPIKeyFromString(apiKeyString, &config.Profile) displayName, _ := getDisplayName(ctx, nil, stripe.DefaultAPIBaseURL, apiKey) config.Profile.DisplayName = displayName @@ -52,7 +54,7 @@ func InteractiveLogin(ctx context.Context, config *config.Config) error { } // getDisplayName returns the display name for a successfully authenticated user -func getDisplayName(ctx context.Context, account *acct.Account, baseURL string, apiKey string) (string, error) { +func getDisplayName(ctx context.Context, account *acct.Account, baseURL string, apiKey *config_pkg.APIKey) (string, error) { // Account will be nil if user did interactive login if account == nil { acc, err := acct.GetUserAccount(ctx, baseURL, apiKey) @@ -85,7 +87,7 @@ func getConfigureAPIKey(input io.Reader) (string, error) { return "", err } - fmt.Printf("Your API key is: %s\n", config.RedactAPIKey(apiKey)) + fmt.Printf("Your API key is: %s\n", config_pkg.RedactAPIKey(apiKey)) return apiKey, nil } diff --git a/pkg/login/interactive_login_test.go b/pkg/login/interactive_login_test.go index a9370223d..e1752693a 100644 --- a/pkg/login/interactive_login_test.go +++ b/pkg/login/interactive_login_test.go @@ -3,6 +3,7 @@ package login import ( "context" "encoding/json" + "github.com/stripe/stripe-cli/pkg/config" "net/http" "net/http/httptest" "os" @@ -22,7 +23,8 @@ func TestDisplayName(t *testing.T) { } account.Settings.Dashboard.DisplayName = testAccountName - displayName, err := getDisplayName(context.Background(), account, "", "sk_test_123") + var apiKey = config.NewAPIKeyFromString("sk_test_123", nil) + displayName, err := getDisplayName(context.Background(), account, "", apiKey) require.NoError(t, err) require.Equal( t, @@ -36,7 +38,8 @@ func TestDisplayNameNoName(t *testing.T) { ID: "acct_123", } - displayName, err := getDisplayName(context.Background(), account, "", "sk_test_123") + var apiKey = config.NewAPIKeyFromString("sk_test_123", nil) + displayName, err := getDisplayName(context.Background(), account, "", apiKey) require.NoError(t, err) require.Equal( t, @@ -60,7 +63,8 @@ func TestDisplayNameGetAccount(t *testing.T) { })) defer ts.Close() - displayName, err := getDisplayName(context.Background(), nil, ts.URL, "sk_test_123") + var apiKey = config.NewAPIKeyFromString("sk_test_123", nil) + displayName, err := getDisplayName(context.Background(), nil, ts.URL, apiKey) require.NoError(t, err) require.Equal( t, @@ -83,7 +87,8 @@ func TestDisplayNameGetAccountNoName(t *testing.T) { })) defer ts.Close() - displayName, err := getDisplayName(context.Background(), nil, ts.URL, "sk_test_123") + var apiKey = config.NewAPIKeyFromString("sk_test_123", nil) + displayName, err := getDisplayName(context.Background(), nil, ts.URL, apiKey) require.NoError(t, err) require.Equal( t, diff --git a/pkg/login/keys/configurer.go b/pkg/login/keys/configurer.go index 1405c3833..28aa1e27e 100644 --- a/pkg/login/keys/configurer.go +++ b/pkg/login/keys/configurer.go @@ -2,6 +2,7 @@ package keys import ( "github.com/spf13/afero" + "time" "github.com/stripe/stripe-cli/pkg/config" "github.com/stripe/stripe-cli/pkg/validators" @@ -33,10 +34,16 @@ func (c *RAKConfigurer) SaveLoginDetails(response *PollAPIKeyResponse) error { return validateErr } - c.cfg.Profile.LiveModeAPIKey = response.LiveModeAPIKey + if response.LiveModeAPIKey != "" { + c.cfg.Profile.LiveModeAPIKey = config.NewAPIKey(response.LiveModeAPIKey, time.Unix(response.LiveModeAPIKeyExpiry, 0), true, &c.cfg.Profile) + } c.cfg.Profile.LiveModePublishableKey = response.LiveModePublishableKey - c.cfg.Profile.TestModeAPIKey = response.TestModeAPIKey + + if response.TestModeAPIKey != "" { + c.cfg.Profile.TestModeAPIKey = config.NewAPIKey(response.TestModeAPIKey, time.Unix(response.TestModeAPIKeyExpiry, 0), false, &c.cfg.Profile) + } c.cfg.Profile.TestModePublishableKey = response.TestModePublishableKey + c.cfg.Profile.DisplayName = response.AccountDisplayName c.cfg.Profile.AccountID = response.AccountID diff --git a/pkg/login/login_message.go b/pkg/login/login_message.go index acc99c509..1228a4b59 100644 --- a/pkg/login/login_message.go +++ b/pkg/login/login_message.go @@ -3,6 +3,7 @@ package login import ( "context" "fmt" + "github.com/stripe/stripe-cli/pkg/config" "os" "github.com/stripe/stripe-cli/pkg/ansi" @@ -10,7 +11,7 @@ import ( ) // SuccessMessage returns the display message for a successfully authenticated user -func SuccessMessage(ctx context.Context, account *acct.Account, baseURL string, apiKey string) (string, error) { +func SuccessMessage(ctx context.Context, account *acct.Account, baseURL string, apiKey *config.APIKey) (string, error) { // Account will be nil if user did interactive login if account == nil { acc, err := acct.GetUserAccount(ctx, baseURL, apiKey) diff --git a/pkg/login/login_message_test.go b/pkg/login/login_message_test.go index e434dbf07..bee27dc9f 100644 --- a/pkg/login/login_message_test.go +++ b/pkg/login/login_message_test.go @@ -3,6 +3,7 @@ package login import ( "context" "encoding/json" + "github.com/stripe/stripe-cli/pkg/config" "net/http" "net/http/httptest" "testing" @@ -20,7 +21,8 @@ func TestSuccessMessage(t *testing.T) { } account.Settings.Dashboard.DisplayName = testDisplayName - msg, err := SuccessMessage(context.Background(), account, "", "sk_test_123") + var apiKey = config.NewAPIKeyFromString("sk_test_123", nil) + msg, err := SuccessMessage(context.Background(), account, "", apiKey) require.NoError(t, err) require.Equal( t, @@ -34,7 +36,8 @@ func TestSuccessMessageNoDisplayName(t *testing.T) { ID: "acct_123", } - msg, err := SuccessMessage(context.Background(), account, "", "sk_test_123") + var apiKey = config.NewAPIKeyFromString("sk_test_123", nil) + msg, err := SuccessMessage(context.Background(), account, "", apiKey) require.NoError(t, err) require.Equal( t, @@ -45,7 +48,9 @@ func TestSuccessMessageNoDisplayName(t *testing.T) { func TestSuccessMessageBasicMessage(t *testing.T) { account := &acct.Account{} - msg, err := SuccessMessage(context.Background(), account, "", "sk_test_123") + + var apiKey = config.NewAPIKeyFromString("sk_test_123", nil) + msg, err := SuccessMessage(context.Background(), account, "", apiKey) require.NoError(t, err) require.Equal( t, @@ -69,7 +74,8 @@ func TestSuccessMessageGetAccount(t *testing.T) { })) defer ts.Close() - msg, err := SuccessMessage(context.Background(), nil, ts.URL, "sk_test_123") + var apiKey = config.NewAPIKeyFromString("sk_test_123", nil) + msg, err := SuccessMessage(context.Background(), nil, ts.URL, apiKey) require.NoError(t, err) require.Equal( t, @@ -92,7 +98,8 @@ func TestSuccessMessageGetAccountNoDisplayName(t *testing.T) { })) defer ts.Close() - msg, err := SuccessMessage(context.Background(), nil, ts.URL, "sk_test_123") + var apiKey = config.NewAPIKeyFromString("sk_test_123", nil) + msg, err := SuccessMessage(context.Background(), nil, ts.URL, apiKey) require.NoError(t, err) require.Equal( t, diff --git a/pkg/logout/logout.go b/pkg/logout/logout.go index 842315071..7ac3f8818 100644 --- a/pkg/logout/logout.go +++ b/pkg/logout/logout.go @@ -11,7 +11,7 @@ func Logout(config *config.Config) error { liveKey, _ := config.Profile.GetAPIKey(true) testKey, _ := config.Profile.GetAPIKey(false) - if liveKey == "" && testKey == "" { + if liveKey == nil && testKey == nil { fmt.Println("You are already logged out.") return nil } diff --git a/pkg/requests/base.go b/pkg/requests/base.go index 0849090c9..d421383b5 100644 --- a/pkg/requests/base.go +++ b/pkg/requests/base.go @@ -178,7 +178,7 @@ func (rb *Base) InitFlags() { // MakeMultiPartRequest will make a multipart/form-data request to the Stripe API with the specific variables given to it. // Similar to making a multipart request using curl, add the local filepath to params arg with @ prefix. // e.g. params.AppendData([]string{"photo=@/path/to/local/file.png"}) -func (rb *Base) MakeMultiPartRequest(ctx context.Context, apiKey, path string, params *RequestParameters, errOnStatus bool) ([]byte, error) { +func (rb *Base) MakeMultiPartRequest(ctx context.Context, apiKey *config.APIKey, path string, params *RequestParameters, errOnStatus bool) ([]byte, error) { reqBody, contentType, err := rb.buildMultiPartRequest(params) if err != nil { return []byte{}, err @@ -204,7 +204,7 @@ func (rb *Base) MakeMultiPartRequest(ctx context.Context, apiKey, path string, p } // MakeRequest will make a request to the Stripe API with the specific variables given to it -func (rb *Base) MakeRequest(ctx context.Context, apiKey, path string, params *RequestParameters, errOnStatus bool) ([]byte, error) { +func (rb *Base) MakeRequest(ctx context.Context, apiKey *config.APIKey, path string, params *RequestParameters, errOnStatus bool) ([]byte, error) { parsedBaseURL, err := url.Parse(rb.APIBaseURL) if err != nil { return []byte{}, err diff --git a/pkg/requests/base_test.go b/pkg/requests/base_test.go index 56004ba92..674436602 100644 --- a/pkg/requests/base_test.go +++ b/pkg/requests/base_test.go @@ -111,7 +111,8 @@ func TestMakeRequest(t *testing.T) { expand: []string{"futurama.employees", "futurama.ships"}, } - _, err := rb.MakeRequest(context.Background(), "sk_test_1234", "/foo/bar", params, true) + apiKey := config.NewAPIKeyFromString("sk_test_1234", nil) + _, err := rb.MakeRequest(context.Background(), apiKey, "/foo/bar", params, true) require.NoError(t, err) } @@ -127,7 +128,8 @@ func TestMakeRequest_ErrOnStatus(t *testing.T) { params := &RequestParameters{} - _, err := rb.MakeRequest(context.Background(), "sk_test_1234", "/foo/bar", params, true) + apiKey := config.NewAPIKeyFromString("sk_test_1234", nil) + _, err := rb.MakeRequest(context.Background(), apiKey, "/foo/bar", params, true) require.Error(t, err) require.Equal(t, "Request failed, status=500, body=:(", err.Error()) } @@ -153,7 +155,8 @@ func TestMakeRequest_ErrOnAPIKeyExpired(t *testing.T) { params := &RequestParameters{} - _, err := rb.MakeRequest(context.Background(), "sk_test_1234", "/foo/bar", params, false) + apiKey := config.NewAPIKeyFromString("sk_test_1234", nil) + _, err := rb.MakeRequest(context.Background(), apiKey, "/foo/bar", params, false) require.Error(t, err) require.Contains(t, err.Error(), "Request failed, status=401, body=") } @@ -190,7 +193,8 @@ func TestMakeMultiPartRequest(t *testing.T) { data: []string{"purpose=app_upload", fmt.Sprintf("file=@%v", tempFile.Name())}, } - _, err = rb.MakeMultiPartRequest(context.Background(), "sk_test_1234", "/foo/bar", params, true) + apiKey := config.NewAPIKeyFromString("sk_test_1234", nil) + _, err = rb.MakeMultiPartRequest(context.Background(), apiKey, "/foo/bar", params, true) require.NoError(t, err) } diff --git a/pkg/requests/plugin.go b/pkg/requests/plugin.go index a279b7d0d..98e586747 100644 --- a/pkg/requests/plugin.go +++ b/pkg/requests/plugin.go @@ -15,7 +15,7 @@ type PluginData struct { } // GetPluginData returns the plugin download information -func GetPluginData(ctx context.Context, baseURL, apiVersion, apiKey string, profile *config.Profile) (PluginData, error) { +func GetPluginData(ctx context.Context, baseURL string, apiVersion string, apiKey *config.APIKey, profile *config.Profile) (PluginData, error) { params := &RequestParameters{ data: []string{}, version: apiVersion, diff --git a/pkg/requests/webhook_endpoints.go b/pkg/requests/webhook_endpoints.go index 8c086a39a..ce28c5f9b 100644 --- a/pkg/requests/webhook_endpoints.go +++ b/pkg/requests/webhook_endpoints.go @@ -25,7 +25,7 @@ type WebhookEndpoint struct { } // WebhookEndpointsList returns all the webhook endpoints on a users' account -func WebhookEndpointsList(ctx context.Context, baseURL, apiVersion, apiKey string, profile *config.Profile) WebhookEndpointList { +func WebhookEndpointsList(ctx context.Context, baseURL string, apiVersion string, apiKey *config.APIKey, profile *config.Profile) WebhookEndpointList { params := &RequestParameters{ data: []string{"limit=30"}, version: apiVersion, @@ -64,7 +64,7 @@ func WebhookEndpointsListWithClient(ctx context.Context, client stripe.RequestPe } // WebhookEndpointCreate creates a new webhook endpoint -func WebhookEndpointCreate(ctx context.Context, baseURL, apiVersion, apiKey, url, description string, connect bool, profile *config.Profile) error { +func WebhookEndpointCreate(ctx context.Context, baseURL string, apiVersion string, apiKey *config.APIKey, url, description string, connect bool, profile *config.Profile) error { if strings.TrimSpace(url) == "" { return fmt.Errorf("url cannot be empty") } diff --git a/pkg/rpcservice/fixtures.go b/pkg/rpcservice/fixtures.go index 0a21bb769..cbb3b160f 100644 --- a/pkg/rpcservice/fixtures.go +++ b/pkg/rpcservice/fixtures.go @@ -10,7 +10,7 @@ import ( // Fixture returns the default fixture of given event in string format func (srv *RPCService) Fixture(ctx context.Context, req *rpc.FixtureRequest) (*rpc.FixtureResponse, error) { fixtureFilename := fixtures.Events[req.Event] - f, err := fixtures.NewFixtureFromFile(nil, "", "", "", fixtureFilename, []string{}, []string{}, []string{}, []string{}, false) + f, err := fixtures.NewFixtureFromFile(nil, nil, "", "", fixtureFilename, []string{}, []string{}, []string{}, []string{}, false) if err != nil { return &rpc.FixtureResponse{Fixture: ""}, err } diff --git a/pkg/samples/samples.go b/pkg/samples/samples.go index c32e61298..cb24db33f 100644 --- a/pkg/samples/samples.go +++ b/pkg/samples/samples.go @@ -336,7 +336,7 @@ func ConfigureDotEnv(ctx context.Context, config *config.Config) (map[string]str return map[string]string{ "STRIPE_PUBLISHABLE_KEY": publishableKey, - "STRIPE_SECRET_KEY": apiKey, + "STRIPE_SECRET_KEY": apiKey.Key, "STRIPE_WEBHOOK_SECRET": authSession.Secret, "STATIC_DIR": "../client", }, nil diff --git a/pkg/stripe/client.go b/pkg/stripe/client.go index 9708df73d..68554ab03 100644 --- a/pkg/stripe/client.go +++ b/pkg/stripe/client.go @@ -2,6 +2,7 @@ package stripe import ( "context" + "github.com/stripe/stripe-cli/pkg/config" "io" "net" "net/http" @@ -35,7 +36,7 @@ type Client struct { // API key used to authenticate requests sent by this client. If left // empty, the `Authorization` header will be omitted. - APIKey string + APIKey *config.APIKey // When this is enabled, request and response headers will be printed to // stdout. @@ -82,8 +83,8 @@ func (c *Client) PerformRequest(ctx context.Context, method, path string, params req.Header.Set("User-Agent", useragent.GetEncodedUserAgent()) req.Header.Set("X-Stripe-Client-User-Agent", useragent.GetEncodedStripeUserAgent()) - if c.APIKey != "" { - req.Header.Set("Authorization", "Bearer "+c.APIKey) + if c.APIKey != nil { + req.Header.Set("Authorization", "Bearer "+c.APIKey.Key) } if configure != nil { @@ -107,7 +108,11 @@ func (c *Client) PerformRequest(ctx context.Context, method, path string, params // RequestID of the API Request requestID := resp.Header.Get("Request-Id") - livemode := strings.Contains(c.APIKey, "live") + + livemode := false + if c.APIKey != nil { + livemode = c.APIKey.Livemode + } go sendTelemetryEvent(ctx, requestID, livemode) return resp, nil } diff --git a/pkg/stripe/client_test.go b/pkg/stripe/client_test.go index 06f04d25e..257a340fd 100644 --- a/pkg/stripe/client_test.go +++ b/pkg/stripe/client_test.go @@ -3,6 +3,7 @@ package stripe import ( "context" "errors" + "github.com/stripe/stripe-cli/pkg/config" "io" "net/http" "net/http/httptest" @@ -99,7 +100,7 @@ func TestPerformRequest_ApiKey_Provided(t *testing.T) { baseURL, _ := url.Parse(ts.URL) client := Client{ BaseURL: baseURL, - APIKey: "sk_test_1234", + APIKey: config.NewAPIKeyFromString("sk_test_1234", nil), } resp, err := client.PerformRequest(context.Background(), http.MethodGet, "/get", "", nil) diff --git a/pkg/stripeauth/client_test.go b/pkg/stripeauth/client_test.go index 118b7c0a4..5dbb6ad2f 100644 --- a/pkg/stripeauth/client_test.go +++ b/pkg/stripeauth/client_test.go @@ -3,6 +3,7 @@ package stripeauth import ( "context" "encoding/json" + "github.com/stripe/stripe-cli/pkg/config" "net/http" "net/http/httptest" "net/url" @@ -38,7 +39,7 @@ func TestAuthorize(t *testing.T) { defer ts.Close() baseURL, _ := url.Parse(ts.URL) - client := NewClient(&stripe.Client{APIKey: "sk_test_123", BaseURL: baseURL}, nil) + client := NewClient(&stripe.Client{APIKey: config.NewAPIKeyFromString("sk_test_123", nil), BaseURL: baseURL}, nil) session, err := client.Authorize(context.Background(), CreateSessionRequest{ DeviceName: "my-device", @@ -61,7 +62,7 @@ func TestUserAgent(t *testing.T) { defer ts.Close() baseURL, _ := url.Parse(ts.URL) - client := NewClient(&stripe.Client{APIKey: "sk_test_123", BaseURL: baseURL}, nil) + client := NewClient(&stripe.Client{APIKey: config.NewAPIKeyFromString("sk_test_123", nil), BaseURL: baseURL}, nil) _, err := client.Authorize(context.Background(), CreateSessionRequest{ DeviceName: "my-device", @@ -88,7 +89,7 @@ func TestStripeClientUserAgent(t *testing.T) { defer ts.Close() baseURL, _ := url.Parse(ts.URL) - client := NewClient(&stripe.Client{APIKey: "sk_test_123", BaseURL: baseURL}, nil) + client := NewClient(&stripe.Client{APIKey: config.NewAPIKeyFromString("sk_test_123", nil), BaseURL: baseURL}, nil) _, err := client.Authorize(context.Background(), CreateSessionRequest{ DeviceName: "my-device", @@ -109,7 +110,7 @@ func TestAuthorizeWithURLDeviceMap(t *testing.T) { defer ts.Close() baseURL, _ := url.Parse(ts.URL) - client := NewClient(&stripe.Client{APIKey: "sk_test_123", BaseURL: baseURL}, nil) + client := NewClient(&stripe.Client{APIKey: config.NewAPIKeyFromString("sk_test_123", nil), BaseURL: baseURL}, nil) devURLMap := DeviceURLMap{ ForwardURL: "http://localhost:3000/events", diff --git a/pkg/terminal/p400/rabbit_service_test.go b/pkg/terminal/p400/rabbit_service_test.go index dd7f57e1f..9c927a00e 100644 --- a/pkg/terminal/p400/rabbit_service_test.go +++ b/pkg/terminal/p400/rabbit_service_test.go @@ -1,6 +1,7 @@ package p400 import ( + "github.com/stripe/stripe-cli/pkg/config" "testing" "github.com/stretchr/testify/require" @@ -8,7 +9,7 @@ import ( func TestCreateRabbitServicePayload(t *testing.T) { tsCtx := TerminalSessionContext{ - APIKey: "sk_123", + APIKey: config.NewAPIKeyFromString("sk_123", nil), DeviceInfo: DeviceInfo{ DeviceClass: "POS", DeviceUUID: "pos-1234", diff --git a/pkg/terminal/p400/reader_methods.go b/pkg/terminal/p400/reader_methods.go index 9b360aff5..926a65be0 100644 --- a/pkg/terminal/p400/reader_methods.go +++ b/pkg/terminal/p400/reader_methods.go @@ -2,6 +2,7 @@ package p400 import ( "fmt" + "github.com/stripe/stripe-cli/pkg/config" "math/rand" "runtime" "strconv" @@ -13,7 +14,7 @@ import ( // TerminalSessionContext is a type that contains important context most methods need to know to complete the quickstart flow // one copy of this is passed around a lot and is mutable for whenever a property needs to change type TerminalSessionContext struct { - APIKey string + APIKey *config.APIKey IPAddress string BaseURL string LocationID string diff --git a/pkg/terminal/p400/stripe_requests.go b/pkg/terminal/p400/stripe_requests.go index 6ddecc064..9ff2103ad 100644 --- a/pkg/terminal/p400/stripe_requests.go +++ b/pkg/terminal/p400/stripe_requests.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "fmt" + "github.com/stripe/stripe-cli/pkg/config" "net/http" "net/url" "strconv" @@ -69,7 +70,7 @@ func DiscoverReaders(ctx context.Context, tsCtx TerminalSessionContext) ([]Reade client := &stripe.Client{ BaseURL: parsedBaseURL, - APIKey: tsCtx.PstToken, + APIKey: config.NewAPIKeyFromString(tsCtx.PstToken, nil), Verbose: false, } From 738b51a50561c50f4e8ca2c702e2622b4a0f2846 Mon Sep 17 00:00:00 2001 From: Fred Hatfull Date: Wed, 14 Jun 2023 15:42:04 -0700 Subject: [PATCH 3/8] Enhances login to use the key expiration returned from API result --- pkg/config/profile.go | 3 --- pkg/login/client_login.go | 12 +++++++++++- pkg/login/keys/keytransfer.go | 2 ++ pkg/login/keys/polling.go | 2 ++ 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/pkg/config/profile.go b/pkg/config/profile.go index 024a4a158..038797028 100644 --- a/pkg/config/profile.go +++ b/pkg/config/profile.go @@ -48,9 +48,6 @@ const ( // DateStringFormat is the format for expiredAt date DateStringFormat = "2006-01-02" - // KeyValidInDays is the number of days the API key is valid for - KeyValidInDays = 90 - // KeyManagementService is the key management service name KeyManagementService = "Stripe CLI" ) diff --git a/pkg/login/client_login.go b/pkg/login/client_login.go index a6d824408..b86e1d309 100644 --- a/pkg/login/client_login.go +++ b/pkg/login/client_login.go @@ -4,7 +4,9 @@ import ( "context" "fmt" "github.com/stripe/stripe-cli/pkg/config" + "math" "os" + "time" "github.com/briandowns/spinner" @@ -90,7 +92,15 @@ func (a *Authenticator) Login(ctx context.Context, links *Links) error { } else { ansi.StopSpinner(s, message, os.Stdout) } - fmt.Println(ansi.Italic("Please note: this key will expire after 90 days, at which point you'll need to re-authenticate.")) + + keyValidityDurationDays := 90 + if !res.KeyExpiration.IsZero() { + keyValidityDurationHours := res.KeyExpiration.Sub(time.Now()).Hours() + keyValidityDurationDays = int(math.Round(keyValidityDurationHours / 24.0)) + } + + keyDurationCourtesyMessage := fmt.Sprintf("Please note: this key will expire after %d days, at which point you'll need to re-authenticate.", keyValidityDurationDays) + fmt.Println(ansi.Italic(keyDurationCourtesyMessage)) return nil } } diff --git a/pkg/login/keys/keytransfer.go b/pkg/login/keys/keytransfer.go index 56a163813..5260b4741 100644 --- a/pkg/login/keys/keytransfer.go +++ b/pkg/login/keys/keytransfer.go @@ -11,6 +11,7 @@ import ( type AsyncPollResult struct { TestModeAPIKey string Account *acct.Account + KeyExpiration time.Time Err error } @@ -57,6 +58,7 @@ func (rt *RAKTransfer) AsyncPollKey(ctx context.Context, pollURL string, interva ch <- AsyncPollResult{ TestModeAPIKey: response.TestModeAPIKey, + KeyExpiration: time.Unix(response.TestModeAPIKeyExpiry, 0), Account: account, Err: nil, } diff --git a/pkg/login/keys/polling.go b/pkg/login/keys/polling.go index 56a77c237..dbacb5428 100644 --- a/pkg/login/keys/polling.go +++ b/pkg/login/keys/polling.go @@ -23,8 +23,10 @@ type PollAPIKeyResponse struct { AccountID string `json:"account_id"` AccountDisplayName string `json:"account_display_name"` LiveModeAPIKey string `json:"livemode_key_secret"` + LiveModeAPIKeyExpiry int64 `json:"livemode_key_expiry"` LiveModePublishableKey string `json:"livemode_key_publishable"` TestModeAPIKey string `json:"testmode_key_secret"` + TestModeAPIKeyExpiry int64 `json:"testmode_key_expiry"` TestModePublishableKey string `json:"testmode_key_publishable"` } From b950606ce89503ebdd410f24b43de0a330e1f57f Mon Sep 17 00:00:00 2001 From: Fred Hatfull Date: Thu, 15 Jun 2023 14:26:19 -0700 Subject: [PATCH 4/8] Adds actual warn capability --- pkg/config/api_key.go | 86 ++++++++++++++++++ pkg/config/api_key_test.go | 174 +++++++++++++++++++++++++++++++++++++ pkg/config/profile.go | 5 +- 3 files changed, 264 insertions(+), 1 deletion(-) diff --git a/pkg/config/api_key.go b/pkg/config/api_key.go index 5c350d64f..0efe28a0b 100644 --- a/pkg/config/api_key.go +++ b/pkg/config/api_key.go @@ -1,10 +1,28 @@ package config import ( + "fmt" + "github.com/spf13/viper" + "github.com/stripe/stripe-cli/pkg/ansi" + "math" + "os" + "strconv" "strings" "time" ) +const LiveModeKeyLastExpirationWarningField = "live_mode_api_key_last_expiration_warning" +const TestModeKeyLastExpirationWarningField = "test_mode_api_key_last_expiration_warning" + +const upcomingExpirationThreshold = 14 * 24 * time.Hour +const imminentExpirationThreshold = 24 * time.Hour + +const upcomingExpirationReminderFrequency = 12 * time.Hour + +// Useful for stubbing in tests +var timeNow = time.Now +var printWarning = printWarningMessage + type APIKey struct { Key string Livemode bool @@ -40,3 +58,71 @@ func NewAPIKeyFromString(key string, profile *Profile) *APIKey { profile: profile, } } + +func (k *APIKey) WarnIfExpirationSoon(profile *Profile) { + if k.Expiration.IsZero() { + return + } + + remainingValidity := k.Expiration.Sub(timeNow()) + if k.shouldShowImminentExpirationWarning() { + warnMsg := fmt.Sprintf("Your API key will expire in less than %.0f hours. You can obtain a new key from the Dashboard or `stripe login`.", imminentExpirationThreshold.Hours()) + printWarning(warnMsg) + _ = k.setLastExpirationWarning(timeNow(), profile) + } else if k.shouldShowUpcomingExpirationWarning(profile) { + remainingDays := int(math.Round(remainingValidity.Hours() / 24.0)) + warnMsg := fmt.Sprintf("Your API key will expire in %d days. You can obtain a new key from the Dashboard or `stripe login`.", remainingDays) + printWarning(warnMsg) + _ = k.setLastExpirationWarning(timeNow(), profile) + } else { + + } +} + +func (k *APIKey) shouldShowImminentExpirationWarning() bool { + remainingValidity := k.Expiration.Sub(timeNow()) + return remainingValidity < imminentExpirationThreshold +} + +func (k *APIKey) shouldShowUpcomingExpirationWarning(profile *Profile) bool { + remainingValidity := k.Expiration.Sub(timeNow()) + if remainingValidity < upcomingExpirationThreshold { + lastWarning := k.fetchLastExpirationWarning(profile) + + if timeNow().Sub(lastWarning) > upcomingExpirationReminderFrequency { + return true + } + } + + return false +} + +func (k *APIKey) fetchLastExpirationWarning(profile *Profile) time.Time { + configKey := profile.GetConfigField(k.expirationWarningField()) + lastWarningTimeString := viper.GetString(configKey) + lastWarningUnixTime, err := strconv.ParseInt(lastWarningTimeString, 10, 64) + if err != nil { + return time.Time{} + } + + return time.Unix(lastWarningUnixTime, 0) +} + +func (k *APIKey) setLastExpirationWarning(warningTime time.Time, profile *Profile) error { + timeStr := strconv.FormatInt(warningTime.Unix(), 10) + return profile.WriteConfigField(k.expirationWarningField(), timeStr) +} + +func (k *APIKey) expirationWarningField() string { + if k.Livemode { + return LiveModeKeyLastExpirationWarningField + } else { + return TestModeKeyLastExpirationWarningField + } +} + +func printWarningMessage(message string) { + formattedMessage := ansi.Color(os.Stderr).Yellow(message).Bold() + _, err := fmt.Fprintln(os.Stderr, formattedMessage) + _ = err +} diff --git a/pkg/config/api_key_test.go b/pkg/config/api_key_test.go index a1c490d33..536d32257 100644 --- a/pkg/config/api_key_test.go +++ b/pkg/config/api_key_test.go @@ -1,8 +1,13 @@ package config import ( + "fmt" + "github.com/spf13/viper" "github.com/stretchr/testify/assert" + "os" + "path/filepath" "testing" + "time" ) func TestNewAPIKeyFromString(t *testing.T) { @@ -22,3 +27,172 @@ func TestNewAPIKeyFromString(t *testing.T) { assert.Zero(t, testmodeKey.Expiration) assert.Nil(t, testmodeKey.profile) } + +func TestWarnIfExpirationSoon(t *testing.T) { + t.Run("warn repeatedly when expiration is imminent", func(t *testing.T) { + now := time.Unix(1000, 0) + expiration := now.Add(imminentExpirationThreshold - 1*time.Hour) + + timeCleanup := setupFakeTimeNow(now) + defer timeCleanup() + + printed, printWarningCleanup := setupFakePrintWarning() + defer printWarningCleanup() + + k := &APIKey{ + Key: "rk_test_1234", + Livemode: false, + Expiration: expiration, + profile: nil, + } + + config, configCleanup := setupTestConfig(k) + defer configCleanup() + + k.WarnIfExpirationSoon(&config.Profile) + assert.Equal(t, 1, len(printed.messages)) + + k.WarnIfExpirationSoon(&config.Profile) + assert.Equal(t, 2, len(printed.messages)) + }) + + t.Run("warn once per period when expiration is upcoming", func(t *testing.T) { + now := time.Unix(5000, 0) + expiration := now.Add(upcomingExpirationThreshold - 1*time.Hour) + + initialTimeCleanup := setupFakeTimeNow(now) + defer initialTimeCleanup() + + printed, printWarningCleanup := setupFakePrintWarning() + defer printWarningCleanup() + + k := &APIKey{ + Key: "rk_test_1234", + Livemode: false, + Expiration: expiration, + profile: nil, + } + + config, configCleanup := setupTestConfig(k) + defer configCleanup() + + nextTime := now + for i := 0; i < 4; i++ { + nextTime = nextTime.Add(upcomingExpirationReminderFrequency + 1*time.Hour) + + advancedTimeCleanup := setupFakeTimeNow(nextTime) + defer advancedTimeCleanup() + + k.WarnIfExpirationSoon(&config.Profile) + assert.Equal(t, i+1, len(printed.messages)) + + k.WarnIfExpirationSoon(&config.Profile) + assert.Equal(t, i+1, len(printed.messages)) + + k.WarnIfExpirationSoon(&config.Profile) + assert.Equal(t, i+1, len(printed.messages)) + } + }) + + t.Run("do not warn when expiration is not near", func(t *testing.T) { + now := time.Unix(900000, 0) + expiration := now.Add(90 * 24 * time.Hour) + + initialTimeCleanup := setupFakeTimeNow(now) + defer initialTimeCleanup() + + printed, printWarningCleanup := setupFakePrintWarning() + defer printWarningCleanup() + + k := &APIKey{ + Key: "rk_test_1234", + Livemode: false, + Expiration: expiration, + profile: nil, + } + + config, configCleanup := setupTestConfig(k) + defer configCleanup() + + k.WarnIfExpirationSoon(&config.Profile) + assert.Equal(t, 0, len(printed.messages)) + }) + + t.Run("do not warn when expiration is unset", func(t *testing.T) { + now := time.Unix(900000, 0) + + initialTimeCleanup := setupFakeTimeNow(now) + defer initialTimeCleanup() + + printed, printWarningCleanup := setupFakePrintWarning() + defer printWarningCleanup() + + k := NewAPIKeyFromString("rk_test_1234", nil) + + config, configCleanup := setupTestConfig(k) + defer configCleanup() + + k.WarnIfExpirationSoon(&config.Profile) + assert.Equal(t, 0, len(printed.messages)) + }) +} + +func setupTestConfig(testmodeKey *APIKey) (*Config, func()) { + uniqueConfig := fmt.Sprintf("config-%d.toml", time.Now().UnixMilli()) + profilesFile := filepath.Join(os.TempDir(), "stripe", uniqueConfig) + + p := Profile{ + DeviceName: "st-testing", + ProfileName: "tests", + DisplayName: "test-account-display-name", + TestModeAPIKey: testmodeKey, + } + + c := &Config{ + Color: "auto", + LogLevel: "info", + Profile: p, + ProfilesFile: profilesFile, + } + c.InitConfig() + + v := viper.New() + _ = p.writeProfile(v) + + return c, func() { + _ = os.Remove(profilesFile) + } +} + +// Mocks the result of time.Now as used in api_key.go and returns a cleanup +// function which should be called in a defer in the consuming test. +func setupFakeTimeNow(t time.Time) func() { + original := timeNow + timeNow = func() time.Time { + return t + } + + return func() { + timeNow = original + } +} + +// This struct encapsulates the message slice since that's the most idiomatic +// way to retain a pointer to the slice outside of the mocked function +type messageRecorder struct { + messages []string +} + +func setupFakePrintWarning() (*messageRecorder, func()) { + original := printWarning + + printed := &messageRecorder{} + + printWarning = func(message string) { + printed.messages = append(printed.messages, message) + } + + return printed, func() { + printWarning = original + } +} diff --git a/pkg/config/profile.go b/pkg/config/profile.go index 038797028..1a97c3dd6 100644 --- a/pkg/config/profile.go +++ b/pkg/config/profile.go @@ -199,7 +199,10 @@ func (p *Profile) GetAPIKey(livemode bool) (*APIKey, error) { if err != nil { return nil, err } - return NewAPIKey(key, keyExpiry, livemode, p), nil + + apiKey := NewAPIKey(key, keyExpiry, livemode, p) + apiKey.WarnIfExpirationSoon(p) + return apiKey, nil } return nil, validators.ErrAPIKeyNotConfigured From 1a59cf5b481964c3f427d210364a03f5151350df Mon Sep 17 00:00:00 2001 From: Fred Hatfull Date: Thu, 15 Jun 2023 14:34:06 -0700 Subject: [PATCH 5/8] removes unnecessary profile pointer from APIKey --- pkg/config/api_key.go | 7 ++----- pkg/config/api_key_test.go | 11 +++-------- pkg/config/profile.go | 6 +++--- pkg/config/profile_test.go | 12 ++++++------ pkg/fixtures/fixtures_test.go | 2 +- pkg/login/acct/retrieve_account_test.go | 4 ++-- pkg/login/client_login.go | 2 +- pkg/login/interactive_login.go | 4 ++-- pkg/login/interactive_login_test.go | 8 ++++---- pkg/login/keys/configurer.go | 4 ++-- pkg/login/login_message_test.go | 10 +++++----- pkg/requests/base_test.go | 8 ++++---- pkg/stripe/client_test.go | 2 +- pkg/stripeauth/client_test.go | 8 ++++---- pkg/terminal/p400/rabbit_service_test.go | 2 +- pkg/terminal/p400/stripe_requests.go | 2 +- 16 files changed, 42 insertions(+), 50 deletions(-) diff --git a/pkg/config/api_key.go b/pkg/config/api_key.go index 0efe28a0b..a7fbb1043 100644 --- a/pkg/config/api_key.go +++ b/pkg/config/api_key.go @@ -27,10 +27,9 @@ type APIKey struct { Key string Livemode bool Expiration time.Time - profile *Profile } -func NewAPIKey(key string, expiration time.Time, livemode bool, profile *Profile) *APIKey { +func NewAPIKey(key string, expiration time.Time, livemode bool) *APIKey { if key == "" { return nil } @@ -39,11 +38,10 @@ func NewAPIKey(key string, expiration time.Time, livemode bool, profile *Profile Key: key, Livemode: livemode, Expiration: expiration, - profile: profile, } } -func NewAPIKeyFromString(key string, profile *Profile) *APIKey { +func NewAPIKeyFromString(key string) *APIKey { if key == "" { return nil } @@ -55,7 +53,6 @@ func NewAPIKeyFromString(key string, profile *Profile) *APIKey { Livemode: strings.Contains(key, "live"), // Expiration intentionally omitted to leave it as the zero value, since // it's not known when e.g. a key is passed using an environment variable. - profile: profile, } } diff --git a/pkg/config/api_key_test.go b/pkg/config/api_key_test.go index 536d32257..ce28b1ebb 100644 --- a/pkg/config/api_key_test.go +++ b/pkg/config/api_key_test.go @@ -14,18 +14,16 @@ func TestNewAPIKeyFromString(t *testing.T) { sampleLivemodeKeyString := "rk_live_1234" sampleTestmodeKeyString := "rk_test_1234" - livemodeKey := NewAPIKeyFromString(sampleLivemodeKeyString, nil) - testmodeKey := NewAPIKeyFromString(sampleTestmodeKeyString, nil) + livemodeKey := NewAPIKeyFromString(sampleLivemodeKeyString) + testmodeKey := NewAPIKeyFromString(sampleTestmodeKeyString) assert.Equal(t, sampleLivemodeKeyString, livemodeKey.Key) assert.True(t, livemodeKey.Livemode) assert.Zero(t, livemodeKey.Expiration) - assert.Nil(t, livemodeKey.profile) assert.Equal(t, sampleTestmodeKeyString, testmodeKey.Key) assert.False(t, testmodeKey.Livemode) assert.Zero(t, testmodeKey.Expiration) - assert.Nil(t, testmodeKey.profile) } func TestWarnIfExpirationSoon(t *testing.T) { @@ -43,7 +41,6 @@ func TestWarnIfExpirationSoon(t *testing.T) { Key: "rk_test_1234", Livemode: false, Expiration: expiration, - profile: nil, } config, configCleanup := setupTestConfig(k) @@ -70,7 +67,6 @@ func TestWarnIfExpirationSoon(t *testing.T) { Key: "rk_test_1234", Livemode: false, Expiration: expiration, - profile: nil, } config, configCleanup := setupTestConfig(k) @@ -108,7 +104,6 @@ func TestWarnIfExpirationSoon(t *testing.T) { Key: "rk_test_1234", Livemode: false, Expiration: expiration, - profile: nil, } config, configCleanup := setupTestConfig(k) @@ -127,7 +122,7 @@ func TestWarnIfExpirationSoon(t *testing.T) { printed, printWarningCleanup := setupFakePrintWarning() defer printWarningCleanup() - k := NewAPIKeyFromString("rk_test_1234", nil) + k := NewAPIKeyFromString("rk_test_1234") config, configCleanup := setupTestConfig(k) defer configCleanup() diff --git a/pkg/config/profile.go b/pkg/config/profile.go index 1a97c3dd6..1d26ca43a 100644 --- a/pkg/config/profile.go +++ b/pkg/config/profile.go @@ -141,7 +141,7 @@ func (p *Profile) GetAPIKey(livemode bool) (*APIKey, error) { return nil, err } - return NewAPIKeyFromString(envKey, p), nil + return NewAPIKeyFromString(envKey), nil } if p.APIKey != "" { @@ -150,7 +150,7 @@ func (p *Profile) GetAPIKey(livemode bool) (*APIKey, error) { return nil, err } - return NewAPIKeyFromString(p.APIKey, p), nil + return NewAPIKeyFromString(p.APIKey), nil } var key string @@ -200,7 +200,7 @@ func (p *Profile) GetAPIKey(livemode bool) (*APIKey, error) { return nil, err } - apiKey := NewAPIKey(key, keyExpiry, livemode, p) + apiKey := NewAPIKey(key, keyExpiry, livemode) apiKey.WarnIfExpirationSoon(p) return apiKey, nil } diff --git a/pkg/config/profile_test.go b/pkg/config/profile_test.go index aaeb652dc..1be835afb 100644 --- a/pkg/config/profile_test.go +++ b/pkg/config/profile_test.go @@ -17,7 +17,7 @@ func TestWriteProfile(t *testing.T) { p := Profile{ DeviceName: "st-testing", ProfileName: "tests", - TestModeAPIKey: NewAPIKey("sk_test_123", expirationTime, false, nil), + TestModeAPIKey: NewAPIKey("sk_test_123", expirationTime, false), DisplayName: "test-account-display-name", } @@ -59,7 +59,7 @@ func TestWriteProfilesMerge(t *testing.T) { p := Profile{ ProfileName: "tests", DeviceName: "st-testing", - TestModeAPIKey: NewAPIKey("sk_test_123", expirationTime, false, nil), + TestModeAPIKey: NewAPIKey("sk_test_123", expirationTime, false), DisplayName: "test-account-display-name", } @@ -109,7 +109,7 @@ func TestExperimentalFields(t *testing.T) { p := Profile{ ProfileName: "tests", DeviceName: "st-testing", - TestModeAPIKey: NewAPIKey("sk_test_123", expirationTime, false, nil), + TestModeAPIKey: NewAPIKey("sk_test_123", expirationTime, false), DisplayName: "test-account-display-name", } c := &Config{ @@ -153,7 +153,7 @@ func TestOldProfileDeleted(t *testing.T) { p := Profile{ ProfileName: "test", DeviceName: "device-before-test", - TestModeAPIKey: NewAPIKey("sk_test_123", expirationTime, false, nil), + TestModeAPIKey: NewAPIKey("sk_test_123", expirationTime, false), DisplayName: "display-name-before-test", } c := &Config{ @@ -175,7 +175,7 @@ func TestOldProfileDeleted(t *testing.T) { untouchedProfile := Profile{ ProfileName: "foo", DeviceName: "foo-device-name", - TestModeAPIKey: NewAPIKey("foo_test_123", expirationTime, false, nil), + TestModeAPIKey: NewAPIKey("foo_test_123", expirationTime, false), } err = untouchedProfile.writeProfile(v) require.NoError(t, err) @@ -183,7 +183,7 @@ func TestOldProfileDeleted(t *testing.T) { p = Profile{ ProfileName: "test", DeviceName: "device-after-test", - TestModeAPIKey: NewAPIKey("sk_test_456", expirationTime, false, nil), + TestModeAPIKey: NewAPIKey("sk_test_456", expirationTime, false), DisplayName: "", } diff --git a/pkg/fixtures/fixtures_test.go b/pkg/fixtures/fixtures_test.go index ed0d96ccd..fcb263593 100644 --- a/pkg/fixtures/fixtures_test.go +++ b/pkg/fixtures/fixtures_test.go @@ -81,7 +81,7 @@ const failureTestFixture = ` ] }` -var apiKey = config.NewAPIKeyFromString("sk_test_1234", nil) +var apiKey = config.NewAPIKeyFromString("sk_test_1234") const file = "test_fixture.json" const customersPath = "/v1/customers" diff --git a/pkg/login/acct/retrieve_account_test.go b/pkg/login/acct/retrieve_account_test.go index 1d7a7b71f..745c55d60 100644 --- a/pkg/login/acct/retrieve_account_test.go +++ b/pkg/login/acct/retrieve_account_test.go @@ -28,7 +28,7 @@ func TestGetAccount(t *testing.T) { })) defer ts.Close() - acc, err := GetUserAccount(context.Background(), ts.URL, config.NewAPIKeyFromString("sk_test_123", nil)) + acc, err := GetUserAccount(context.Background(), ts.URL, config.NewAPIKeyFromString("sk_test_123")) require.NoError(t, err) require.Equal( t, @@ -56,7 +56,7 @@ func TestGetAccountNoDisplayName(t *testing.T) { })) defer ts.Close() - acc, err := GetUserAccount(context.Background(), ts.URL, config.NewAPIKeyFromString("sk_test_123", nil)) + acc, err := GetUserAccount(context.Background(), ts.URL, config.NewAPIKeyFromString("sk_test_123")) require.NoError(t, err) require.Equal( t, diff --git a/pkg/login/client_login.go b/pkg/login/client_login.go index b86e1d309..cc5b7dd04 100644 --- a/pkg/login/client_login.go +++ b/pkg/login/client_login.go @@ -80,7 +80,7 @@ func (a *Authenticator) Login(ctx context.Context, links *Links) error { return res.Err } - apiKey := config.NewAPIKeyFromString(res.TestModeAPIKey, nil) + apiKey := config.NewAPIKeyFromString(res.TestModeAPIKey) message, err := SuccessMessage(ctx, res.Account, stripe.DefaultAPIBaseURL, apiKey) if err != nil { fmt.Printf("> Error verifying the CLI was set up successfully: %s\n", err) diff --git a/pkg/login/interactive_login.go b/pkg/login/interactive_login.go index a64f4ce3c..3abbbb8e0 100644 --- a/pkg/login/interactive_login.go +++ b/pkg/login/interactive_login.go @@ -27,10 +27,10 @@ func InteractiveLogin(ctx context.Context, config *config_pkg.Config) error { return err } - apiKey := config_pkg.NewAPIKeyFromString(apiKeyString, nil) + apiKey := config_pkg.NewAPIKeyFromString(apiKeyString) config.Profile.DeviceName = getConfigureDeviceName(os.Stdin) - config.Profile.TestModeAPIKey = config_pkg.NewAPIKeyFromString(apiKeyString, &config.Profile) + config.Profile.TestModeAPIKey = config_pkg.NewAPIKeyFromString(apiKeyString) displayName, _ := getDisplayName(ctx, nil, stripe.DefaultAPIBaseURL, apiKey) config.Profile.DisplayName = displayName diff --git a/pkg/login/interactive_login_test.go b/pkg/login/interactive_login_test.go index e1752693a..594a7e7f2 100644 --- a/pkg/login/interactive_login_test.go +++ b/pkg/login/interactive_login_test.go @@ -23,7 +23,7 @@ func TestDisplayName(t *testing.T) { } account.Settings.Dashboard.DisplayName = testAccountName - var apiKey = config.NewAPIKeyFromString("sk_test_123", nil) + var apiKey = config.NewAPIKeyFromString("sk_test_123") displayName, err := getDisplayName(context.Background(), account, "", apiKey) require.NoError(t, err) require.Equal( @@ -38,7 +38,7 @@ func TestDisplayNameNoName(t *testing.T) { ID: "acct_123", } - var apiKey = config.NewAPIKeyFromString("sk_test_123", nil) + var apiKey = config.NewAPIKeyFromString("sk_test_123") displayName, err := getDisplayName(context.Background(), account, "", apiKey) require.NoError(t, err) require.Equal( @@ -63,7 +63,7 @@ func TestDisplayNameGetAccount(t *testing.T) { })) defer ts.Close() - var apiKey = config.NewAPIKeyFromString("sk_test_123", nil) + var apiKey = config.NewAPIKeyFromString("sk_test_123") displayName, err := getDisplayName(context.Background(), nil, ts.URL, apiKey) require.NoError(t, err) require.Equal( @@ -87,7 +87,7 @@ func TestDisplayNameGetAccountNoName(t *testing.T) { })) defer ts.Close() - var apiKey = config.NewAPIKeyFromString("sk_test_123", nil) + var apiKey = config.NewAPIKeyFromString("sk_test_123") displayName, err := getDisplayName(context.Background(), nil, ts.URL, apiKey) require.NoError(t, err) require.Equal( diff --git a/pkg/login/keys/configurer.go b/pkg/login/keys/configurer.go index 28aa1e27e..3d497602d 100644 --- a/pkg/login/keys/configurer.go +++ b/pkg/login/keys/configurer.go @@ -35,12 +35,12 @@ func (c *RAKConfigurer) SaveLoginDetails(response *PollAPIKeyResponse) error { } if response.LiveModeAPIKey != "" { - c.cfg.Profile.LiveModeAPIKey = config.NewAPIKey(response.LiveModeAPIKey, time.Unix(response.LiveModeAPIKeyExpiry, 0), true, &c.cfg.Profile) + c.cfg.Profile.LiveModeAPIKey = config.NewAPIKey(response.LiveModeAPIKey, time.Unix(response.LiveModeAPIKeyExpiry, 0), true) } c.cfg.Profile.LiveModePublishableKey = response.LiveModePublishableKey if response.TestModeAPIKey != "" { - c.cfg.Profile.TestModeAPIKey = config.NewAPIKey(response.TestModeAPIKey, time.Unix(response.TestModeAPIKeyExpiry, 0), false, &c.cfg.Profile) + c.cfg.Profile.TestModeAPIKey = config.NewAPIKey(response.TestModeAPIKey, time.Unix(response.TestModeAPIKeyExpiry, 0), false) } c.cfg.Profile.TestModePublishableKey = response.TestModePublishableKey diff --git a/pkg/login/login_message_test.go b/pkg/login/login_message_test.go index bee27dc9f..1e7fb552f 100644 --- a/pkg/login/login_message_test.go +++ b/pkg/login/login_message_test.go @@ -21,7 +21,7 @@ func TestSuccessMessage(t *testing.T) { } account.Settings.Dashboard.DisplayName = testDisplayName - var apiKey = config.NewAPIKeyFromString("sk_test_123", nil) + var apiKey = config.NewAPIKeyFromString("sk_test_123") msg, err := SuccessMessage(context.Background(), account, "", apiKey) require.NoError(t, err) require.Equal( @@ -36,7 +36,7 @@ func TestSuccessMessageNoDisplayName(t *testing.T) { ID: "acct_123", } - var apiKey = config.NewAPIKeyFromString("sk_test_123", nil) + var apiKey = config.NewAPIKeyFromString("sk_test_123") msg, err := SuccessMessage(context.Background(), account, "", apiKey) require.NoError(t, err) require.Equal( @@ -49,7 +49,7 @@ func TestSuccessMessageNoDisplayName(t *testing.T) { func TestSuccessMessageBasicMessage(t *testing.T) { account := &acct.Account{} - var apiKey = config.NewAPIKeyFromString("sk_test_123", nil) + var apiKey = config.NewAPIKeyFromString("sk_test_123") msg, err := SuccessMessage(context.Background(), account, "", apiKey) require.NoError(t, err) require.Equal( @@ -74,7 +74,7 @@ func TestSuccessMessageGetAccount(t *testing.T) { })) defer ts.Close() - var apiKey = config.NewAPIKeyFromString("sk_test_123", nil) + var apiKey = config.NewAPIKeyFromString("sk_test_123") msg, err := SuccessMessage(context.Background(), nil, ts.URL, apiKey) require.NoError(t, err) require.Equal( @@ -98,7 +98,7 @@ func TestSuccessMessageGetAccountNoDisplayName(t *testing.T) { })) defer ts.Close() - var apiKey = config.NewAPIKeyFromString("sk_test_123", nil) + var apiKey = config.NewAPIKeyFromString("sk_test_123") msg, err := SuccessMessage(context.Background(), nil, ts.URL, apiKey) require.NoError(t, err) require.Equal( diff --git a/pkg/requests/base_test.go b/pkg/requests/base_test.go index 674436602..6e6a4877f 100644 --- a/pkg/requests/base_test.go +++ b/pkg/requests/base_test.go @@ -111,7 +111,7 @@ func TestMakeRequest(t *testing.T) { expand: []string{"futurama.employees", "futurama.ships"}, } - apiKey := config.NewAPIKeyFromString("sk_test_1234", nil) + apiKey := config.NewAPIKeyFromString("sk_test_1234") _, err := rb.MakeRequest(context.Background(), apiKey, "/foo/bar", params, true) require.NoError(t, err) } @@ -128,7 +128,7 @@ func TestMakeRequest_ErrOnStatus(t *testing.T) { params := &RequestParameters{} - apiKey := config.NewAPIKeyFromString("sk_test_1234", nil) + apiKey := config.NewAPIKeyFromString("sk_test_1234") _, err := rb.MakeRequest(context.Background(), apiKey, "/foo/bar", params, true) require.Error(t, err) require.Equal(t, "Request failed, status=500, body=:(", err.Error()) @@ -155,7 +155,7 @@ func TestMakeRequest_ErrOnAPIKeyExpired(t *testing.T) { params := &RequestParameters{} - apiKey := config.NewAPIKeyFromString("sk_test_1234", nil) + apiKey := config.NewAPIKeyFromString("sk_test_1234") _, err := rb.MakeRequest(context.Background(), apiKey, "/foo/bar", params, false) require.Error(t, err) require.Contains(t, err.Error(), "Request failed, status=401, body=") @@ -193,7 +193,7 @@ func TestMakeMultiPartRequest(t *testing.T) { data: []string{"purpose=app_upload", fmt.Sprintf("file=@%v", tempFile.Name())}, } - apiKey := config.NewAPIKeyFromString("sk_test_1234", nil) + apiKey := config.NewAPIKeyFromString("sk_test_1234") _, err = rb.MakeMultiPartRequest(context.Background(), apiKey, "/foo/bar", params, true) require.NoError(t, err) } diff --git a/pkg/stripe/client_test.go b/pkg/stripe/client_test.go index 257a340fd..9f05fc536 100644 --- a/pkg/stripe/client_test.go +++ b/pkg/stripe/client_test.go @@ -100,7 +100,7 @@ func TestPerformRequest_ApiKey_Provided(t *testing.T) { baseURL, _ := url.Parse(ts.URL) client := Client{ BaseURL: baseURL, - APIKey: config.NewAPIKeyFromString("sk_test_1234", nil), + APIKey: config.NewAPIKeyFromString("sk_test_1234"), } resp, err := client.PerformRequest(context.Background(), http.MethodGet, "/get", "", nil) diff --git a/pkg/stripeauth/client_test.go b/pkg/stripeauth/client_test.go index 5dbb6ad2f..52f20a970 100644 --- a/pkg/stripeauth/client_test.go +++ b/pkg/stripeauth/client_test.go @@ -39,7 +39,7 @@ func TestAuthorize(t *testing.T) { defer ts.Close() baseURL, _ := url.Parse(ts.URL) - client := NewClient(&stripe.Client{APIKey: config.NewAPIKeyFromString("sk_test_123", nil), BaseURL: baseURL}, nil) + client := NewClient(&stripe.Client{APIKey: config.NewAPIKeyFromString("sk_test_123"), BaseURL: baseURL}, nil) session, err := client.Authorize(context.Background(), CreateSessionRequest{ DeviceName: "my-device", @@ -62,7 +62,7 @@ func TestUserAgent(t *testing.T) { defer ts.Close() baseURL, _ := url.Parse(ts.URL) - client := NewClient(&stripe.Client{APIKey: config.NewAPIKeyFromString("sk_test_123", nil), BaseURL: baseURL}, nil) + client := NewClient(&stripe.Client{APIKey: config.NewAPIKeyFromString("sk_test_123"), BaseURL: baseURL}, nil) _, err := client.Authorize(context.Background(), CreateSessionRequest{ DeviceName: "my-device", @@ -89,7 +89,7 @@ func TestStripeClientUserAgent(t *testing.T) { defer ts.Close() baseURL, _ := url.Parse(ts.URL) - client := NewClient(&stripe.Client{APIKey: config.NewAPIKeyFromString("sk_test_123", nil), BaseURL: baseURL}, nil) + client := NewClient(&stripe.Client{APIKey: config.NewAPIKeyFromString("sk_test_123"), BaseURL: baseURL}, nil) _, err := client.Authorize(context.Background(), CreateSessionRequest{ DeviceName: "my-device", @@ -110,7 +110,7 @@ func TestAuthorizeWithURLDeviceMap(t *testing.T) { defer ts.Close() baseURL, _ := url.Parse(ts.URL) - client := NewClient(&stripe.Client{APIKey: config.NewAPIKeyFromString("sk_test_123", nil), BaseURL: baseURL}, nil) + client := NewClient(&stripe.Client{APIKey: config.NewAPIKeyFromString("sk_test_123"), BaseURL: baseURL}, nil) devURLMap := DeviceURLMap{ ForwardURL: "http://localhost:3000/events", diff --git a/pkg/terminal/p400/rabbit_service_test.go b/pkg/terminal/p400/rabbit_service_test.go index 9c927a00e..dd0ae5fae 100644 --- a/pkg/terminal/p400/rabbit_service_test.go +++ b/pkg/terminal/p400/rabbit_service_test.go @@ -9,7 +9,7 @@ import ( func TestCreateRabbitServicePayload(t *testing.T) { tsCtx := TerminalSessionContext{ - APIKey: config.NewAPIKeyFromString("sk_123", nil), + APIKey: config.NewAPIKeyFromString("sk_123"), DeviceInfo: DeviceInfo{ DeviceClass: "POS", DeviceUUID: "pos-1234", diff --git a/pkg/terminal/p400/stripe_requests.go b/pkg/terminal/p400/stripe_requests.go index 9ff2103ad..1312f30a5 100644 --- a/pkg/terminal/p400/stripe_requests.go +++ b/pkg/terminal/p400/stripe_requests.go @@ -70,7 +70,7 @@ func DiscoverReaders(ctx context.Context, tsCtx TerminalSessionContext) ([]Reade client := &stripe.Client{ BaseURL: parsedBaseURL, - APIKey: config.NewAPIKeyFromString(tsCtx.PstToken, nil), + APIKey: config.NewAPIKeyFromString(tsCtx.PstToken), Verbose: false, } From 64507973a2bbd9f6e6559f796048a5ef2eba1710 Mon Sep 17 00:00:00 2001 From: Fred Hatfull Date: Thu, 15 Jun 2023 14:59:58 -0700 Subject: [PATCH 6/8] remove unnecessary else --- pkg/config/api_key.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/config/api_key.go b/pkg/config/api_key.go index a7fbb1043..9a9ebae7b 100644 --- a/pkg/config/api_key.go +++ b/pkg/config/api_key.go @@ -71,8 +71,6 @@ func (k *APIKey) WarnIfExpirationSoon(profile *Profile) { warnMsg := fmt.Sprintf("Your API key will expire in %d days. You can obtain a new key from the Dashboard or `stripe login`.", remainingDays) printWarning(warnMsg) _ = k.setLastExpirationWarning(timeNow(), profile) - } else { - } } From 7aa6c2cee0f181d31d4bd849840efcd43e0c3292 Mon Sep 17 00:00:00 2001 From: Fred Hatfull Date: Thu, 15 Jun 2023 15:04:18 -0700 Subject: [PATCH 7/8] fixes lint issues --- pkg/config/api_key.go | 14 +++++++++----- pkg/login/client_login.go | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/pkg/config/api_key.go b/pkg/config/api_key.go index 9a9ebae7b..a06d404a4 100644 --- a/pkg/config/api_key.go +++ b/pkg/config/api_key.go @@ -11,8 +11,8 @@ import ( "time" ) -const LiveModeKeyLastExpirationWarningField = "live_mode_api_key_last_expiration_warning" -const TestModeKeyLastExpirationWarningField = "test_mode_api_key_last_expiration_warning" +const liveModeKeyLastExpirationWarningField = "live_mode_api_key_last_expiration_warning" +const testModeKeyLastExpirationWarningField = "test_mode_api_key_last_expiration_warning" const upcomingExpirationThreshold = 14 * 24 * time.Hour const imminentExpirationThreshold = 24 * time.Hour @@ -23,12 +23,15 @@ const upcomingExpirationReminderFrequency = 12 * time.Hour var timeNow = time.Now var printWarning = printWarningMessage +// APIKey holds various details about an API key like the key itself, its expiration, +// and whether its for live or testmode usage. type APIKey struct { Key string Livemode bool Expiration time.Time } +// NewAPIKey creates an APIKey from the relevant data func NewAPIKey(key string, expiration time.Time, livemode bool) *APIKey { if key == "" { return nil @@ -41,6 +44,7 @@ func NewAPIKey(key string, expiration time.Time, livemode bool) *APIKey { } } +// NewAPIKeyFromString creates an APIKey when only the key is known func NewAPIKeyFromString(key string) *APIKey { if key == "" { return nil @@ -56,6 +60,7 @@ func NewAPIKeyFromString(key string) *APIKey { } } +// WarnIfExpirationSoon shows the relevant warning if the key is due to expire soon func (k *APIKey) WarnIfExpirationSoon(profile *Profile) { if k.Expiration.IsZero() { return @@ -110,10 +115,9 @@ func (k *APIKey) setLastExpirationWarning(warningTime time.Time, profile *Profil func (k *APIKey) expirationWarningField() string { if k.Livemode { - return LiveModeKeyLastExpirationWarningField - } else { - return TestModeKeyLastExpirationWarningField + return liveModeKeyLastExpirationWarningField } + return testModeKeyLastExpirationWarningField } func printWarningMessage(message string) { diff --git a/pkg/login/client_login.go b/pkg/login/client_login.go index cc5b7dd04..e3b5d3367 100644 --- a/pkg/login/client_login.go +++ b/pkg/login/client_login.go @@ -95,7 +95,7 @@ func (a *Authenticator) Login(ctx context.Context, links *Links) error { keyValidityDurationDays := 90 if !res.KeyExpiration.IsZero() { - keyValidityDurationHours := res.KeyExpiration.Sub(time.Now()).Hours() + keyValidityDurationHours := time.Until(res.KeyExpiration).Hours() keyValidityDurationDays = int(math.Round(keyValidityDurationHours / 24.0)) } From ff85def75fb9871964f5e60a2e30d648c646fd90 Mon Sep 17 00:00:00 2001 From: Fred Hatfull Date: Thu, 15 Jun 2023 15:24:58 -0700 Subject: [PATCH 8/8] Fixes a bunch of messed up imports caused by editor misconfiguration >:( --- pkg/config/api_key.go | 6 ++++-- pkg/config/api_key_test.go | 5 +++-- pkg/fixtures/fixtures.go | 2 +- pkg/fixtures/fixtures_test.go | 3 ++- pkg/fixtures/triggers.go | 2 +- pkg/login/acct/retrieve_account.go | 2 +- pkg/login/acct/retrieve_account_test.go | 3 ++- pkg/login/client_login.go | 2 +- pkg/login/interactive_login_test.go | 2 +- pkg/login/keys/configurer.go | 3 ++- pkg/login/login_message.go | 2 +- pkg/login/login_message_test.go | 2 +- pkg/stripe/client.go | 2 +- pkg/stripe/client_test.go | 3 ++- pkg/stripeauth/client_test.go | 2 +- pkg/terminal/p400/rabbit_service_test.go | 3 ++- pkg/terminal/p400/reader_methods.go | 2 +- pkg/terminal/p400/stripe_requests.go | 2 +- 18 files changed, 28 insertions(+), 20 deletions(-) diff --git a/pkg/config/api_key.go b/pkg/config/api_key.go index a06d404a4..29bf0a18b 100644 --- a/pkg/config/api_key.go +++ b/pkg/config/api_key.go @@ -2,13 +2,15 @@ package config import ( "fmt" - "github.com/spf13/viper" - "github.com/stripe/stripe-cli/pkg/ansi" "math" "os" "strconv" "strings" "time" + + "github.com/spf13/viper" + + "github.com/stripe/stripe-cli/pkg/ansi" ) const liveModeKeyLastExpirationWarningField = "live_mode_api_key_last_expiration_warning" diff --git a/pkg/config/api_key_test.go b/pkg/config/api_key_test.go index ce28b1ebb..321028518 100644 --- a/pkg/config/api_key_test.go +++ b/pkg/config/api_key_test.go @@ -2,12 +2,13 @@ package config import ( "fmt" - "github.com/spf13/viper" - "github.com/stretchr/testify/assert" "os" "path/filepath" "testing" "time" + + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" ) func TestNewAPIKeyFromString(t *testing.T) { diff --git a/pkg/fixtures/fixtures.go b/pkg/fixtures/fixtures.go index 9d555064f..17f380624 100644 --- a/pkg/fixtures/fixtures.go +++ b/pkg/fixtures/fixtures.go @@ -5,7 +5,6 @@ import ( "encoding/json" "errors" "fmt" - "github.com/stripe/stripe-cli/pkg/config" "io" "os" "path/filepath" @@ -16,6 +15,7 @@ import ( "github.com/spf13/afero" "github.com/tidwall/gjson" + "github.com/stripe/stripe-cli/pkg/config" "github.com/stripe/stripe-cli/pkg/git" "github.com/stripe/stripe-cli/pkg/parsers" "github.com/stripe/stripe-cli/pkg/requests" diff --git a/pkg/fixtures/fixtures_test.go b/pkg/fixtures/fixtures_test.go index fcb263593..13d99f81b 100644 --- a/pkg/fixtures/fixtures_test.go +++ b/pkg/fixtures/fixtures_test.go @@ -3,7 +3,6 @@ package fixtures import ( "context" "errors" - "github.com/stripe/stripe-cli/pkg/config" "io" "net/http" "net/http/httptest" @@ -17,6 +16,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/stripe/stripe-cli/pkg/config" ) const testFixture = ` diff --git a/pkg/fixtures/triggers.go b/pkg/fixtures/triggers.go index 277a9a7b1..bbbc9d393 100644 --- a/pkg/fixtures/triggers.go +++ b/pkg/fixtures/triggers.go @@ -4,11 +4,11 @@ import ( "context" "embed" "fmt" - "github.com/stripe/stripe-cli/pkg/config" "sort" "github.com/spf13/afero" + "github.com/stripe/stripe-cli/pkg/config" "github.com/stripe/stripe-cli/pkg/stripe" ) diff --git a/pkg/login/acct/retrieve_account.go b/pkg/login/acct/retrieve_account.go index 94d627f01..14ddb177b 100644 --- a/pkg/login/acct/retrieve_account.go +++ b/pkg/login/acct/retrieve_account.go @@ -3,9 +3,9 @@ package acct import ( "context" "encoding/json" - "github.com/stripe/stripe-cli/pkg/config" "net/url" + "github.com/stripe/stripe-cli/pkg/config" "github.com/stripe/stripe-cli/pkg/stripe" ) diff --git a/pkg/login/acct/retrieve_account_test.go b/pkg/login/acct/retrieve_account_test.go index 745c55d60..dc18ba1fe 100644 --- a/pkg/login/acct/retrieve_account_test.go +++ b/pkg/login/acct/retrieve_account_test.go @@ -3,12 +3,13 @@ package acct import ( "context" "encoding/json" - "github.com/stripe/stripe-cli/pkg/config" "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/require" + + "github.com/stripe/stripe-cli/pkg/config" ) const testName = "test_name" diff --git a/pkg/login/client_login.go b/pkg/login/client_login.go index e3b5d3367..3065e4700 100644 --- a/pkg/login/client_login.go +++ b/pkg/login/client_login.go @@ -3,7 +3,6 @@ package login import ( "context" "fmt" - "github.com/stripe/stripe-cli/pkg/config" "math" "os" "time" @@ -11,6 +10,7 @@ import ( "github.com/briandowns/spinner" "github.com/stripe/stripe-cli/pkg/ansi" + "github.com/stripe/stripe-cli/pkg/config" "github.com/stripe/stripe-cli/pkg/login/keys" "github.com/stripe/stripe-cli/pkg/open" "github.com/stripe/stripe-cli/pkg/stripe" diff --git a/pkg/login/interactive_login_test.go b/pkg/login/interactive_login_test.go index 594a7e7f2..fcb98741b 100644 --- a/pkg/login/interactive_login_test.go +++ b/pkg/login/interactive_login_test.go @@ -3,7 +3,6 @@ package login import ( "context" "encoding/json" - "github.com/stripe/stripe-cli/pkg/config" "net/http" "net/http/httptest" "os" @@ -12,6 +11,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/stripe/stripe-cli/pkg/config" "github.com/stripe/stripe-cli/pkg/login/acct" ) diff --git a/pkg/login/keys/configurer.go b/pkg/login/keys/configurer.go index 3d497602d..b7743ee16 100644 --- a/pkg/login/keys/configurer.go +++ b/pkg/login/keys/configurer.go @@ -1,9 +1,10 @@ package keys import ( - "github.com/spf13/afero" "time" + "github.com/spf13/afero" + "github.com/stripe/stripe-cli/pkg/config" "github.com/stripe/stripe-cli/pkg/validators" ) diff --git a/pkg/login/login_message.go b/pkg/login/login_message.go index 1228a4b59..80b1e0572 100644 --- a/pkg/login/login_message.go +++ b/pkg/login/login_message.go @@ -3,10 +3,10 @@ package login import ( "context" "fmt" - "github.com/stripe/stripe-cli/pkg/config" "os" "github.com/stripe/stripe-cli/pkg/ansi" + "github.com/stripe/stripe-cli/pkg/config" "github.com/stripe/stripe-cli/pkg/login/acct" ) diff --git a/pkg/login/login_message_test.go b/pkg/login/login_message_test.go index 1e7fb552f..5658d119c 100644 --- a/pkg/login/login_message_test.go +++ b/pkg/login/login_message_test.go @@ -3,13 +3,13 @@ package login import ( "context" "encoding/json" - "github.com/stripe/stripe-cli/pkg/config" "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/require" + "github.com/stripe/stripe-cli/pkg/config" "github.com/stripe/stripe-cli/pkg/login/acct" ) diff --git a/pkg/stripe/client.go b/pkg/stripe/client.go index 68554ab03..23ffdcfd4 100644 --- a/pkg/stripe/client.go +++ b/pkg/stripe/client.go @@ -2,7 +2,6 @@ package stripe import ( "context" - "github.com/stripe/stripe-cli/pkg/config" "io" "net" "net/http" @@ -13,6 +12,7 @@ import ( log "github.com/sirupsen/logrus" + "github.com/stripe/stripe-cli/pkg/config" "github.com/stripe/stripe-cli/pkg/useragent" ) diff --git a/pkg/stripe/client_test.go b/pkg/stripe/client_test.go index 9f05fc536..9822a731b 100644 --- a/pkg/stripe/client_test.go +++ b/pkg/stripe/client_test.go @@ -3,7 +3,6 @@ package stripe import ( "context" "errors" - "github.com/stripe/stripe-cli/pkg/config" "io" "net/http" "net/http/httptest" @@ -11,6 +10,8 @@ import ( "testing" "github.com/stretchr/testify/require" + + "github.com/stripe/stripe-cli/pkg/config" ) func TestPerformRequest_ParamsEncoding_Delete(t *testing.T) { diff --git a/pkg/stripeauth/client_test.go b/pkg/stripeauth/client_test.go index 52f20a970..0f359ffce 100644 --- a/pkg/stripeauth/client_test.go +++ b/pkg/stripeauth/client_test.go @@ -3,7 +3,6 @@ package stripeauth import ( "context" "encoding/json" - "github.com/stripe/stripe-cli/pkg/config" "net/http" "net/http/httptest" "net/url" @@ -12,6 +11,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/stripe/stripe-cli/pkg/config" "github.com/stripe/stripe-cli/pkg/stripe" ) diff --git a/pkg/terminal/p400/rabbit_service_test.go b/pkg/terminal/p400/rabbit_service_test.go index dd0ae5fae..a88e1a70c 100644 --- a/pkg/terminal/p400/rabbit_service_test.go +++ b/pkg/terminal/p400/rabbit_service_test.go @@ -1,10 +1,11 @@ package p400 import ( - "github.com/stripe/stripe-cli/pkg/config" "testing" "github.com/stretchr/testify/require" + + "github.com/stripe/stripe-cli/pkg/config" ) func TestCreateRabbitServicePayload(t *testing.T) { diff --git a/pkg/terminal/p400/reader_methods.go b/pkg/terminal/p400/reader_methods.go index 926a65be0..0f5b21a04 100644 --- a/pkg/terminal/p400/reader_methods.go +++ b/pkg/terminal/p400/reader_methods.go @@ -2,12 +2,12 @@ package p400 import ( "fmt" - "github.com/stripe/stripe-cli/pkg/config" "math/rand" "runtime" "strconv" "time" + "github.com/stripe/stripe-cli/pkg/config" "github.com/stripe/stripe-cli/pkg/version" ) diff --git a/pkg/terminal/p400/stripe_requests.go b/pkg/terminal/p400/stripe_requests.go index 1312f30a5..1cad502e2 100644 --- a/pkg/terminal/p400/stripe_requests.go +++ b/pkg/terminal/p400/stripe_requests.go @@ -5,11 +5,11 @@ import ( "context" "encoding/json" "fmt" - "github.com/stripe/stripe-cli/pkg/config" "net/http" "net/url" "strconv" + "github.com/stripe/stripe-cli/pkg/config" "github.com/stripe/stripe-cli/pkg/stripe" )