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 new file mode 100644 index 000000000..29bf0a18b --- /dev/null +++ b/pkg/config/api_key.go @@ -0,0 +1,129 @@ +package config + +import ( + "fmt" + "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" +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 + +// 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 + } + + return &APIKey{ + Key: key, + Livemode: livemode, + Expiration: expiration, + } +} + +// NewAPIKeyFromString creates an APIKey when only the key is known +func NewAPIKeyFromString(key string) *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 + // 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. + } +} + +// WarnIfExpirationSoon shows the relevant warning if the key is due to expire soon +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) + } +} + +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 + } + 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 new file mode 100644 index 000000000..321028518 --- /dev/null +++ b/pkg/config/api_key_test.go @@ -0,0 +1,194 @@ +package config + +import ( + "fmt" + "os" + "path/filepath" + "testing" + "time" + + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" +) + +func TestNewAPIKeyFromString(t *testing.T) { + sampleLivemodeKeyString := "rk_live_1234" + sampleTestmodeKeyString := "rk_test_1234" + + livemodeKey := NewAPIKeyFromString(sampleLivemodeKeyString) + testmodeKey := NewAPIKeyFromString(sampleTestmodeKeyString) + + assert.Equal(t, sampleLivemodeKeyString, livemodeKey.Key) + assert.True(t, livemodeKey.Livemode) + assert.Zero(t, livemodeKey.Expiration) + + assert.Equal(t, sampleTestmodeKeyString, testmodeKey.Key) + assert.False(t, testmodeKey.Livemode) + assert.Zero(t, testmodeKey.Expiration) +} + +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, + } + + 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, + } + + 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, + } + + 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") + + 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 62aa28c38..c733ba255 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 @@ -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" ) @@ -136,27 +133,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), 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), nil } var key string + var keyExpiry time.Time var err error // Try to fetch the API key from the configuration file @@ -171,24 +169,43 @@ 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 + + apiKey := NewAPIKey(key, keyExpiry, livemode) + apiKey.WarnIfExpirationSoon(p) + return apiKey, nil } - return "", validators.ErrAPIKeyNotConfigured + return nil, validators.ErrAPIKeyNotConfigured } // GetExpiresAt returns the API key expirary date @@ -308,24 +325,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 != "" { @@ -343,7 +359,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") } @@ -434,8 +450,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 46ecb5bf8..fca6a95b0 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), 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), 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), 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), 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), } 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), DisplayName: "", } diff --git a/pkg/fixtures/fixtures.go b/pkg/fixtures/fixtures.go index 6ed738b1f..17f380624 100644 --- a/pkg/fixtures/fixtures.go +++ b/pkg/fixtures/fixtures.go @@ -15,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" @@ -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..13d99f81b 100644 --- a/pkg/fixtures/fixtures_test.go +++ b/pkg/fixtures/fixtures_test.go @@ -16,6 +16,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/stripe/stripe-cli/pkg/config" ) const testFixture = ` @@ -80,7 +82,8 @@ const failureTestFixture = ` ] }` -const apiKey = "sk_test_1234" +var apiKey = config.NewAPIKeyFromString("sk_test_1234") + 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..bbbc9d393 100644 --- a/pkg/fixtures/triggers.go +++ b/pkg/fixtures/triggers.go @@ -8,6 +8,7 @@ import ( "github.com/spf13/afero" + "github.com/stripe/stripe-cli/pkg/config" "github.com/stripe/stripe-cli/pkg/stripe" ) @@ -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..14ddb177b 100644 --- a/pkg/login/acct/retrieve_account.go +++ b/pkg/login/acct/retrieve_account.go @@ -5,6 +5,7 @@ import ( "encoding/json" "net/url" + "github.com/stripe/stripe-cli/pkg/config" "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..dc18ba1fe 100644 --- a/pkg/login/acct/retrieve_account_test.go +++ b/pkg/login/acct/retrieve_account_test.go @@ -8,6 +8,8 @@ import ( "testing" "github.com/stretchr/testify/require" + + "github.com/stripe/stripe-cli/pkg/config" ) const testName = "test_name" @@ -27,7 +29,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")) require.NoError(t, err) require.Equal( t, @@ -55,7 +57,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")) require.NoError(t, err) require.Equal( t, diff --git a/pkg/login/client_login.go b/pkg/login/client_login.go index 894f1d937..3065e4700 100644 --- a/pkg/login/client_login.go +++ b/pkg/login/client_login.go @@ -3,11 +3,14 @@ package login import ( "context" "fmt" + "math" "os" + "time" "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" @@ -77,7 +80,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) + 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 @@ -88,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 := time.Until(res.KeyExpiration).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/interactive_login.go b/pkg/login/interactive_login.go index 9f6382a2b..3abbbb8e0 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) + config.Profile.DeviceName = getConfigureDeviceName(os.Stdin) - config.Profile.TestModeAPIKey = apiKey + config.Profile.TestModeAPIKey = config_pkg.NewAPIKeyFromString(apiKeyString) 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..fcb98741b 100644 --- a/pkg/login/interactive_login_test.go +++ b/pkg/login/interactive_login_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/stripe/stripe-cli/pkg/config" "github.com/stripe/stripe-cli/pkg/login/acct" ) @@ -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") + 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") + 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") + 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") + 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..b7743ee16 100644 --- a/pkg/login/keys/configurer.go +++ b/pkg/login/keys/configurer.go @@ -1,6 +1,8 @@ package keys import ( + "time" + "github.com/spf13/afero" "github.com/stripe/stripe-cli/pkg/config" @@ -33,10 +35,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.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.TestModePublishableKey = response.TestModePublishableKey + c.cfg.Profile.DisplayName = response.AccountDisplayName c.cfg.Profile.AccountID = response.AccountID 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"` } diff --git a/pkg/login/login_message.go b/pkg/login/login_message.go index acc99c509..80b1e0572 100644 --- a/pkg/login/login_message.go +++ b/pkg/login/login_message.go @@ -6,11 +6,12 @@ import ( "os" "github.com/stripe/stripe-cli/pkg/ansi" + "github.com/stripe/stripe-cli/pkg/config" "github.com/stripe/stripe-cli/pkg/login/acct" ) // 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..5658d119c 100644 --- a/pkg/login/login_message_test.go +++ b/pkg/login/login_message_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/stripe/stripe-cli/pkg/config" "github.com/stripe/stripe-cli/pkg/login/acct" ) @@ -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") + 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") + 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") + 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") + 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") + 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..6e6a4877f 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") + _, 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") + _, 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") + _, 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") + _, 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..23ffdcfd4 100644 --- a/pkg/stripe/client.go +++ b/pkg/stripe/client.go @@ -12,6 +12,7 @@ import ( log "github.com/sirupsen/logrus" + "github.com/stripe/stripe-cli/pkg/config" "github.com/stripe/stripe-cli/pkg/useragent" ) @@ -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..9822a731b 100644 --- a/pkg/stripe/client_test.go +++ b/pkg/stripe/client_test.go @@ -10,6 +10,8 @@ import ( "testing" "github.com/stretchr/testify/require" + + "github.com/stripe/stripe-cli/pkg/config" ) func TestPerformRequest_ParamsEncoding_Delete(t *testing.T) { @@ -99,7 +101,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"), } 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..0f359ffce 100644 --- a/pkg/stripeauth/client_test.go +++ b/pkg/stripeauth/client_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/stripe/stripe-cli/pkg/config" "github.com/stripe/stripe-cli/pkg/stripe" ) @@ -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"), 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"), 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"), 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"), 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..a88e1a70c 100644 --- a/pkg/terminal/p400/rabbit_service_test.go +++ b/pkg/terminal/p400/rabbit_service_test.go @@ -4,11 +4,13 @@ import ( "testing" "github.com/stretchr/testify/require" + + "github.com/stripe/stripe-cli/pkg/config" ) func TestCreateRabbitServicePayload(t *testing.T) { tsCtx := TerminalSessionContext{ - APIKey: "sk_123", + APIKey: config.NewAPIKeyFromString("sk_123"), 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..0f5b21a04 100644 --- a/pkg/terminal/p400/reader_methods.go +++ b/pkg/terminal/p400/reader_methods.go @@ -7,13 +7,14 @@ import ( "strconv" "time" + "github.com/stripe/stripe-cli/pkg/config" "github.com/stripe/stripe-cli/pkg/version" ) // 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..1cad502e2 100644 --- a/pkg/terminal/p400/stripe_requests.go +++ b/pkg/terminal/p400/stripe_requests.go @@ -9,6 +9,7 @@ import ( "net/url" "strconv" + "github.com/stripe/stripe-cli/pkg/config" "github.com/stripe/stripe-cli/pkg/stripe" ) @@ -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), Verbose: false, }