diff --git a/satellite/console/consoleweb/consoleapi/buckets.go b/satellite/console/consoleweb/consoleapi/buckets.go index 0087330f0..45ef74b29 100644 --- a/satellite/console/consoleweb/consoleapi/buckets.go +++ b/satellite/console/consoleweb/consoleapi/buckets.go @@ -149,7 +149,13 @@ func (b *Buckets) GetBucketTotals(w http.ResponseWriter, r *http.Request) { Page: page, }, before) if err != nil { + if console.ErrUnauthorized.Has(err) { + b.serveJSONError(ctx, w, http.StatusUnauthorized, err) + return + } + b.serveJSONError(ctx, w, http.StatusInternalServerError, err) + return } err = json.NewEncoder(w).Encode(totals) diff --git a/satellite/console/consoleweb/consoleapi/payments.go b/satellite/console/consoleweb/consoleapi/payments.go index 5ca114950..c21590291 100644 --- a/satellite/console/consoleweb/consoleapi/payments.go +++ b/satellite/console/consoleweb/consoleapi/payments.go @@ -270,6 +270,11 @@ func (p *Payments) MakeCreditCardDefault(w http.ResponseWriter, r *http.Request) err = p.service.Payments().MakeCreditCardDefault(ctx, string(cardID)) if err != nil { + if stripe.ErrCardNotFound.Has(err) { + p.serveJSONError(ctx, w, http.StatusNotFound, err) + return + } + if console.ErrUnauthorized.Has(err) { p.serveJSONError(ctx, w, http.StatusUnauthorized, err) return @@ -302,6 +307,10 @@ func (p *Payments) RemoveCreditCard(w http.ResponseWriter, r *http.Request) { err = p.service.Payments().RemoveCreditCard(ctx, cardID) if err != nil { + if stripe.ErrCardNotFound.Has(err) { + p.serveJSONError(ctx, w, http.StatusNotFound, err) + return + } if console.ErrUnauthorized.Has(err) { p.serveJSONError(ctx, w, http.StatusUnauthorized, err) return diff --git a/satellite/console/consoleweb/endpoints_test.go b/satellite/console/consoleweb/endpoints_test.go index d8938f2e7..0845b2001 100644 --- a/satellite/console/consoleweb/endpoints_test.go +++ b/satellite/console/consoleweb/endpoints_test.go @@ -16,11 +16,13 @@ import ( "time" "github.com/stretchr/testify/require" + "go.uber.org/zap" "storj.io/common/testcontext" "storj.io/common/uuid" "storj.io/storj/private/apigen" "storj.io/storj/private/testplanet" + "storj.io/storj/satellite" "storj.io/storj/satellite/accounting" "storj.io/storj/satellite/console" "storj.io/storj/satellite/payments/storjscan/blockchaintest" @@ -622,11 +624,15 @@ func TestProjects(t *testing.T) { func TestWrongUser(t *testing.T) { testplanet.Run(t, testplanet.Config{ SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 1, + Reconfigure: testplanet.Reconfigure{ + Satellite: func(log *zap.Logger, index int, config *satellite.Config) { + config.Console.RateLimit.Burst = 4 + }, + }, }, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) { test := newTest(t, ctx, planet) authorizedUser := test.defaultUser() unauthorizedUser := test.registerUser("user@mail.test", "#$Rnkl12i3nkljfds") - test.login(unauthorizedUser.email, unauthorizedUser.password) type endpointTest struct { endpoint string @@ -635,10 +641,25 @@ func TestWrongUser(t *testing.T) { } baseProjectsUrl := "/projects" + baseApiKeyUrl := "/api-keys" baseProjectIdUrl := fmt.Sprintf("%s/%s", baseProjectsUrl, test.defaultProjectID()) getProjectResourceUrl := func(resource string) string { return fmt.Sprintf("%s/%s", baseProjectIdUrl, resource) } + getIdAppendedApiKeyUrl := func(resource string) string { + return fmt.Sprintf("%s/%s%s", baseApiKeyUrl, resource, test.defaultProjectID()) + } + + // login and create an api key and credit card to test deletion. + test.login(authorizedUser.email, authorizedUser.password) + resp, body := test.request(http.MethodPost, getIdAppendedApiKeyUrl("create/"), test.toJSON("some name")) + require.Equal(t, http.StatusOK, resp.StatusCode) + var response console.CreateAPIKeyResponse + require.NoError(t, json.Unmarshal([]byte(body), &response)) + + apiKeyId := response.KeyInfo.ID.String() + + test.login(unauthorizedUser.email, unauthorizedUser.password) testCases := []endpointTest{ { @@ -680,20 +701,47 @@ func TestWrongUser(t *testing.T) { endpoint: getProjectResourceUrl("usage-limits"), method: http.MethodGet, }, + { + endpoint: "/buckets/bucket-names?projectID=" + test.defaultProjectID(), + method: http.MethodGet, + }, + { + endpoint: "/buckets/usage-totals?limit=10&page=1&before=" + time.Now().Format(apigen.DateFormat) + "&projectID=" + test.defaultProjectID(), + method: http.MethodGet, + }, { endpoint: getProjectResourceUrl("daily-usage") + "?from=100000000&to=200000000000", method: http.MethodGet, }, { - endpoint: "/api-keys/create/" + test.defaultProjectID(), + endpoint: getIdAppendedApiKeyUrl("create/"), method: http.MethodPost, body: "name", }, + { + endpoint: getIdAppendedApiKeyUrl("delete-by-name?name=name&projectID="), + method: http.MethodDelete, + }, + { + endpoint: getIdAppendedApiKeyUrl("list-paged?limit=10&page=1&order=1&orderDirection=1&projectID="), + method: http.MethodGet, + }, + { + endpoint: getIdAppendedApiKeyUrl("api-key-names?projectID="), + method: http.MethodGet, + }, + { + endpoint: baseApiKeyUrl + "/delete-by-ids", + method: http.MethodDelete, + body: map[string]interface{}{ + "ids": []string{apiKeyId}, + }, + }, } for _, testCase := range testCases { t.Run(fmt.Sprintf("Unauthorized on %s", testCase.endpoint), func(t *testing.T) { - resp, body := test.request(testCase.method, testCase.endpoint, test.toJSON(testCase.body)) + resp, body = test.request(testCase.method, testCase.endpoint, test.toJSON(testCase.body)) require.Contains(t, body, "not authorized") require.Equal(t, http.StatusUnauthorized, resp.StatusCode) }) @@ -703,7 +751,7 @@ func TestWrongUser(t *testing.T) { test.login(authorizedUser.email, authorizedUser.password) for _, testCase := range testCases { t.Run(fmt.Sprintf("Authorized on %s", testCase.endpoint), func(t *testing.T) { - resp, body := test.request(testCase.method, testCase.endpoint, test.toJSON(testCase.body)) + resp, body = test.request(testCase.method, testCase.endpoint, test.toJSON(testCase.body)) require.NotContains(t, body, "not authorized") require.NotEqual(t, http.StatusUnauthorized, resp.StatusCode) }) @@ -762,6 +810,10 @@ func (test *test) url(suffix string) string { } func (test *test) toJSON(v interface{}) io.Reader { + if str, ok := v.(string); ok { + return strings.NewReader(str) + } + data, err := json.Marshal(v) require.NoError(test.t, err) return strings.NewReader(string(data)) @@ -784,6 +836,7 @@ func (test *test) login(email, password string) Response { "email": email, "password": password, })) + require.Equal(test.t, http.StatusOK, resp.StatusCode) cookie := findCookie(resp, "_tokenKey") require.NotNil(test.t, cookie) diff --git a/satellite/console/service.go b/satellite/console/service.go index 9af84111c..2191be6d3 100644 --- a/satellite/console/service.go +++ b/satellite/console/service.go @@ -2460,7 +2460,7 @@ func (s *Service) GetAllAPIKeyNamesByProjectID(ctx context.Context, projectID uu isMember, err := s.isProjectMember(ctx, user.ID, projectID) if err != nil { - return nil, Error.Wrap(err) + return nil, ErrUnauthorized.Wrap(err) } names, err = s.store.APIKeys().GetAllNamesByProjectID(ctx, isMember.project.ID) @@ -2483,7 +2483,7 @@ func (s *Service) DeleteAPIKeyByNameAndProjectID(ctx context.Context, name strin isMember, err := s.isProjectMember(ctx, user.ID, projectID) if err != nil { - return Error.Wrap(err) + return ErrUnauthorized.Wrap(err) } key, err := s.store.APIKeys().GetByNameAndProjectID(ctx, name, isMember.project.ID) @@ -2599,7 +2599,7 @@ func (s *Service) GetBucketTotals(ctx context.Context, projectID uuid.UUID, curs isMember, err := s.isProjectMember(ctx, user.ID, projectID) if err != nil { - return nil, Error.Wrap(err) + return nil, ErrUnauthorized.Wrap(err) } usage, err := s.projectAccounting.GetBucketTotals(ctx, isMember.project.ID, cursor, before) @@ -2622,7 +2622,7 @@ func (s *Service) GetAllBucketNames(ctx context.Context, projectID uuid.UUID) (_ isMember, err := s.isProjectMember(ctx, user.ID, projectID) if err != nil { - return nil, Error.Wrap(err) + return nil, ErrUnauthorized.Wrap(err) } listOptions := buckets.ListOptions{ diff --git a/satellite/payments/stripe/creditcards.go b/satellite/payments/stripe/creditcards.go index fe36a50d5..d2b614e9d 100644 --- a/satellite/payments/stripe/creditcards.go +++ b/satellite/payments/stripe/creditcards.go @@ -5,6 +5,7 @@ package stripe import ( "context" + "strings" "github.com/stripe/stripe-go/v72" "github.com/zeebo/errs" @@ -20,6 +21,10 @@ var ( ErrDefaultCard = errs.Class("default card") // ErrDuplicateCard is returned when a user tries to add duplicate card. ErrDuplicateCard = errs.Class("duplicate card") + + // UnattachedErrString is part of the err string returned by stripe if a payment + // method does not belong to a customer. + UnattachedErrString = "The payment method must be attached to the customer" ) // creditCards is an implementation of payments.CreditCards. @@ -165,6 +170,9 @@ func (creditCards *creditCards) MakeDefault(ctx context.Context, userID uuid.UUI } _, err = creditCards.service.stripeClient.Customers().Update(customerID, params) + if err != nil && strings.Contains(err.Error(), UnattachedErrString) { + return ErrCardNotFound.New("this card is not attached to this account.") + } return Error.Wrap(err) }