satellite/payments: prevent removing other users' cards

This change patches a loophole that allowed accounts to remove cards
that belong to other users.

Closes #storj/storj-private#326

Change-Id: I33e9efe5c9cdb03aa48ad4c6b1d3283c396a7890
This commit is contained in:
Wilfred Asomani 2023-07-18 13:13:04 +00:00
parent 31bb6d54c7
commit 7cc873a62a
3 changed files with 57 additions and 8 deletions

View File

@ -13,6 +13,13 @@ import (
"storj.io/storj/satellite/payments" "storj.io/storj/satellite/payments"
) )
var (
// ErrCardNotFound is returned when card is not found for a user.
ErrCardNotFound = errs.Class("card not found")
// ErrDefaultCard is returned when a user tries to delete their default card.
ErrDefaultCard = errs.Class("default card")
)
// creditCards is an implementation of payments.CreditCards. // creditCards is an implementation of payments.CreditCards.
// //
// architecture: Service // architecture: Service
@ -156,7 +163,29 @@ func (creditCards *creditCards) Remove(ctx context.Context, userID uuid.UUID, ca
if customer.InvoiceSettings != nil && if customer.InvoiceSettings != nil &&
customer.InvoiceSettings.DefaultPaymentMethod != nil && customer.InvoiceSettings.DefaultPaymentMethod != nil &&
customer.InvoiceSettings.DefaultPaymentMethod.ID == cardID { customer.InvoiceSettings.DefaultPaymentMethod.ID == cardID {
return Error.Wrap(errs.New("can not detach default payment method.")) return ErrDefaultCard.New("can not detach default payment method.")
}
cardIter := creditCards.service.stripeClient.PaymentMethods().List(&stripe.PaymentMethodListParams{
ListParams: stripe.ListParams{Context: ctx},
Customer: &customerID,
Type: stripe.String(string(stripe.PaymentMethodTypeCard)),
})
isUserCard := false
for cardIter.Next() {
if cardIter.PaymentMethod().ID == cardID {
isUserCard = true
break
}
}
if err = cardIter.Err(); err != nil {
return Error.Wrap(err)
}
if !isUserCard {
return ErrCardNotFound.New("this card is not attached to this account.")
} }
cardParams := &stripe.PaymentMethodDetachParams{Params: stripe.Params{Context: ctx}} cardParams := &stripe.PaymentMethodDetachParams{Params: stripe.Params{Context: ctx}}

View File

@ -87,31 +87,42 @@ func TestCreditCards_Add(t *testing.T) {
func TestCreditCards_Remove(t *testing.T) { func TestCreditCards_Remove(t *testing.T) {
testplanet.Run(t, testplanet.Config{ testplanet.Run(t, testplanet.Config{
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 1, SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 2,
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) { }, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
satellite := planet.Satellites[0] satellite := planet.Satellites[0]
userID := planet.Uplinks[0].Projects[0].Owner.ID userID := planet.Uplinks[0].Projects[0].Owner.ID
user2ID := planet.Uplinks[1].Projects[0].Owner.ID
_, err := satellite.API.Payments.Accounts.CreditCards().Add(ctx, userID, "test") card1, err := satellite.API.Payments.Accounts.CreditCards().Add(ctx, userID, "test")
require.NoError(t, err) require.NoError(t, err)
_, err = satellite.API.Payments.Accounts.CreditCards().Add(ctx, userID, "test2") // card2 becomes the default card
card2, err := satellite.API.Payments.Accounts.CreditCards().Add(ctx, userID, "test2")
require.NoError(t, err) require.NoError(t, err)
cards, err := satellite.API.Payments.Accounts.CreditCards().List(ctx, userID) cards, err := satellite.API.Payments.Accounts.CreditCards().List(ctx, userID)
require.NoError(t, err) require.NoError(t, err)
require.Len(t, cards, 2) require.Len(t, cards, 2)
// Save card that should remain after deletion. // user2ID should not be able to delete userID's cards
savedCard := cards[0] for _, card := range cards {
err = satellite.API.Payments.Accounts.CreditCards().Remove(ctx, user2ID, card.ID)
require.Error(t, err)
require.True(t, stripe.ErrCardNotFound.Has(err))
}
err = satellite.API.Payments.Accounts.CreditCards().Remove(ctx, userID, cards[1].ID) // Can not remove default card
err = satellite.API.Payments.Accounts.CreditCards().Remove(ctx, userID, card2.ID)
require.Error(t, err)
require.True(t, stripe.ErrDefaultCard.Has(err))
err = satellite.API.Payments.Accounts.CreditCards().Remove(ctx, userID, card1.ID)
require.NoError(t, err) require.NoError(t, err)
cards, err = satellite.API.Payments.Accounts.CreditCards().List(ctx, userID) cards, err = satellite.API.Payments.Accounts.CreditCards().List(ctx, userID)
require.NoError(t, err) require.NoError(t, err)
require.Len(t, cards, 1) require.Len(t, cards, 1)
require.Equal(t, savedCard, cards[0]) require.Equal(t, card2, cards[0])
}) })
} }

View File

@ -356,6 +356,15 @@ func (m *mockCustomers) Update(id string, params *stripe.CustomerParams) (*strip
if params.Balance != nil { if params.Balance != nil {
customer.Balance = *params.Balance customer.Balance = *params.Balance
} }
if params.InvoiceSettings != nil {
if params.InvoiceSettings.DefaultPaymentMethod != nil {
customer.InvoiceSettings = &stripe.CustomerInvoiceSettings{
DefaultPaymentMethod: &stripe.PaymentMethod{
ID: *params.InvoiceSettings.DefaultPaymentMethod,
},
}
}
}
// TODO update customer with more params as necessary // TODO update customer with more params as necessary
return customer, nil return customer, nil