2020-10-06 15:25:53 +01:00
|
|
|
// Copyright (C) 2020 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package console_test
|
|
|
|
|
|
|
|
import (
|
2023-05-15 16:14:30 +01:00
|
|
|
"bytes"
|
2021-07-13 18:21:16 +01:00
|
|
|
"context"
|
2022-06-05 23:41:38 +01:00
|
|
|
"database/sql"
|
2022-05-11 09:02:58 +01:00
|
|
|
"encoding/json"
|
2022-06-16 14:26:27 +01:00
|
|
|
"fmt"
|
2023-03-16 11:42:01 +00:00
|
|
|
"math/rand"
|
2022-06-16 14:26:27 +01:00
|
|
|
"sort"
|
2023-06-01 08:27:09 +01:00
|
|
|
"strings"
|
2020-10-06 15:25:53 +01:00
|
|
|
"testing"
|
2021-07-13 18:21:16 +01:00
|
|
|
"time"
|
2020-10-06 15:25:53 +01:00
|
|
|
|
2022-06-16 14:26:27 +01:00
|
|
|
"github.com/shopspring/decimal"
|
2020-10-06 15:25:53 +01:00
|
|
|
"github.com/stretchr/testify/require"
|
2021-07-01 00:13:45 +01:00
|
|
|
"go.uber.org/zap"
|
2023-03-01 19:52:52 +00:00
|
|
|
"golang.org/x/crypto/bcrypt"
|
2020-10-06 15:25:53 +01:00
|
|
|
|
2022-09-06 13:43:09 +01:00
|
|
|
"storj.io/common/currency"
|
2021-03-16 19:43:02 +00:00
|
|
|
"storj.io/common/macaroon"
|
2021-07-01 00:13:45 +01:00
|
|
|
"storj.io/common/memory"
|
2020-10-06 15:25:53 +01:00
|
|
|
"storj.io/common/testcontext"
|
2020-11-13 11:41:35 +00:00
|
|
|
"storj.io/common/testrand"
|
2020-10-09 14:40:12 +01:00
|
|
|
"storj.io/common/uuid"
|
2022-05-11 09:02:58 +01:00
|
|
|
"storj.io/storj/private/blockchain"
|
2020-10-06 15:25:53 +01:00
|
|
|
"storj.io/storj/private/testplanet"
|
2021-07-01 00:13:45 +01:00
|
|
|
"storj.io/storj/satellite"
|
2023-04-13 13:04:07 +01:00
|
|
|
"storj.io/storj/satellite/buckets"
|
2020-10-06 15:25:53 +01:00
|
|
|
"storj.io/storj/satellite/console"
|
2022-06-16 14:26:27 +01:00
|
|
|
"storj.io/storj/satellite/payments"
|
2023-05-15 16:14:30 +01:00
|
|
|
"storj.io/storj/satellite/payments/billing"
|
2022-06-16 14:26:27 +01:00
|
|
|
"storj.io/storj/satellite/payments/coinpayments"
|
|
|
|
"storj.io/storj/satellite/payments/storjscan"
|
|
|
|
"storj.io/storj/satellite/payments/storjscan/blockchaintest"
|
2023-04-06 12:41:14 +01:00
|
|
|
"storj.io/storj/satellite/payments/stripe"
|
2020-10-06 15:25:53 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestService(t *testing.T) {
|
|
|
|
testplanet.Run(t, testplanet.Config{
|
2022-02-23 16:02:45 +00:00
|
|
|
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 2,
|
2023-01-25 21:38:29 +00:00
|
|
|
Reconfigure: testplanet.Reconfigure{
|
|
|
|
Satellite: func(log *zap.Logger, index int, config *satellite.Config) {
|
2023-04-06 12:41:14 +01:00
|
|
|
config.Payments.StripeCoinPayments.StripeFreeTierCouponID = stripe.MockCouponID1
|
2023-01-25 21:38:29 +00:00
|
|
|
},
|
|
|
|
},
|
2022-02-23 16:02:45 +00:00
|
|
|
},
|
2020-10-06 15:25:53 +01:00
|
|
|
func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
|
|
|
sat := planet.Satellites[0]
|
|
|
|
service := sat.API.Console.Service
|
|
|
|
|
|
|
|
up1Pro1, err := sat.API.DB.Console().Projects().Get(ctx, planet.Uplinks[0].Projects[0].ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
up2Pro1, err := sat.API.DB.Console().Projects().Get(ctx, planet.Uplinks[1].Projects[0].ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
up2User, err := sat.API.DB.Console().Users().Get(ctx, up2Pro1.OwnerID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
require.NotEqual(t, up1Pro1.ID, up2Pro1.ID)
|
|
|
|
require.NotEqual(t, up1Pro1.OwnerID, up2Pro1.OwnerID)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
userCtx1, err := sat.UserContext(ctx, up1Pro1.OwnerID)
|
2020-10-06 15:25:53 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
userCtx2, err := sat.UserContext(ctx, up2Pro1.OwnerID)
|
2020-10-06 15:25:53 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-07-01 10:32:17 +01:00
|
|
|
t.Run("GetProject", func(t *testing.T) {
|
2020-10-06 15:25:53 +01:00
|
|
|
// Getting own project details should work
|
2022-06-05 23:41:38 +01:00
|
|
|
project, err := service.GetProject(userCtx1, up1Pro1.ID)
|
2020-10-06 15:25:53 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, up1Pro1.ID, project.ID)
|
|
|
|
|
|
|
|
// Getting someone else project details should not work
|
2022-06-05 23:41:38 +01:00
|
|
|
project, err = service.GetProject(userCtx1, up2Pro1.ID)
|
2020-10-06 15:25:53 +01:00
|
|
|
require.Error(t, err)
|
|
|
|
require.Nil(t, project)
|
|
|
|
})
|
|
|
|
|
2022-09-13 13:59:15 +01:00
|
|
|
t.Run("GetSalt", func(t *testing.T) {
|
|
|
|
// Getting project salt as a member should work
|
|
|
|
salt, err := service.GetSalt(userCtx1, up1Pro1.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, salt)
|
|
|
|
|
2023-01-05 11:14:55 +00:00
|
|
|
// Getting project salt with publicID should work
|
|
|
|
salt1, err := service.GetSalt(userCtx1, up1Pro1.PublicID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, salt1)
|
|
|
|
|
|
|
|
// project.PublicID salt should be the same as project.ID salt
|
|
|
|
require.Equal(t, salt, salt1)
|
|
|
|
|
2022-09-13 13:59:15 +01:00
|
|
|
// Getting project salt as a non-member should not work
|
|
|
|
salt, err = service.GetSalt(userCtx1, up2Pro1.ID)
|
|
|
|
require.Error(t, err)
|
|
|
|
require.Nil(t, salt)
|
|
|
|
})
|
|
|
|
|
2023-01-26 14:24:41 +00:00
|
|
|
t.Run("AddCreditCard fails when payments.CreditCards.Add returns error", func(t *testing.T) {
|
|
|
|
// user should be in free tier
|
|
|
|
user, err := service.GetUser(ctx, up1Pro1.OwnerID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.False(t, user.PaidTier)
|
|
|
|
// get context
|
|
|
|
userCtx1, err := sat.UserContext(ctx, user.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// stripecoinpayments.TestPaymentMethodsAttachFailure triggers the underlying mock stripe client to return an error
|
|
|
|
// when attaching a payment method to a customer.
|
2023-04-06 12:41:14 +01:00
|
|
|
_, err = service.Payments().AddCreditCard(userCtx1, stripe.TestPaymentMethodsAttachFailure)
|
2023-02-06 20:21:33 +00:00
|
|
|
require.Error(t, err)
|
2023-01-26 14:24:41 +00:00
|
|
|
|
|
|
|
// user still in free tier
|
|
|
|
user, err = service.GetUser(ctx, up1Pro1.OwnerID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.False(t, user.PaidTier)
|
|
|
|
|
|
|
|
cards, err := service.Payments().ListCreditCards(userCtx1)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, cards, 0)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("AddCreditCard", func(t *testing.T) {
|
|
|
|
// user should be in free tier
|
|
|
|
user, err := service.GetUser(ctx, up1Pro1.OwnerID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.False(t, user.PaidTier)
|
|
|
|
// get context
|
|
|
|
userCtx1, err := sat.UserContext(ctx, user.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
// add a credit card to put the user in the paid tier
|
2023-02-06 20:21:33 +00:00
|
|
|
card, err := service.Payments().AddCreditCard(userCtx1, "test-cc-token")
|
2023-01-26 14:24:41 +00:00
|
|
|
require.NoError(t, err)
|
2023-02-06 20:21:33 +00:00
|
|
|
require.NotEmpty(t, card)
|
2023-01-26 14:24:41 +00:00
|
|
|
// user should be in paid tier
|
|
|
|
user, err = service.GetUser(ctx, up1Pro1.OwnerID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.True(t, user.PaidTier)
|
|
|
|
|
|
|
|
cards, err := service.Payments().ListCreditCards(userCtx1)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, cards, 1)
|
|
|
|
})
|
|
|
|
|
2023-01-05 14:51:45 +00:00
|
|
|
t.Run("CreateProject", func(t *testing.T) {
|
|
|
|
// Creating a project with a previously used name should fail
|
|
|
|
createdProject, err := service.CreateProject(userCtx1, console.ProjectInfo{
|
|
|
|
Name: up1Pro1.Name,
|
|
|
|
})
|
|
|
|
require.Error(t, err)
|
|
|
|
require.Nil(t, createdProject)
|
|
|
|
})
|
|
|
|
|
2022-07-01 10:32:17 +01:00
|
|
|
t.Run("UpdateProject", func(t *testing.T) {
|
2021-08-02 23:06:15 +01:00
|
|
|
updatedName := "newName"
|
|
|
|
updatedDescription := "newDescription"
|
|
|
|
updatedStorageLimit := memory.Size(100)
|
|
|
|
updatedBandwidthLimit := memory.Size(100)
|
|
|
|
|
|
|
|
user, err := service.GetUser(ctx, up1Pro1.OwnerID)
|
|
|
|
require.NoError(t, err)
|
2023-01-26 14:24:41 +00:00
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
userCtx1, err := sat.UserContext(ctx, user.ID)
|
2021-08-02 23:06:15 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2020-10-06 15:25:53 +01:00
|
|
|
// Updating own project should work
|
2022-06-05 23:41:38 +01:00
|
|
|
updatedProject, err := service.UpdateProject(userCtx1, up1Pro1.ID, console.ProjectInfo{
|
2021-08-02 23:06:15 +01:00
|
|
|
Name: updatedName,
|
|
|
|
Description: updatedDescription,
|
|
|
|
StorageLimit: updatedStorageLimit,
|
|
|
|
BandwidthLimit: updatedBandwidthLimit,
|
|
|
|
})
|
2020-10-06 15:25:53 +01:00
|
|
|
require.NoError(t, err)
|
2021-08-02 23:06:15 +01:00
|
|
|
require.NotEqual(t, up1Pro1.Name, updatedProject.Name)
|
|
|
|
require.Equal(t, updatedName, updatedProject.Name)
|
|
|
|
require.NotEqual(t, up1Pro1.Description, updatedProject.Description)
|
|
|
|
require.Equal(t, updatedDescription, updatedProject.Description)
|
|
|
|
require.NotEqual(t, *up1Pro1.StorageLimit, *updatedProject.StorageLimit)
|
|
|
|
require.Equal(t, updatedStorageLimit, *updatedProject.StorageLimit)
|
|
|
|
require.NotEqual(t, *up1Pro1.BandwidthLimit, *updatedProject.BandwidthLimit)
|
|
|
|
require.Equal(t, updatedBandwidthLimit, *updatedProject.BandwidthLimit)
|
2022-11-06 00:43:11 +00:00
|
|
|
require.Equal(t, updatedStorageLimit, *updatedProject.UserSpecifiedStorageLimit)
|
|
|
|
require.Equal(t, updatedBandwidthLimit, *updatedProject.UserSpecifiedBandwidthLimit)
|
2020-10-06 15:25:53 +01:00
|
|
|
|
|
|
|
// Updating someone else project details should not work
|
2022-06-05 23:41:38 +01:00
|
|
|
updatedProject, err = service.UpdateProject(userCtx1, up2Pro1.ID, console.ProjectInfo{
|
2021-08-02 23:06:15 +01:00
|
|
|
Name: "newName",
|
|
|
|
Description: "TestUpdate",
|
|
|
|
StorageLimit: memory.Size(100),
|
|
|
|
BandwidthLimit: memory.Size(100),
|
|
|
|
})
|
2020-10-06 15:25:53 +01:00
|
|
|
require.Error(t, err)
|
2021-08-02 23:06:15 +01:00
|
|
|
require.Nil(t, updatedProject)
|
2021-08-24 22:12:07 +01:00
|
|
|
|
|
|
|
// attempting to update a project with bandwidth or storage limits set to 0 should fail
|
|
|
|
size0 := new(memory.Size)
|
|
|
|
*size0 = 0
|
|
|
|
size100 := new(memory.Size)
|
|
|
|
*size100 = memory.Size(100)
|
|
|
|
|
|
|
|
up1Pro1.StorageLimit = size0
|
|
|
|
err = sat.DB.Console().Projects().Update(ctx, up1Pro1)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
updateInfo := console.ProjectInfo{
|
|
|
|
Name: "a b c",
|
|
|
|
Description: "1 2 3",
|
|
|
|
StorageLimit: memory.Size(123),
|
|
|
|
BandwidthLimit: memory.Size(123),
|
|
|
|
}
|
2022-06-05 23:41:38 +01:00
|
|
|
updatedProject, err = service.UpdateProject(userCtx1, up1Pro1.ID, updateInfo)
|
2021-08-24 22:12:07 +01:00
|
|
|
require.Error(t, err)
|
|
|
|
require.Nil(t, updatedProject)
|
|
|
|
|
|
|
|
up1Pro1.StorageLimit = size100
|
|
|
|
up1Pro1.BandwidthLimit = size0
|
2021-12-06 19:06:50 +00:00
|
|
|
|
2021-08-24 22:12:07 +01:00
|
|
|
err = sat.DB.Console().Projects().Update(ctx, up1Pro1)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
updatedProject, err = service.UpdateProject(userCtx1, up1Pro1.ID, updateInfo)
|
2021-08-24 22:12:07 +01:00
|
|
|
require.Error(t, err)
|
|
|
|
require.Nil(t, updatedProject)
|
|
|
|
|
|
|
|
up1Pro1.StorageLimit = size100
|
|
|
|
up1Pro1.BandwidthLimit = size100
|
|
|
|
err = sat.DB.Console().Projects().Update(ctx, up1Pro1)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
updatedProject, err = service.UpdateProject(userCtx1, up1Pro1.ID, updateInfo)
|
2021-08-24 22:12:07 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, updateInfo.Name, updatedProject.Name)
|
|
|
|
require.Equal(t, updateInfo.Description, updatedProject.Description)
|
|
|
|
require.NotNil(t, updatedProject.StorageLimit)
|
|
|
|
require.NotNil(t, updatedProject.BandwidthLimit)
|
|
|
|
require.Equal(t, updateInfo.StorageLimit, *updatedProject.StorageLimit)
|
|
|
|
require.Equal(t, updateInfo.BandwidthLimit, *updatedProject.BandwidthLimit)
|
2021-12-14 09:37:45 +00:00
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
project, err := service.GetProject(userCtx1, up1Pro1.ID)
|
2021-12-14 09:37:45 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, updateInfo.StorageLimit, *project.StorageLimit)
|
|
|
|
require.Equal(t, updateInfo.BandwidthLimit, *project.BandwidthLimit)
|
2023-01-05 14:51:45 +00:00
|
|
|
|
|
|
|
// attempting to update a project with a previously used name should fail
|
|
|
|
updatedProject, err = service.UpdateProject(userCtx1, up2Pro1.ID, console.ProjectInfo{
|
|
|
|
Name: up1Pro1.Name,
|
|
|
|
})
|
|
|
|
require.Error(t, err)
|
|
|
|
require.Nil(t, updatedProject)
|
2020-10-06 15:25:53 +01:00
|
|
|
})
|
|
|
|
|
2022-07-01 10:32:17 +01:00
|
|
|
t.Run("AddProjectMembers", func(t *testing.T) {
|
2020-10-06 15:25:53 +01:00
|
|
|
// Adding members to own project should work
|
2022-06-05 23:41:38 +01:00
|
|
|
addedUsers, err := service.AddProjectMembers(userCtx1, up1Pro1.ID, []string{up2User.Email})
|
2020-10-06 15:25:53 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, addedUsers, 1)
|
|
|
|
require.Contains(t, addedUsers, up2User)
|
|
|
|
|
|
|
|
// Adding members to someone else project should not work
|
2022-06-05 23:41:38 +01:00
|
|
|
addedUsers, err = service.AddProjectMembers(userCtx1, up2Pro1.ID, []string{up2User.Email})
|
2020-10-06 15:25:53 +01:00
|
|
|
require.Error(t, err)
|
|
|
|
require.Nil(t, addedUsers)
|
|
|
|
})
|
|
|
|
|
2022-07-01 10:32:17 +01:00
|
|
|
t.Run("GetProjectMembers", func(t *testing.T) {
|
2020-10-06 15:25:53 +01:00
|
|
|
// Getting the project members of an own project that one is a part of should work
|
2022-06-05 23:41:38 +01:00
|
|
|
userPage, err := service.GetProjectMembers(userCtx1, up1Pro1.ID, console.ProjectMembersCursor{Page: 1, Limit: 10})
|
2020-10-06 15:25:53 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, userPage.ProjectMembers, 2)
|
|
|
|
|
|
|
|
// Getting the project members of a foreign project that one is a part of should work
|
2022-06-05 23:41:38 +01:00
|
|
|
userPage, err = service.GetProjectMembers(userCtx2, up1Pro1.ID, console.ProjectMembersCursor{Page: 1, Limit: 10})
|
2020-10-06 15:25:53 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, userPage.ProjectMembers, 2)
|
|
|
|
|
|
|
|
// Getting the project members of a foreign project that one is not a part of should not work
|
2022-06-05 23:41:38 +01:00
|
|
|
userPage, err = service.GetProjectMembers(userCtx1, up2Pro1.ID, console.ProjectMembersCursor{Page: 1, Limit: 10})
|
2020-10-06 15:25:53 +01:00
|
|
|
require.Error(t, err)
|
|
|
|
require.Nil(t, userPage)
|
|
|
|
})
|
|
|
|
|
2022-07-01 10:32:17 +01:00
|
|
|
t.Run("DeleteProjectMembers", func(t *testing.T) {
|
2020-10-06 15:25:53 +01:00
|
|
|
// Deleting project members of an own project should work
|
2022-06-05 23:41:38 +01:00
|
|
|
err := service.DeleteProjectMembers(userCtx1, up1Pro1.ID, []string{up2User.Email})
|
2020-10-06 15:25:53 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Deleting Project members of someone else project should not work
|
2022-06-05 23:41:38 +01:00
|
|
|
err = service.DeleteProjectMembers(userCtx1, up2Pro1.ID, []string{up2User.Email})
|
2020-10-06 15:25:53 +01:00
|
|
|
require.Error(t, err)
|
|
|
|
})
|
|
|
|
|
2022-07-01 10:32:17 +01:00
|
|
|
t.Run("DeleteProject", func(t *testing.T) {
|
2020-10-09 14:40:12 +01:00
|
|
|
// Deleting the own project should not work before deleting the API-Key
|
2022-06-05 23:41:38 +01:00
|
|
|
err := service.DeleteProject(userCtx1, up1Pro1.ID)
|
2020-10-09 14:40:12 +01:00
|
|
|
require.Error(t, err)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
keys, err := service.GetAPIKeys(userCtx1, up1Pro1.ID, console.APIKeyCursor{Page: 1, Limit: 10})
|
2020-10-09 14:40:12 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, keys.APIKeys, 1)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
err = service.DeleteAPIKeys(userCtx1, []uuid.UUID{keys.APIKeys[0].ID})
|
2020-10-09 14:40:12 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Deleting the own project should now work
|
2022-06-05 23:41:38 +01:00
|
|
|
err = service.DeleteProject(userCtx1, up1Pro1.ID)
|
2020-10-06 15:25:53 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Deleting someone else project should not work
|
2022-06-05 23:41:38 +01:00
|
|
|
err = service.DeleteProject(userCtx1, up2Pro1.ID)
|
2020-10-09 14:40:12 +01:00
|
|
|
require.Error(t, err)
|
|
|
|
|
|
|
|
err = planet.Uplinks[1].CreateBucket(ctx, sat, "testbucket")
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// deleting a project with a bucket should fail
|
2022-06-05 23:41:38 +01:00
|
|
|
err = service.DeleteProject(userCtx2, up2Pro1.ID)
|
2020-10-06 15:25:53 +01:00
|
|
|
require.Error(t, err)
|
2021-04-28 09:06:17 +01:00
|
|
|
require.Equal(t, "console service: project usage: some buckets still exist", err.Error())
|
2020-10-06 15:25:53 +01:00
|
|
|
})
|
2020-11-05 16:16:55 +00:00
|
|
|
|
2023-01-05 11:14:55 +00:00
|
|
|
t.Run("GetProjectUsageLimits", func(t *testing.T) {
|
|
|
|
bandwidthLimit := sat.Config.Console.UsageLimits.Bandwidth.Free
|
|
|
|
storageLimit := sat.Config.Console.UsageLimits.Storage.Free
|
|
|
|
|
|
|
|
limits1, err := service.GetProjectUsageLimits(userCtx2, up2Pro1.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, limits1)
|
|
|
|
|
|
|
|
// Get usage limits with publicID
|
|
|
|
limits2, err := service.GetProjectUsageLimits(userCtx2, up2Pro1.PublicID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, limits2)
|
|
|
|
|
|
|
|
// limits gotten by ID and publicID should be the same
|
|
|
|
require.Equal(t, storageLimit.Int64(), limits1.StorageLimit)
|
|
|
|
require.Equal(t, bandwidthLimit.Int64(), limits1.BandwidthLimit)
|
|
|
|
require.Equal(t, storageLimit.Int64(), limits2.StorageLimit)
|
|
|
|
require.Equal(t, bandwidthLimit.Int64(), limits2.BandwidthLimit)
|
|
|
|
|
|
|
|
// update project's limits
|
|
|
|
updatedStorageLimit := memory.Size(100) + memory.TB
|
|
|
|
updatedBandwidthLimit := memory.Size(100) + memory.TB
|
|
|
|
up2Pro1.StorageLimit = new(memory.Size)
|
|
|
|
*up2Pro1.StorageLimit = updatedStorageLimit
|
|
|
|
up2Pro1.BandwidthLimit = new(memory.Size)
|
|
|
|
*up2Pro1.BandwidthLimit = updatedBandwidthLimit
|
|
|
|
err = sat.DB.Console().Projects().Update(ctx, up2Pro1)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
limits1, err = service.GetProjectUsageLimits(userCtx2, up2Pro1.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, limits1)
|
|
|
|
|
|
|
|
// Get usage limits with publicID
|
|
|
|
limits2, err = service.GetProjectUsageLimits(userCtx2, up2Pro1.PublicID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, limits2)
|
|
|
|
|
|
|
|
// limits gotten by ID and publicID should be the same
|
|
|
|
require.Equal(t, updatedStorageLimit.Int64(), limits1.StorageLimit)
|
|
|
|
require.Equal(t, updatedBandwidthLimit.Int64(), limits1.BandwidthLimit)
|
|
|
|
require.Equal(t, updatedStorageLimit.Int64(), limits2.StorageLimit)
|
|
|
|
require.Equal(t, updatedBandwidthLimit.Int64(), limits2.BandwidthLimit)
|
|
|
|
})
|
|
|
|
|
2022-07-01 10:32:17 +01:00
|
|
|
t.Run("ChangeEmail", func(t *testing.T) {
|
2020-11-05 16:16:55 +00:00
|
|
|
const newEmail = "newEmail@example.com"
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
err = service.ChangeEmail(userCtx2, newEmail)
|
2020-11-05 16:16:55 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
user, _, err := service.GetUserByEmailWithUnverified(userCtx2, newEmail)
|
2020-11-05 16:16:55 +00:00
|
|
|
require.NoError(t, err)
|
2021-11-18 18:55:37 +00:00
|
|
|
require.Equal(t, newEmail, user.Email)
|
2020-11-05 16:16:55 +00:00
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
err = service.ChangeEmail(userCtx2, newEmail)
|
2020-11-05 16:16:55 +00:00
|
|
|
require.Error(t, err)
|
|
|
|
})
|
2020-11-13 11:41:35 +00:00
|
|
|
|
2022-07-01 10:32:17 +01:00
|
|
|
t.Run("GetAllBucketNames", func(t *testing.T) {
|
2023-04-13 13:04:07 +01:00
|
|
|
bucket1 := buckets.Bucket{
|
2020-11-13 11:41:35 +00:00
|
|
|
ID: testrand.UUID(),
|
|
|
|
Name: "testBucket1",
|
|
|
|
ProjectID: up2Pro1.ID,
|
|
|
|
}
|
|
|
|
|
2023-04-13 13:04:07 +01:00
|
|
|
bucket2 := buckets.Bucket{
|
2020-11-13 11:41:35 +00:00
|
|
|
ID: testrand.UUID(),
|
|
|
|
Name: "testBucket2",
|
|
|
|
ProjectID: up2Pro1.ID,
|
|
|
|
}
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
_, err := sat.API.Buckets.Service.CreateBucket(userCtx2, bucket1)
|
2020-11-13 11:41:35 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
_, err = sat.API.Buckets.Service.CreateBucket(userCtx2, bucket2)
|
2020-11-13 11:41:35 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
bucketNames, err := service.GetAllBucketNames(userCtx2, up2Pro1.ID)
|
2020-11-13 11:41:35 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, bucket1.Name, bucketNames[0])
|
|
|
|
require.Equal(t, bucket2.Name, bucketNames[1])
|
2020-11-30 16:51:47 +00:00
|
|
|
|
2023-02-13 18:19:20 +00:00
|
|
|
bucketNames, err = service.GetAllBucketNames(userCtx2, up2Pro1.PublicID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, bucket1.Name, bucketNames[0])
|
|
|
|
require.Equal(t, bucket2.Name, bucketNames[1])
|
|
|
|
|
2020-11-30 16:51:47 +00:00
|
|
|
// Getting someone else buckets should not work
|
2022-06-05 23:41:38 +01:00
|
|
|
bucketsForUnauthorizedUser, err := service.GetAllBucketNames(userCtx1, up2Pro1.ID)
|
2020-11-30 16:51:47 +00:00
|
|
|
require.Error(t, err)
|
|
|
|
require.Nil(t, bucketsForUnauthorizedUser)
|
2020-11-13 11:41:35 +00:00
|
|
|
})
|
2021-03-16 19:43:02 +00:00
|
|
|
|
2022-07-01 10:32:17 +01:00
|
|
|
t.Run("DeleteAPIKeyByNameAndProjectID", func(t *testing.T) {
|
2021-03-16 19:43:02 +00:00
|
|
|
secret, err := macaroon.NewSecret()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
key, err := macaroon.NewAPIKey(secret)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
apikey := console.APIKeyInfo{
|
|
|
|
Name: "test",
|
|
|
|
ProjectID: up2Pro1.ID,
|
|
|
|
Secret: secret,
|
|
|
|
}
|
|
|
|
|
|
|
|
createdKey, err := sat.DB.Console().APIKeys().Create(ctx, key.Head(), apikey)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
info, err := sat.DB.Console().APIKeys().Get(ctx, createdKey.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, info)
|
|
|
|
|
|
|
|
// Deleting someone else api keys should not work
|
2022-06-05 23:41:38 +01:00
|
|
|
err = service.DeleteAPIKeyByNameAndProjectID(userCtx1, apikey.Name, up2Pro1.ID)
|
2021-03-16 19:43:02 +00:00
|
|
|
require.Error(t, err)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
err = service.DeleteAPIKeyByNameAndProjectID(userCtx2, apikey.Name, up2Pro1.ID)
|
2021-03-16 19:43:02 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
info, err = sat.DB.Console().APIKeys().Get(ctx, createdKey.ID)
|
|
|
|
require.Error(t, err)
|
|
|
|
require.Nil(t, info)
|
2023-01-05 09:17:16 +00:00
|
|
|
|
|
|
|
// test deleting by project.publicID
|
|
|
|
createdKey, err = sat.DB.Console().APIKeys().Create(ctx, key.Head(), apikey)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
info, err = sat.DB.Console().APIKeys().Get(ctx, createdKey.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, info)
|
|
|
|
|
|
|
|
// deleting by project.publicID
|
|
|
|
err = service.DeleteAPIKeyByNameAndProjectID(userCtx2, apikey.Name, up2Pro1.PublicID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
info, err = sat.DB.Console().APIKeys().Get(ctx, createdKey.ID)
|
|
|
|
require.Error(t, err)
|
|
|
|
require.Nil(t, info)
|
2021-03-16 19:43:02 +00:00
|
|
|
})
|
2023-01-25 21:38:29 +00:00
|
|
|
t.Run("ApplyFreeTierCoupon", func(t *testing.T) {
|
|
|
|
// testplanet applies the free tier coupon first, so we need to change it in order
|
|
|
|
// to verify that ApplyFreeTierCoupon really works.
|
|
|
|
freeTier := sat.Config.Payments.StripeCoinPayments.StripeFreeTierCouponID
|
2023-04-06 12:41:14 +01:00
|
|
|
coupon3, err := service.Payments().ApplyCoupon(userCtx1, stripe.MockCouponID3)
|
2023-01-25 21:38:29 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, coupon3)
|
|
|
|
require.NotEqual(t, freeTier, coupon3.ID)
|
|
|
|
|
|
|
|
coupon, err := service.Payments().ApplyFreeTierCoupon(userCtx1)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, coupon)
|
|
|
|
require.Equal(t, freeTier, coupon.ID)
|
|
|
|
|
|
|
|
coupon, err = sat.API.Payments.Accounts.Coupons().GetByUserID(ctx, up1Pro1.OwnerID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, freeTier, coupon.ID)
|
|
|
|
|
|
|
|
})
|
|
|
|
t.Run("ApplyFreeTierCoupon fails with unknown user", func(t *testing.T) {
|
|
|
|
coupon, err := service.Payments().ApplyFreeTierCoupon(ctx)
|
|
|
|
require.Error(t, err)
|
|
|
|
require.Nil(t, coupon)
|
|
|
|
})
|
|
|
|
t.Run("ApplyCoupon", func(t *testing.T) {
|
2023-04-06 12:41:14 +01:00
|
|
|
id := stripe.MockCouponID2
|
2023-01-25 21:38:29 +00:00
|
|
|
coupon, err := service.Payments().ApplyCoupon(userCtx2, id)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, coupon)
|
|
|
|
require.Equal(t, id, coupon.ID)
|
|
|
|
|
|
|
|
coupon, err = sat.API.Payments.Accounts.Coupons().GetByUserID(ctx, up2Pro1.OwnerID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, id, coupon.ID)
|
|
|
|
})
|
|
|
|
t.Run("ApplyCoupon fails with unknown user", func(t *testing.T) {
|
2023-04-06 12:41:14 +01:00
|
|
|
id := stripe.MockCouponID2
|
2023-01-25 21:38:29 +00:00
|
|
|
coupon, err := service.Payments().ApplyCoupon(ctx, id)
|
|
|
|
require.Error(t, err)
|
|
|
|
require.Nil(t, coupon)
|
|
|
|
})
|
|
|
|
t.Run("ApplyCoupon fails with unknown coupon ID", func(t *testing.T) {
|
|
|
|
coupon, err := service.Payments().ApplyCoupon(userCtx2, "unknown_coupon_id")
|
|
|
|
require.Error(t, err)
|
|
|
|
require.Nil(t, coupon)
|
|
|
|
})
|
2023-03-22 20:23:44 +00:00
|
|
|
t.Run("UpdatePackage", func(t *testing.T) {
|
|
|
|
packagePlan := "package-plan-1"
|
|
|
|
purchaseTime := time.Now()
|
|
|
|
|
|
|
|
check := func() {
|
|
|
|
dbPackagePlan, dbPurchaseTime, err := sat.DB.StripeCoinPayments().Customers().GetPackageInfo(ctx, up1Pro1.OwnerID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, dbPackagePlan)
|
|
|
|
require.NotNil(t, dbPurchaseTime)
|
|
|
|
require.Equal(t, packagePlan, *dbPackagePlan)
|
|
|
|
require.Equal(t, dbPurchaseTime.Truncate(time.Millisecond), dbPurchaseTime.Truncate(time.Millisecond))
|
|
|
|
}
|
|
|
|
|
|
|
|
require.NoError(t, service.Payments().UpdatePackage(userCtx1, packagePlan, purchaseTime))
|
|
|
|
check()
|
|
|
|
|
|
|
|
// Check values can't be overwritten
|
|
|
|
err = service.Payments().UpdatePackage(userCtx1, "different-package-plan", time.Now())
|
|
|
|
require.Error(t, err)
|
|
|
|
require.True(t, console.ErrAlreadyHasPackage.Has(err))
|
|
|
|
|
|
|
|
check()
|
|
|
|
})
|
2023-03-24 22:25:36 +00:00
|
|
|
t.Run("ApplyCredit fails when payments.Balances.ApplyCredit returns an error", func(t *testing.T) {
|
2023-04-06 12:41:14 +01:00
|
|
|
require.Error(t, service.Payments().ApplyCredit(userCtx1, 1000, stripe.MockCBTXsNewFailure))
|
2023-03-24 22:25:36 +00:00
|
|
|
btxs, err := sat.API.Payments.Accounts.Balances().ListTransactions(ctx, up1Pro1.OwnerID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Zero(t, len(btxs))
|
|
|
|
})
|
|
|
|
t.Run("ApplyCredit", func(t *testing.T) {
|
|
|
|
amount := int64(1000)
|
|
|
|
desc := "test"
|
|
|
|
require.NoError(t, service.Payments().ApplyCredit(userCtx1, 1000, desc))
|
|
|
|
btxs, err := sat.API.Payments.Accounts.Balances().ListTransactions(ctx, up1Pro1.OwnerID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, btxs, 1)
|
|
|
|
require.Equal(t, amount, btxs[0].Amount)
|
|
|
|
require.Equal(t, desc, btxs[0].Description)
|
|
|
|
|
|
|
|
// test same description results in no new credit
|
|
|
|
require.NoError(t, service.Payments().ApplyCredit(userCtx1, 1000, desc))
|
|
|
|
btxs, err = sat.API.Payments.Accounts.Balances().ListTransactions(ctx, up1Pro1.OwnerID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, btxs, 1)
|
|
|
|
|
|
|
|
// test different description results in new credit
|
|
|
|
require.NoError(t, service.Payments().ApplyCredit(userCtx1, 1000, "new desc"))
|
|
|
|
btxs, err = sat.API.Payments.Accounts.Balances().ListTransactions(ctx, up1Pro1.OwnerID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, btxs, 2)
|
|
|
|
})
|
|
|
|
t.Run("ApplyCredit fails with unknown user", func(t *testing.T) {
|
|
|
|
require.Error(t, service.Payments().ApplyCredit(ctx, 1000, "test"))
|
|
|
|
})
|
2020-10-06 15:25:53 +01:00
|
|
|
})
|
|
|
|
}
|
2021-07-01 00:13:45 +01:00
|
|
|
|
|
|
|
func TestPaidTier(t *testing.T) {
|
|
|
|
usageConfig := console.UsageLimitsConfig{
|
|
|
|
Storage: console.StorageLimitConfig{
|
|
|
|
Free: memory.GB,
|
|
|
|
Paid: memory.TB,
|
|
|
|
},
|
|
|
|
Bandwidth: console.BandwidthLimitConfig{
|
|
|
|
Free: 2 * memory.GB,
|
|
|
|
Paid: 2 * memory.TB,
|
|
|
|
},
|
2021-12-06 19:06:50 +00:00
|
|
|
Segment: console.SegmentLimitConfig{
|
|
|
|
Free: 10,
|
|
|
|
Paid: 50,
|
|
|
|
},
|
2022-02-28 21:37:46 +00:00
|
|
|
Project: console.ProjectLimitConfig{
|
|
|
|
Free: 1,
|
|
|
|
Paid: 3,
|
|
|
|
},
|
2021-07-01 00:13:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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.UsageLimits = usageConfig
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
|
|
|
sat := planet.Satellites[0]
|
|
|
|
service := sat.API.Console.Service
|
|
|
|
|
|
|
|
// project should have free tier usage limits
|
|
|
|
proj1, err := sat.API.DB.Console().Projects().Get(ctx, planet.Uplinks[0].Projects[0].ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, usageConfig.Storage.Free, *proj1.StorageLimit)
|
|
|
|
require.Equal(t, usageConfig.Bandwidth.Free, *proj1.BandwidthLimit)
|
2022-02-28 21:37:46 +00:00
|
|
|
require.Equal(t, usageConfig.Segment.Free, *proj1.SegmentLimit)
|
2021-07-01 00:13:45 +01:00
|
|
|
|
|
|
|
// user should be in free tier
|
|
|
|
user, err := service.GetUser(ctx, proj1.OwnerID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.False(t, user.PaidTier)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
userCtx, err := sat.UserContext(ctx, user.ID)
|
2021-07-01 00:13:45 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// add a credit card to the user
|
2023-02-06 20:21:33 +00:00
|
|
|
_, err = service.Payments().AddCreditCard(userCtx, "test-cc-token")
|
2021-07-01 00:13:45 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// expect user to be in paid tier
|
|
|
|
user, err = service.GetUser(ctx, user.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.True(t, user.PaidTier)
|
2022-02-28 21:37:46 +00:00
|
|
|
require.Equal(t, usageConfig.Project.Paid, user.ProjectLimit)
|
2021-07-01 00:13:45 +01:00
|
|
|
|
|
|
|
// update auth ctx
|
2022-06-05 23:41:38 +01:00
|
|
|
userCtx, err = sat.UserContext(ctx, user.ID)
|
2021-07-01 00:13:45 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// expect project to be migrated to paid tier usage limits
|
2022-06-05 23:41:38 +01:00
|
|
|
proj1, err = service.GetProject(userCtx, proj1.ID)
|
2021-07-01 00:13:45 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, usageConfig.Storage.Paid, *proj1.StorageLimit)
|
|
|
|
require.Equal(t, usageConfig.Bandwidth.Paid, *proj1.BandwidthLimit)
|
2022-02-28 21:37:46 +00:00
|
|
|
require.Equal(t, usageConfig.Segment.Paid, *proj1.SegmentLimit)
|
2021-07-01 00:13:45 +01:00
|
|
|
|
|
|
|
// expect new project to be created with paid tier usage limits
|
2022-06-05 23:41:38 +01:00
|
|
|
proj2, err := service.CreateProject(userCtx, console.ProjectInfo{Name: "Project 2"})
|
2021-07-01 00:13:45 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, usageConfig.Storage.Paid, *proj2.StorageLimit)
|
|
|
|
})
|
|
|
|
}
|
2021-07-13 18:21:16 +01:00
|
|
|
|
2022-07-08 03:01:08 +01:00
|
|
|
// TestUpdateProjectExceedsLimits ensures that a project with limits manually set above the defaults can be updated.
|
|
|
|
func TestUpdateProjectExceedsLimits(t *testing.T) {
|
|
|
|
usageConfig := console.UsageLimitsConfig{
|
|
|
|
Storage: console.StorageLimitConfig{
|
|
|
|
Free: memory.GB,
|
|
|
|
Paid: memory.TB,
|
|
|
|
},
|
|
|
|
Bandwidth: console.BandwidthLimitConfig{
|
|
|
|
Free: 2 * memory.GB,
|
|
|
|
Paid: 2 * memory.TB,
|
|
|
|
},
|
|
|
|
Segment: console.SegmentLimitConfig{
|
|
|
|
Free: 10,
|
|
|
|
Paid: 50,
|
|
|
|
},
|
|
|
|
Project: console.ProjectLimitConfig{
|
|
|
|
Free: 1,
|
|
|
|
Paid: 3,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
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.UsageLimits = usageConfig
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
|
|
|
sat := planet.Satellites[0]
|
|
|
|
service := sat.API.Console.Service
|
|
|
|
projectID := planet.Uplinks[0].Projects[0].ID
|
|
|
|
|
|
|
|
updatedName := "newName"
|
|
|
|
updatedDescription := "newDescription"
|
|
|
|
updatedStorageLimit := memory.Size(100) + memory.TB
|
|
|
|
updatedBandwidthLimit := memory.Size(100) + memory.TB
|
|
|
|
|
|
|
|
proj, err := sat.API.DB.Console().Projects().Get(ctx, projectID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
userCtx1, err := sat.UserContext(ctx, proj.OwnerID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// project should have free tier usage limits
|
|
|
|
require.Equal(t, usageConfig.Storage.Free, *proj.StorageLimit)
|
|
|
|
require.Equal(t, usageConfig.Bandwidth.Free, *proj.BandwidthLimit)
|
|
|
|
require.Equal(t, usageConfig.Segment.Free, *proj.SegmentLimit)
|
|
|
|
|
|
|
|
// update project name should succeed
|
|
|
|
_, err = service.UpdateProject(userCtx1, projectID, console.ProjectInfo{
|
|
|
|
Name: updatedName,
|
|
|
|
Description: updatedDescription,
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// manually set project limits above defaults
|
|
|
|
proj1, err := sat.API.DB.Console().Projects().Get(ctx, projectID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
proj1.StorageLimit = new(memory.Size)
|
|
|
|
*proj1.StorageLimit = updatedStorageLimit
|
|
|
|
proj1.BandwidthLimit = new(memory.Size)
|
|
|
|
*proj1.BandwidthLimit = updatedBandwidthLimit
|
|
|
|
err = sat.DB.Console().Projects().Update(ctx, proj1)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// try to update project name should succeed
|
|
|
|
_, err = service.UpdateProject(userCtx1, projectID, console.ProjectInfo{
|
|
|
|
Name: "updatedName",
|
|
|
|
Description: "updatedDescription",
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-07-13 18:21:16 +01:00
|
|
|
func TestMFA(t *testing.T) {
|
|
|
|
testplanet.Run(t, testplanet.Config{
|
|
|
|
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 0,
|
|
|
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
|
|
|
sat := planet.Satellites[0]
|
|
|
|
service := sat.API.Console.Service
|
|
|
|
|
|
|
|
user, err := sat.AddUser(ctx, console.CreateUser{
|
|
|
|
FullName: "MFA Test User",
|
|
|
|
Email: "mfauser@mail.test",
|
|
|
|
}, 1)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
updateContext := func() (context.Context, *console.User) {
|
|
|
|
userCtx, err := sat.UserContext(ctx, user.ID)
|
2021-07-13 18:21:16 +01:00
|
|
|
require.NoError(t, err)
|
2022-06-05 23:41:38 +01:00
|
|
|
user, err := console.GetUser(userCtx)
|
2021-07-13 18:21:16 +01:00
|
|
|
require.NoError(t, err)
|
2022-06-05 23:41:38 +01:00
|
|
|
return userCtx, user
|
2021-07-13 18:21:16 +01:00
|
|
|
}
|
2022-06-05 23:41:38 +01:00
|
|
|
userCtx, user := updateContext()
|
2021-07-13 18:21:16 +01:00
|
|
|
|
|
|
|
var key string
|
2022-07-01 10:32:17 +01:00
|
|
|
t.Run("ResetMFASecretKey", func(t *testing.T) {
|
2022-06-05 23:41:38 +01:00
|
|
|
key, err = service.ResetMFASecretKey(userCtx)
|
2021-07-13 18:21:16 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
_, user := updateContext()
|
|
|
|
require.NotEmpty(t, user.MFASecretKey)
|
2021-07-13 18:21:16 +01:00
|
|
|
})
|
|
|
|
|
2022-07-01 10:32:17 +01:00
|
|
|
t.Run("EnableUserMFABadPasscode", func(t *testing.T) {
|
2021-07-13 18:21:16 +01:00
|
|
|
// Expect MFA-enabling attempt to be rejected when providing stale passcode.
|
|
|
|
badCode, err := console.NewMFAPasscode(key, time.Time{}.Add(time.Hour))
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
err = service.EnableUserMFA(userCtx, badCode, time.Time{})
|
2021-07-13 18:21:16 +01:00
|
|
|
require.True(t, console.ErrValidation.Has(err))
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
userCtx, _ = updateContext()
|
|
|
|
_, err = service.ResetMFARecoveryCodes(userCtx)
|
2021-07-13 18:21:16 +01:00
|
|
|
require.True(t, console.ErrUnauthorized.Has(err))
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
_, user = updateContext()
|
|
|
|
require.False(t, user.MFAEnabled)
|
2021-07-13 18:21:16 +01:00
|
|
|
})
|
|
|
|
|
2022-07-01 10:32:17 +01:00
|
|
|
t.Run("EnableUserMFAGoodPasscode", func(t *testing.T) {
|
2021-07-13 18:21:16 +01:00
|
|
|
// Expect MFA-enabling attempt to succeed when providing valid passcode.
|
|
|
|
goodCode, err := console.NewMFAPasscode(key, time.Time{})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
userCtx, _ = updateContext()
|
|
|
|
err = service.EnableUserMFA(userCtx, goodCode, time.Time{})
|
2021-07-13 18:21:16 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
_, user = updateContext()
|
|
|
|
require.True(t, user.MFAEnabled)
|
|
|
|
require.Equal(t, user.MFASecretKey, key)
|
2021-07-13 18:21:16 +01:00
|
|
|
})
|
|
|
|
|
2022-07-01 10:32:17 +01:00
|
|
|
t.Run("MFAGetToken", func(t *testing.T) {
|
2021-07-13 18:21:16 +01:00
|
|
|
request := console.AuthUser{Email: user.Email, Password: user.FullName}
|
|
|
|
|
|
|
|
// Expect no token due to lack of MFA passcode.
|
|
|
|
token, err := service.Token(ctx, request)
|
2021-08-16 22:23:06 +01:00
|
|
|
require.True(t, console.ErrMFAMissing.Has(err))
|
2021-07-13 18:21:16 +01:00
|
|
|
require.Empty(t, token)
|
|
|
|
|
|
|
|
// Expect no token due to bad MFA passcode.
|
2021-08-24 17:35:53 +01:00
|
|
|
wrongCode, err := console.NewMFAPasscode(key, time.Now().Add(time.Hour))
|
2021-07-13 18:21:16 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
request.MFAPasscode = wrongCode
|
|
|
|
token, err = service.Token(ctx, request)
|
2021-11-18 18:55:37 +00:00
|
|
|
require.True(t, console.ErrMFAPasscode.Has(err))
|
2021-07-13 18:21:16 +01:00
|
|
|
require.Empty(t, token)
|
|
|
|
|
|
|
|
// Expect token when providing valid passcode.
|
|
|
|
goodCode, err := console.NewMFAPasscode(key, time.Now())
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
request.MFAPasscode = goodCode
|
|
|
|
token, err = service.Token(ctx, request)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotEmpty(t, token)
|
|
|
|
})
|
|
|
|
|
2022-07-01 10:32:17 +01:00
|
|
|
t.Run("MFARecoveryCodes", func(t *testing.T) {
|
2022-06-05 23:41:38 +01:00
|
|
|
_, err = service.ResetMFARecoveryCodes(userCtx)
|
2021-07-13 18:21:16 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
_, user = updateContext()
|
|
|
|
require.Len(t, user.MFARecoveryCodes, console.MFARecoveryCodeCount)
|
2021-07-13 18:21:16 +01:00
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
for _, code := range user.MFARecoveryCodes {
|
2021-07-13 18:21:16 +01:00
|
|
|
// Ensure code is of the form XXXX-XXXX-XXXX where X is A-Z or 0-9.
|
|
|
|
require.Regexp(t, "^([A-Z0-9]{4})((-[A-Z0-9]{4})){2}$", code)
|
|
|
|
|
|
|
|
// Expect token when providing valid recovery code.
|
|
|
|
request := console.AuthUser{Email: user.Email, Password: user.FullName, MFARecoveryCode: code}
|
|
|
|
token, err := service.Token(ctx, request)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotEmpty(t, token)
|
|
|
|
|
|
|
|
// Expect no token due to providing previously-used recovery code.
|
|
|
|
token, err = service.Token(ctx, request)
|
2021-11-18 18:55:37 +00:00
|
|
|
require.True(t, console.ErrMFARecoveryCode.Has(err))
|
2021-07-13 18:21:16 +01:00
|
|
|
require.Empty(t, token)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
_, user = updateContext()
|
2021-07-13 18:21:16 +01:00
|
|
|
}
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
userCtx, _ = updateContext()
|
|
|
|
_, err = service.ResetMFARecoveryCodes(userCtx)
|
2021-07-13 18:21:16 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
})
|
|
|
|
|
2022-07-01 10:32:17 +01:00
|
|
|
t.Run("DisableUserMFABadPasscode", func(t *testing.T) {
|
2021-07-13 18:21:16 +01:00
|
|
|
// Expect MFA-disabling attempt to fail when providing valid passcode.
|
|
|
|
badCode, err := console.NewMFAPasscode(key, time.Time{}.Add(time.Hour))
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
userCtx, _ = updateContext()
|
|
|
|
err = service.DisableUserMFA(userCtx, badCode, time.Time{}, "")
|
2021-07-13 18:21:16 +01:00
|
|
|
require.True(t, console.ErrValidation.Has(err))
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
_, user = updateContext()
|
|
|
|
require.True(t, user.MFAEnabled)
|
|
|
|
require.NotEmpty(t, user.MFASecretKey)
|
|
|
|
require.NotEmpty(t, user.MFARecoveryCodes)
|
2021-07-13 18:21:16 +01:00
|
|
|
})
|
|
|
|
|
2022-07-01 10:32:17 +01:00
|
|
|
t.Run("DisableUserMFAConflict", func(t *testing.T) {
|
2021-08-16 22:23:06 +01:00
|
|
|
// Expect MFA-disabling attempt to fail when providing both recovery code and passcode.
|
|
|
|
goodCode, err := console.NewMFAPasscode(key, time.Time{})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
userCtx, user = updateContext()
|
|
|
|
err = service.DisableUserMFA(userCtx, goodCode, time.Time{}, user.MFARecoveryCodes[0])
|
2021-08-16 22:23:06 +01:00
|
|
|
require.True(t, console.ErrMFAConflict.Has(err))
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
_, user = updateContext()
|
|
|
|
require.True(t, user.MFAEnabled)
|
|
|
|
require.NotEmpty(t, user.MFASecretKey)
|
|
|
|
require.NotEmpty(t, user.MFARecoveryCodes)
|
2021-08-16 22:23:06 +01:00
|
|
|
})
|
|
|
|
|
2022-07-01 10:32:17 +01:00
|
|
|
t.Run("DisableUserMFAGoodPasscode", func(t *testing.T) {
|
2021-07-13 18:21:16 +01:00
|
|
|
// Expect MFA-disabling attempt to succeed when providing valid passcode.
|
|
|
|
goodCode, err := console.NewMFAPasscode(key, time.Time{})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
userCtx, _ = updateContext()
|
|
|
|
err = service.DisableUserMFA(userCtx, goodCode, time.Time{}, "")
|
2021-08-16 22:23:06 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
userCtx, user = updateContext()
|
|
|
|
require.False(t, user.MFAEnabled)
|
|
|
|
require.Empty(t, user.MFASecretKey)
|
|
|
|
require.Empty(t, user.MFARecoveryCodes)
|
2021-08-16 22:23:06 +01:00
|
|
|
})
|
|
|
|
|
2022-07-01 10:32:17 +01:00
|
|
|
t.Run("DisableUserMFAGoodRecoveryCode", func(t *testing.T) {
|
2021-08-16 22:23:06 +01:00
|
|
|
// Expect MFA-disabling attempt to succeed when providing valid recovery code.
|
|
|
|
// Enable MFA
|
2022-06-05 23:41:38 +01:00
|
|
|
key, err = service.ResetMFASecretKey(userCtx)
|
2021-08-16 22:23:06 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
goodCode, err := console.NewMFAPasscode(key, time.Time{})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
userCtx, _ = updateContext()
|
|
|
|
err = service.EnableUserMFA(userCtx, goodCode, time.Time{})
|
2021-08-16 22:23:06 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
userCtx, _ = updateContext()
|
|
|
|
_, err = service.ResetMFARecoveryCodes(userCtx)
|
2021-08-16 22:23:06 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
userCtx, user = updateContext()
|
|
|
|
require.True(t, user.MFAEnabled)
|
|
|
|
require.NotEmpty(t, user.MFASecretKey)
|
|
|
|
require.NotEmpty(t, user.MFARecoveryCodes)
|
2021-08-16 22:23:06 +01:00
|
|
|
|
|
|
|
// Disable MFA
|
2022-06-05 23:41:38 +01:00
|
|
|
err = service.DisableUserMFA(userCtx, "", time.Time{}, user.MFARecoveryCodes[0])
|
2021-07-13 18:21:16 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
_, user = updateContext()
|
|
|
|
require.False(t, user.MFAEnabled)
|
|
|
|
require.Empty(t, user.MFASecretKey)
|
|
|
|
require.Empty(t, user.MFARecoveryCodes)
|
2021-07-13 18:21:16 +01:00
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
2021-07-08 20:06:07 +01:00
|
|
|
|
2021-07-23 21:53:19 +01:00
|
|
|
func TestResetPassword(t *testing.T) {
|
|
|
|
testplanet.Run(t, testplanet.Config{
|
|
|
|
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 0,
|
|
|
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
|
|
|
sat := planet.Satellites[0]
|
|
|
|
service := sat.API.Console.Service
|
|
|
|
|
|
|
|
user, err := sat.AddUser(ctx, console.CreateUser{
|
|
|
|
FullName: "Test User",
|
|
|
|
Email: "test@mail.test",
|
|
|
|
}, 1)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-01-12 01:42:38 +00:00
|
|
|
newPass := user.FullName
|
|
|
|
|
|
|
|
getNewResetToken := func() *console.ResetPasswordToken {
|
|
|
|
token, err := sat.DB.Console().ResetPasswordTokens().Create(ctx, user.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, token)
|
|
|
|
return token
|
|
|
|
}
|
|
|
|
token := getNewResetToken()
|
2021-07-23 21:53:19 +01:00
|
|
|
|
|
|
|
// Expect error when providing bad token.
|
2022-01-12 01:42:38 +00:00
|
|
|
err = service.ResetPassword(ctx, "badToken", newPass, "", "", token.CreatedAt)
|
2021-07-23 21:53:19 +01:00
|
|
|
require.True(t, console.ErrRecoveryToken.Has(err))
|
|
|
|
|
|
|
|
// Expect error when providing good but expired token.
|
2022-04-19 21:50:15 +01:00
|
|
|
err = service.ResetPassword(ctx, token.Secret.String(), newPass, "", "", token.CreatedAt.Add(sat.Config.ConsoleAuth.TokenExpirationTime).Add(time.Second))
|
2021-07-23 21:53:19 +01:00
|
|
|
require.True(t, console.ErrTokenExpiration.Has(err))
|
|
|
|
|
|
|
|
// Expect error when providing good token with bad (too short) password.
|
2022-01-12 01:42:38 +00:00
|
|
|
err = service.ResetPassword(ctx, token.Secret.String(), "bad", "", "", token.CreatedAt)
|
2021-07-23 21:53:19 +01:00
|
|
|
require.True(t, console.ErrValidation.Has(err))
|
|
|
|
|
|
|
|
// Expect success when providing good token and good password.
|
2022-01-12 01:42:38 +00:00
|
|
|
err = service.ResetPassword(ctx, token.Secret.String(), newPass, "", "", token.CreatedAt)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
token = getNewResetToken()
|
|
|
|
|
|
|
|
// Enable MFA.
|
2022-06-05 23:41:38 +01:00
|
|
|
userCtx, err := sat.UserContext(ctx, user.ID)
|
|
|
|
require.NoError(t, err)
|
2022-01-12 01:42:38 +00:00
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
key, err := service.ResetMFASecretKey(userCtx)
|
|
|
|
require.NoError(t, err)
|
|
|
|
userCtx, err = sat.UserContext(ctx, user.ID)
|
2022-01-12 01:42:38 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
passcode, err := console.NewMFAPasscode(key, token.CreatedAt)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
err = service.EnableUserMFA(userCtx, passcode, token.CreatedAt)
|
2022-01-12 01:42:38 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Expect error when providing bad passcode.
|
|
|
|
badPasscode, err := console.NewMFAPasscode(key, token.CreatedAt.Add(time.Hour))
|
|
|
|
require.NoError(t, err)
|
|
|
|
err = service.ResetPassword(ctx, token.Secret.String(), newPass, badPasscode, "", token.CreatedAt)
|
|
|
|
require.True(t, console.ErrMFAPasscode.Has(err))
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
for _, recoveryCode := range user.MFARecoveryCodes {
|
2022-01-12 01:42:38 +00:00
|
|
|
// Expect success when providing bad passcode and good recovery code.
|
|
|
|
err = service.ResetPassword(ctx, token.Secret.String(), newPass, badPasscode, recoveryCode, token.CreatedAt)
|
|
|
|
require.NoError(t, err)
|
|
|
|
token = getNewResetToken()
|
|
|
|
|
|
|
|
// Expect error when providing bad passcode and already-used recovery code.
|
|
|
|
err = service.ResetPassword(ctx, token.Secret.String(), newPass, badPasscode, recoveryCode, token.CreatedAt)
|
|
|
|
require.True(t, console.ErrMFARecoveryCode.Has(err))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Expect success when providing good passcode.
|
|
|
|
err = service.ResetPassword(ctx, token.Secret.String(), newPass, passcode, "", token.CreatedAt)
|
2021-07-23 21:53:19 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
})
|
|
|
|
}
|
2021-10-06 14:33:54 +01:00
|
|
|
|
2023-03-01 19:52:52 +00:00
|
|
|
func TestChangePassword(t *testing.T) {
|
|
|
|
testplanet.Run(t, testplanet.Config{
|
|
|
|
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 1,
|
|
|
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
|
|
|
sat := planet.Satellites[0]
|
|
|
|
upl := planet.Uplinks[0]
|
|
|
|
newPass := "newPass123!"
|
|
|
|
|
|
|
|
user, err := sat.DB.Console().Users().GetByEmail(ctx, upl.User[sat.ID()].Email)
|
|
|
|
require.NoError(t, err)
|
|
|
|
userCtx, err := sat.UserContext(ctx, user.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// generate a password recovery token to test that changing password invalidates it
|
|
|
|
passwordRecoveryToken, err := sat.API.Console.Service.GeneratePasswordRecoveryToken(userCtx, user.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
require.NoError(t, sat.API.Console.Service.ChangePassword(userCtx, upl.User[sat.ID()].Password, newPass))
|
|
|
|
user, err = sat.DB.Console().Users().Get(ctx, user.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NoError(t, bcrypt.CompareHashAndPassword(user.PasswordHash, []byte(newPass)))
|
|
|
|
|
|
|
|
err = sat.API.Console.Service.ResetPassword(userCtx, passwordRecoveryToken, "aDifferentPassword123!", "", "", time.Now())
|
|
|
|
require.Error(t, err)
|
|
|
|
require.True(t, console.ErrRecoveryToken.Has(err))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-03-07 16:40:49 +00:00
|
|
|
func TestGenerateSessionToken(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.Session.InactivityTimerEnabled = true
|
|
|
|
config.Console.Session.InactivityTimerDuration = 600
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
|
|
|
sat := planet.Satellites[0]
|
|
|
|
srv := sat.API.Console.Service
|
|
|
|
|
|
|
|
user, _, err := srv.GetUserByEmailWithUnverified(ctx, planet.Uplinks[0].User[sat.ID()].Email)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
userCtx, err := sat.UserContext(ctx, user.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
now := time.Now()
|
|
|
|
token1, err := srv.GenerateSessionToken(userCtx, user.ID, user.Email, "", "")
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, token1)
|
|
|
|
|
|
|
|
token1Duration := token1.ExpiresAt.Sub(now)
|
|
|
|
increase := 10 * time.Minute
|
|
|
|
increasedDuration := time.Duration(sat.Config.Console.Session.InactivityTimerDuration)*time.Second + increase
|
|
|
|
ptr := &increasedDuration
|
|
|
|
require.NoError(t, sat.DB.Console().Users().UpsertSettings(ctx, user.ID, console.UpsertUserSettingsRequest{
|
|
|
|
SessionDuration: &ptr,
|
|
|
|
}))
|
|
|
|
|
|
|
|
now = time.Now()
|
|
|
|
token2, err := srv.GenerateSessionToken(userCtx, user.ID, user.Email, "", "")
|
|
|
|
require.NoError(t, err)
|
|
|
|
token2Duration := token2.ExpiresAt.Sub(now)
|
|
|
|
require.Greater(t, token2Duration, token1Duration)
|
|
|
|
|
|
|
|
decrease := -5 * time.Minute
|
|
|
|
decreasedDuration := time.Duration(sat.Config.Console.Session.InactivityTimerDuration)*time.Second + decrease
|
|
|
|
ptr = &decreasedDuration
|
|
|
|
require.NoError(t, sat.DB.Console().Users().UpsertSettings(ctx, user.ID, console.UpsertUserSettingsRequest{
|
|
|
|
SessionDuration: &ptr,
|
|
|
|
}))
|
|
|
|
|
|
|
|
now = time.Now()
|
|
|
|
token3, err := srv.GenerateSessionToken(userCtx, user.ID, user.Email, "", "")
|
|
|
|
require.NoError(t, err)
|
|
|
|
token3Duration := token3.ExpiresAt.Sub(now)
|
|
|
|
require.Less(t, token3Duration, token1Duration)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRefreshSessionToken(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.Session.InactivityTimerEnabled = true
|
|
|
|
config.Console.Session.InactivityTimerDuration = 600
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
|
|
|
sat := planet.Satellites[0]
|
|
|
|
srv := sat.API.Console.Service
|
|
|
|
|
|
|
|
user, _, err := srv.GetUserByEmailWithUnverified(ctx, planet.Uplinks[0].User[sat.ID()].Email)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
userCtx, err := sat.UserContext(ctx, user.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
now := time.Now()
|
|
|
|
token, err := srv.GenerateSessionToken(userCtx, user.ID, user.Email, "", "")
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, token)
|
|
|
|
|
|
|
|
defaultDuration := token.ExpiresAt.Sub(now)
|
|
|
|
increase := 10 * time.Minute
|
|
|
|
increasedDuration := time.Duration(sat.Config.Console.Session.InactivityTimerDuration)*time.Second + increase
|
|
|
|
ptr := &increasedDuration
|
|
|
|
require.NoError(t, sat.DB.Console().Users().UpsertSettings(ctx, user.ID, console.UpsertUserSettingsRequest{
|
|
|
|
SessionDuration: &ptr,
|
|
|
|
}))
|
|
|
|
|
|
|
|
sessionID, err := uuid.FromBytes(token.Token.Payload)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
now = time.Now()
|
|
|
|
increasedExpiration, err := srv.RefreshSession(userCtx, sessionID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Greater(t, increasedExpiration.Sub(now), defaultDuration)
|
|
|
|
|
|
|
|
decrease := -5 * time.Minute
|
|
|
|
decreasedDuration := time.Duration(sat.Config.Console.Session.InactivityTimerDuration)*time.Second + decrease
|
|
|
|
ptr = &decreasedDuration
|
|
|
|
require.NoError(t, sat.DB.Console().Users().UpsertSettings(ctx, user.ID, console.UpsertUserSettingsRequest{
|
|
|
|
SessionDuration: &ptr,
|
|
|
|
}))
|
|
|
|
|
|
|
|
now = time.Now()
|
|
|
|
decreasedExpiration, err := srv.RefreshSession(userCtx, sessionID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Less(t, decreasedExpiration.Sub(now), defaultDuration)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-03-16 11:42:01 +00:00
|
|
|
func TestUserSettings(t *testing.T) {
|
|
|
|
testplanet.Run(t, testplanet.Config{
|
|
|
|
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 1,
|
|
|
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
|
|
|
sat := planet.Satellites[0]
|
|
|
|
srv := sat.API.Console.Service
|
|
|
|
userDB := sat.DB.Console().Users()
|
|
|
|
|
2023-03-30 22:21:27 +01:00
|
|
|
existingUser, _, err := srv.GetUserByEmailWithUnverified(ctx, planet.Uplinks[0].User[sat.ID()].Email)
|
2023-03-16 11:42:01 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2023-03-30 22:21:27 +01:00
|
|
|
userCtx, err := sat.UserContext(ctx, existingUser.ID)
|
2023-03-16 11:42:01 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2023-03-30 22:21:27 +01:00
|
|
|
_, err = userDB.GetSettings(userCtx, existingUser.ID)
|
2023-03-16 11:42:01 +00:00
|
|
|
require.Error(t, err)
|
|
|
|
|
2023-03-30 22:21:27 +01:00
|
|
|
// a user that already has a project prior to getting user settings should not go through onboarding again
|
|
|
|
// in other words, onboarding start and end should both be true for users who have a project
|
2023-03-16 11:42:01 +00:00
|
|
|
settings, err := srv.GetUserSettings(userCtx)
|
|
|
|
require.NoError(t, err)
|
2023-03-30 22:21:27 +01:00
|
|
|
require.Equal(t, true, settings.OnboardingStart)
|
|
|
|
require.Equal(t, true, settings.OnboardingEnd)
|
|
|
|
require.Nil(t, settings.OnboardingStep)
|
|
|
|
require.Nil(t, settings.SessionDuration)
|
|
|
|
|
|
|
|
newUser, err := userDB.Insert(ctx, &console.User{
|
|
|
|
ID: testrand.UUID(),
|
|
|
|
Email: "newuser@example.com",
|
|
|
|
PasswordHash: []byte("i am a hash of a password, hello"),
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
userCtx, err = sat.UserContext(ctx, newUser.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// a brand new user with no project should go through onboarding
|
|
|
|
// in other words, onboarding start and end should both be false for users withouut a project
|
|
|
|
settings, err = srv.GetUserSettings(userCtx)
|
|
|
|
require.NoError(t, err)
|
2023-03-16 11:42:01 +00:00
|
|
|
require.Equal(t, false, settings.OnboardingStart)
|
|
|
|
require.Equal(t, false, settings.OnboardingEnd)
|
|
|
|
require.Nil(t, settings.OnboardingStep)
|
|
|
|
require.Nil(t, settings.SessionDuration)
|
|
|
|
|
|
|
|
onboardingBool := true
|
|
|
|
onboardingStep := "Overview"
|
|
|
|
sessionDur := time.Duration(rand.Int63()).Round(time.Minute)
|
|
|
|
sessionDurPtr := &sessionDur
|
2023-03-30 14:54:41 +01:00
|
|
|
settings, err = srv.SetUserSettings(userCtx, console.UpsertUserSettingsRequest{
|
2023-03-16 11:42:01 +00:00
|
|
|
SessionDuration: &sessionDurPtr,
|
|
|
|
OnboardingStart: &onboardingBool,
|
|
|
|
OnboardingEnd: &onboardingBool,
|
|
|
|
OnboardingStep: &onboardingStep,
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
2023-03-30 14:54:41 +01:00
|
|
|
require.Equal(t, onboardingBool, settings.OnboardingStart)
|
|
|
|
require.Equal(t, onboardingBool, settings.OnboardingEnd)
|
|
|
|
require.Equal(t, &onboardingStep, settings.OnboardingStep)
|
|
|
|
require.Equal(t, sessionDurPtr, settings.SessionDuration)
|
2023-03-16 11:42:01 +00:00
|
|
|
|
2023-03-30 22:21:27 +01:00
|
|
|
settings, err = userDB.GetSettings(userCtx, newUser.ID)
|
2023-03-16 11:42:01 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, onboardingBool, settings.OnboardingStart)
|
|
|
|
require.Equal(t, onboardingBool, settings.OnboardingEnd)
|
|
|
|
require.Equal(t, &onboardingStep, settings.OnboardingStep)
|
|
|
|
require.Equal(t, sessionDurPtr, settings.SessionDuration)
|
|
|
|
|
|
|
|
// passing nil should not override existing values
|
2023-03-30 14:54:41 +01:00
|
|
|
settings, err = srv.SetUserSettings(userCtx, console.UpsertUserSettingsRequest{
|
2023-03-16 11:42:01 +00:00
|
|
|
SessionDuration: nil,
|
|
|
|
OnboardingStart: nil,
|
|
|
|
OnboardingEnd: nil,
|
|
|
|
OnboardingStep: nil,
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
2023-03-30 14:54:41 +01:00
|
|
|
require.Equal(t, onboardingBool, settings.OnboardingStart)
|
|
|
|
require.Equal(t, onboardingBool, settings.OnboardingEnd)
|
|
|
|
require.Equal(t, &onboardingStep, settings.OnboardingStep)
|
|
|
|
require.Equal(t, sessionDurPtr, settings.SessionDuration)
|
2023-03-16 11:42:01 +00:00
|
|
|
|
2023-03-30 22:21:27 +01:00
|
|
|
settings, err = userDB.GetSettings(userCtx, newUser.ID)
|
2023-03-16 11:42:01 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, onboardingBool, settings.OnboardingStart)
|
|
|
|
require.Equal(t, onboardingBool, settings.OnboardingEnd)
|
|
|
|
require.Equal(t, &onboardingStep, settings.OnboardingStep)
|
|
|
|
require.Equal(t, sessionDurPtr, settings.SessionDuration)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-04-12 17:59:07 +01:00
|
|
|
func TestRESTKeys(t *testing.T) {
|
2022-02-11 22:48:35 +00:00
|
|
|
testplanet.Run(t, testplanet.Config{
|
|
|
|
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 1,
|
|
|
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
|
|
|
sat := planet.Satellites[0]
|
|
|
|
service := sat.API.Console.Service
|
|
|
|
|
|
|
|
proj1, err := sat.API.DB.Console().Projects().Get(ctx, planet.Uplinks[0].Projects[0].ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
user, err := service.GetUser(ctx, proj1.OwnerID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
userCtx, err := sat.UserContext(ctx, user.ID)
|
2022-02-11 22:48:35 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
now := time.Now()
|
|
|
|
expires := 5 * time.Hour
|
2022-06-05 23:41:38 +01:00
|
|
|
apiKey, expiresAt, err := service.CreateRESTKey(userCtx, expires)
|
2022-02-11 22:48:35 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotEmpty(t, apiKey)
|
|
|
|
require.True(t, expiresAt.After(now))
|
|
|
|
require.True(t, expiresAt.Before(now.Add(expires+time.Hour)))
|
|
|
|
|
|
|
|
// test revocation
|
2022-06-05 23:41:38 +01:00
|
|
|
require.NoError(t, service.RevokeRESTKey(userCtx, apiKey))
|
2022-02-11 22:48:35 +00:00
|
|
|
|
|
|
|
// test revoke non existent key
|
|
|
|
nonexistent := testrand.UUID()
|
2022-06-05 23:41:38 +01:00
|
|
|
err = service.RevokeRESTKey(userCtx, nonexistent.String())
|
2022-02-11 22:48:35 +00:00
|
|
|
require.Error(t, err)
|
|
|
|
})
|
|
|
|
}
|
2022-03-31 12:51:07 +01:00
|
|
|
|
|
|
|
// TestLockAccount ensures user's gets locked when incorrect credentials are provided.
|
|
|
|
func TestLockAccount(t *testing.T) {
|
|
|
|
testplanet.Run(t, testplanet.Config{
|
|
|
|
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 0,
|
|
|
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
|
|
|
sat := planet.Satellites[0]
|
|
|
|
service := sat.API.Console.Service
|
|
|
|
usersDB := sat.DB.Console().Users()
|
|
|
|
consoleConfig := sat.Config.Console
|
|
|
|
|
|
|
|
newUser := console.CreateUser{
|
|
|
|
FullName: "token test",
|
|
|
|
Email: "token_test@mail.test",
|
|
|
|
}
|
|
|
|
|
|
|
|
user, err := sat.AddUser(ctx, newUser, 1)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
updateContext := func() (context.Context, *console.User) {
|
|
|
|
userCtx, err := sat.UserContext(ctx, user.ID)
|
2022-03-31 12:51:07 +01:00
|
|
|
require.NoError(t, err)
|
2022-06-05 23:41:38 +01:00
|
|
|
user, err := console.GetUser(userCtx)
|
2022-03-31 12:51:07 +01:00
|
|
|
require.NoError(t, err)
|
2022-06-05 23:41:38 +01:00
|
|
|
return userCtx, user
|
2022-03-31 12:51:07 +01:00
|
|
|
}
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
userCtx, _ := updateContext()
|
|
|
|
secret, err := service.ResetMFASecretKey(userCtx)
|
2022-03-31 12:51:07 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
goodCode0, err := console.NewMFAPasscode(secret, time.Time{})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
userCtx, _ = updateContext()
|
|
|
|
err = service.EnableUserMFA(userCtx, goodCode0, time.Time{})
|
2022-03-31 12:51:07 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
now := time.Now()
|
|
|
|
|
|
|
|
goodCode1, err := console.NewMFAPasscode(secret, now)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
authUser := console.AuthUser{
|
|
|
|
Email: newUser.Email,
|
|
|
|
Password: newUser.FullName,
|
|
|
|
MFAPasscode: goodCode1,
|
|
|
|
}
|
|
|
|
|
|
|
|
// successful login.
|
|
|
|
token, err := service.Token(ctx, authUser)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotEmpty(t, token)
|
|
|
|
|
|
|
|
// check if user's account gets locked because of providing wrong password.
|
|
|
|
authUser.Password = "qweQWE1@"
|
|
|
|
for i := 1; i <= consoleConfig.LoginAttemptsWithoutPenalty; i++ {
|
|
|
|
token, err = service.Token(ctx, authUser)
|
|
|
|
require.Empty(t, token)
|
2022-11-30 04:54:07 +00:00
|
|
|
require.True(t, console.ErrLoginCredentials.Has(err))
|
2022-03-31 12:51:07 +01:00
|
|
|
}
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
lockedUser, err := service.GetUser(userCtx, user.ID)
|
2022-03-31 12:51:07 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.True(t, lockedUser.FailedLoginCount == consoleConfig.LoginAttemptsWithoutPenalty)
|
|
|
|
require.True(t, lockedUser.LoginLockoutExpiration.After(now))
|
|
|
|
|
|
|
|
// lock account once again and check if lockout expiration time increased.
|
2022-07-14 14:44:06 +01:00
|
|
|
err = service.UpdateUsersFailedLoginState(userCtx, lockedUser)
|
2022-03-31 12:51:07 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
lockedUser, err = service.GetUser(userCtx, user.ID)
|
2022-03-31 12:51:07 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.True(t, lockedUser.FailedLoginCount == consoleConfig.LoginAttemptsWithoutPenalty+1)
|
|
|
|
|
|
|
|
diff := lockedUser.LoginLockoutExpiration.Sub(now)
|
|
|
|
require.Greater(t, diff, time.Duration(consoleConfig.FailedLoginPenalty)*time.Minute)
|
|
|
|
|
|
|
|
// unlock account by successful login
|
|
|
|
lockedUser.LoginLockoutExpiration = now.Add(-time.Second)
|
2022-06-01 22:15:37 +01:00
|
|
|
lockoutExpirationPtr := &lockedUser.LoginLockoutExpiration
|
|
|
|
err = usersDB.Update(userCtx, lockedUser.ID, console.UpdateUserRequest{
|
|
|
|
LoginLockoutExpiration: &lockoutExpirationPtr,
|
|
|
|
})
|
2022-03-31 12:51:07 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
authUser.Password = newUser.FullName
|
|
|
|
token, err = service.Token(ctx, authUser)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotEmpty(t, token)
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
unlockedUser, err := service.GetUser(userCtx, user.ID)
|
2022-03-31 12:51:07 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.Zero(t, unlockedUser.FailedLoginCount)
|
|
|
|
|
|
|
|
// check if user's account gets locked because of providing wrong mfa passcode.
|
|
|
|
authUser.MFAPasscode = "000000"
|
|
|
|
for i := 1; i <= consoleConfig.LoginAttemptsWithoutPenalty; i++ {
|
|
|
|
token, err = service.Token(ctx, authUser)
|
|
|
|
require.Empty(t, token)
|
2022-07-20 10:32:09 +01:00
|
|
|
require.True(t, console.ErrMFAPasscode.Has(err))
|
2022-03-31 12:51:07 +01:00
|
|
|
}
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
lockedUser, err = service.GetUser(userCtx, user.ID)
|
2022-03-31 12:51:07 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.True(t, lockedUser.FailedLoginCount == consoleConfig.LoginAttemptsWithoutPenalty)
|
|
|
|
require.True(t, lockedUser.LoginLockoutExpiration.After(now))
|
|
|
|
|
|
|
|
// unlock account
|
|
|
|
lockedUser.LoginLockoutExpiration = time.Time{}
|
2022-06-01 22:15:37 +01:00
|
|
|
lockoutExpirationPtr = &lockedUser.LoginLockoutExpiration
|
2022-03-31 12:51:07 +01:00
|
|
|
lockedUser.FailedLoginCount = 0
|
2022-06-01 22:15:37 +01:00
|
|
|
err = usersDB.Update(userCtx, lockedUser.ID, console.UpdateUserRequest{
|
|
|
|
LoginLockoutExpiration: &lockoutExpirationPtr,
|
|
|
|
FailedLoginCount: &lockedUser.FailedLoginCount,
|
|
|
|
})
|
2022-03-31 12:51:07 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// check if user's account gets locked because of providing wrong mfa recovery code.
|
|
|
|
authUser.MFAPasscode = ""
|
|
|
|
authUser.MFARecoveryCode = "000000"
|
|
|
|
for i := 1; i <= consoleConfig.LoginAttemptsWithoutPenalty; i++ {
|
|
|
|
token, err = service.Token(ctx, authUser)
|
|
|
|
require.Empty(t, token)
|
2022-07-20 10:32:09 +01:00
|
|
|
require.True(t, console.ErrMFARecoveryCode.Has(err))
|
2022-03-31 12:51:07 +01:00
|
|
|
}
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
lockedUser, err = service.GetUser(userCtx, user.ID)
|
2022-03-31 12:51:07 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.True(t, lockedUser.FailedLoginCount == consoleConfig.LoginAttemptsWithoutPenalty)
|
|
|
|
require.True(t, lockedUser.LoginLockoutExpiration.After(now))
|
|
|
|
})
|
|
|
|
}
|
2022-05-11 09:02:58 +01:00
|
|
|
|
|
|
|
func TestWalletJsonMarshall(t *testing.T) {
|
|
|
|
wi := console.WalletInfo{
|
|
|
|
Address: blockchain.Address{1, 2, 3},
|
2022-09-21 16:01:36 +01:00
|
|
|
Balance: currency.AmountFromBaseUnits(10000, currency.USDollars),
|
2022-05-11 09:02:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
out, err := json.Marshal(wi)
|
|
|
|
require.NoError(t, err)
|
2022-10-06 15:22:45 +01:00
|
|
|
require.Contains(t, string(out), "\"address\":\"0x0102030000000000000000000000000000000000\"")
|
2022-09-21 16:01:36 +01:00
|
|
|
require.Contains(t, string(out), "\"balance\":{\"value\":\"100\",\"currency\":\"USD\"}")
|
2022-05-11 09:02:58 +01:00
|
|
|
|
|
|
|
}
|
2022-06-05 23:41:38 +01:00
|
|
|
|
|
|
|
func TestSessionExpiration(t *testing.T) {
|
|
|
|
testplanet.Run(t, testplanet.Config{
|
|
|
|
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 0,
|
|
|
|
Reconfigure: testplanet.Reconfigure{
|
|
|
|
Satellite: func(log *zap.Logger, index int, config *satellite.Config) {
|
2022-07-19 10:26:18 +01:00
|
|
|
config.Console.Session.Duration = time.Hour
|
2022-06-05 23:41:38 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
|
|
|
sat := planet.Satellites[0]
|
|
|
|
service := sat.API.Console.Service
|
|
|
|
|
|
|
|
user, err := sat.AddUser(ctx, console.CreateUser{
|
|
|
|
FullName: "Test User",
|
|
|
|
Email: "test@mail.test",
|
|
|
|
}, 1)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Session should be added to DB after token request
|
2022-07-19 10:26:18 +01:00
|
|
|
tokenInfo, err := service.Token(ctx, console.AuthUser{Email: user.Email, Password: user.FullName})
|
2022-06-05 23:41:38 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-07-19 10:26:18 +01:00
|
|
|
_, err = service.TokenAuth(ctx, tokenInfo.Token, time.Now())
|
2022-06-05 23:41:38 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-07-19 10:26:18 +01:00
|
|
|
sessionID, err := uuid.FromBytes(tokenInfo.Token.Payload)
|
2022-06-05 23:41:38 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
_, err = sat.DB.Console().WebappSessions().GetBySessionID(ctx, sessionID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Session should be removed from DB after it has expired
|
2022-07-19 10:26:18 +01:00
|
|
|
_, err = service.TokenAuth(ctx, tokenInfo.Token, time.Now().Add(2*time.Hour))
|
2022-06-05 23:41:38 +01:00
|
|
|
require.True(t, console.ErrTokenExpiration.Has(err))
|
|
|
|
|
|
|
|
_, err = sat.DB.Console().WebappSessions().GetBySessionID(ctx, sessionID)
|
|
|
|
require.ErrorIs(t, sql.ErrNoRows, err)
|
|
|
|
})
|
|
|
|
}
|
2022-06-16 14:26:27 +01:00
|
|
|
|
2022-11-21 22:56:09 +00:00
|
|
|
func TestDeleteAllSessionsByUserIDExcept(t *testing.T) {
|
|
|
|
testplanet.Run(t, testplanet.Config{
|
|
|
|
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 0,
|
|
|
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
|
|
|
sat := planet.Satellites[0]
|
|
|
|
service := sat.API.Console.Service
|
|
|
|
|
|
|
|
user, err := sat.AddUser(ctx, console.CreateUser{
|
|
|
|
FullName: "Test User",
|
|
|
|
Email: "test@mail.test",
|
|
|
|
}, 1)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Session should be added to DB after token request
|
|
|
|
tokenInfo, err := service.Token(ctx, console.AuthUser{Email: user.Email, Password: user.FullName})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
_, err = service.TokenAuth(ctx, tokenInfo.Token, time.Now())
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
sessionID, err := uuid.FromBytes(tokenInfo.Token.Payload)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
_, err = sat.DB.Console().WebappSessions().GetBySessionID(ctx, sessionID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Session2 should be added to DB after token request
|
|
|
|
tokenInfo2, err := service.Token(ctx, console.AuthUser{Email: user.Email, Password: user.FullName})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
_, err = service.TokenAuth(ctx, tokenInfo2.Token, time.Now())
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
sessionID2, err := uuid.FromBytes(tokenInfo2.Token.Payload)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
_, err = sat.DB.Console().WebappSessions().GetBySessionID(ctx, sessionID2)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Session2 should be removed from DB after calling DeleteAllSessionByUserIDExcept with Session1
|
|
|
|
err = service.DeleteAllSessionsByUserIDExcept(ctx, user.ID, sessionID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
_, err = sat.DB.Console().WebappSessions().GetBySessionID(ctx, sessionID2)
|
|
|
|
require.ErrorIs(t, sql.ErrNoRows, err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-06-16 14:26:27 +01:00
|
|
|
func TestPaymentsWalletPayments(t *testing.T) {
|
|
|
|
testplanet.Run(t, testplanet.Config{
|
|
|
|
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 0,
|
2023-05-15 16:14:30 +01:00
|
|
|
Reconfigure: testplanet.Reconfigure{
|
|
|
|
Satellite: func(log *zap.Logger, index int, config *satellite.Config) {
|
|
|
|
config.Payments.BillingConfig.DisableLoop = false
|
|
|
|
config.Payments.BonusRate = 10
|
|
|
|
},
|
|
|
|
},
|
2022-06-16 14:26:27 +01:00
|
|
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
|
|
|
sat := planet.Satellites[0]
|
|
|
|
now := time.Now().Truncate(time.Second).UTC()
|
|
|
|
|
|
|
|
user, err := sat.AddUser(ctx, console.CreateUser{
|
|
|
|
FullName: "Test User",
|
|
|
|
Email: "test@mail.test",
|
|
|
|
Password: "example",
|
|
|
|
}, 1)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
wallet := blockchaintest.NewAddress()
|
|
|
|
err = sat.DB.Wallets().Add(ctx, user.ID, wallet)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2023-04-06 12:41:14 +01:00
|
|
|
var transactions []stripe.Transaction
|
2022-06-16 14:26:27 +01:00
|
|
|
for i := 0; i < 5; i++ {
|
2023-04-06 12:41:14 +01:00
|
|
|
tx := stripe.Transaction{
|
2022-06-16 14:26:27 +01:00
|
|
|
ID: coinpayments.TransactionID(fmt.Sprintf("%d", i)),
|
|
|
|
AccountID: user.ID,
|
|
|
|
Address: blockchaintest.NewAddress().Hex(),
|
2022-09-06 13:43:09 +01:00
|
|
|
Amount: currency.AmountFromBaseUnits(1000000000, currency.StorjToken),
|
|
|
|
Received: currency.AmountFromBaseUnits(1000000000, currency.StorjToken),
|
2022-06-16 14:26:27 +01:00
|
|
|
Status: coinpayments.StatusCompleted,
|
|
|
|
Key: "key",
|
|
|
|
Timeout: 0,
|
|
|
|
}
|
|
|
|
|
2022-11-29 12:36:41 +00:00
|
|
|
createdAt, err := sat.DB.StripeCoinPayments().Transactions().TestInsert(ctx, tx)
|
2022-06-16 14:26:27 +01:00
|
|
|
require.NoError(t, err)
|
2022-11-29 12:36:41 +00:00
|
|
|
err = sat.DB.StripeCoinPayments().Transactions().TestLockRate(ctx, tx.ID, decimal.NewFromInt(1))
|
2022-06-16 14:26:27 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
tx.CreatedAt = createdAt.UTC()
|
|
|
|
transactions = append(transactions, tx)
|
|
|
|
}
|
|
|
|
|
|
|
|
var cachedPayments []storjscan.CachedPayment
|
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
cachedPayments = append(cachedPayments, storjscan.CachedPayment{
|
|
|
|
From: blockchaintest.NewAddress(),
|
|
|
|
To: wallet,
|
2022-09-06 13:43:09 +01:00
|
|
|
TokenValue: currency.AmountFromBaseUnits(1000, currency.StorjToken),
|
|
|
|
USDValue: currency.AmountFromBaseUnits(1000, currency.USDollarsMicro),
|
2022-06-16 14:26:27 +01:00
|
|
|
Status: payments.PaymentStatusConfirmed,
|
|
|
|
BlockHash: blockchaintest.NewHash(),
|
|
|
|
BlockNumber: int64(i),
|
|
|
|
Transaction: blockchaintest.NewHash(),
|
|
|
|
LogIndex: 0,
|
|
|
|
Timestamp: now.Add(time.Duration(i) * 15 * time.Second),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
err = sat.DB.StorjscanPayments().InsertBatch(ctx, cachedPayments)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
reqCtx := console.WithUser(ctx, user)
|
|
|
|
|
|
|
|
sort.Slice(cachedPayments, func(i, j int) bool {
|
|
|
|
return cachedPayments[i].BlockNumber > cachedPayments[j].BlockNumber
|
|
|
|
})
|
|
|
|
sort.Slice(transactions, func(i, j int) bool {
|
|
|
|
return transactions[i].CreatedAt.After(transactions[j].CreatedAt)
|
|
|
|
})
|
|
|
|
|
|
|
|
var expected []console.PaymentInfo
|
|
|
|
for _, pmnt := range cachedPayments {
|
|
|
|
expected = append(expected, console.PaymentInfo{
|
|
|
|
ID: fmt.Sprintf("%s#%d", pmnt.Transaction.Hex(), pmnt.LogIndex),
|
|
|
|
Type: "storjscan",
|
|
|
|
Wallet: pmnt.To.Hex(),
|
|
|
|
Amount: pmnt.USDValue,
|
|
|
|
Status: string(pmnt.Status),
|
|
|
|
Link: console.EtherscanURL(pmnt.Transaction.Hex()),
|
|
|
|
Timestamp: pmnt.Timestamp,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
for _, tx := range transactions {
|
|
|
|
expected = append(expected, console.PaymentInfo{
|
|
|
|
ID: tx.ID.String(),
|
|
|
|
Type: "coinpayments",
|
|
|
|
Wallet: tx.Address,
|
2022-09-06 13:43:09 +01:00
|
|
|
Amount: currency.AmountFromBaseUnits(1000, currency.USDollars),
|
|
|
|
Received: currency.AmountFromBaseUnits(1000, currency.USDollars),
|
2022-06-16 14:26:27 +01:00
|
|
|
Status: tx.Status.String(),
|
|
|
|
Link: coinpayments.GetCheckoutURL(tx.Key, tx.ID),
|
|
|
|
Timestamp: tx.CreatedAt,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-05-15 16:14:30 +01:00
|
|
|
// get billing chore to insert bonuses for transactions.
|
|
|
|
sat.Core.Payments.BillingChore.TransactionCycle.TriggerWait()
|
|
|
|
|
|
|
|
txns, err := sat.DB.Billing().ListSource(ctx, user.ID, billing.StorjScanBonusSource)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotEmpty(t, txns)
|
|
|
|
|
|
|
|
for _, txn := range txns {
|
|
|
|
if txn.Source != billing.StorjScanBonusSource {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
var meta struct {
|
|
|
|
ReferenceID string
|
|
|
|
Wallet string
|
|
|
|
LogIndex int
|
|
|
|
}
|
|
|
|
err = json.NewDecoder(bytes.NewReader(txn.Metadata)).Decode(&meta)
|
2023-06-01 11:53:10 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2023-05-15 16:14:30 +01:00
|
|
|
expected = append(expected, console.PaymentInfo{
|
|
|
|
ID: fmt.Sprintf("%s#%d", meta.ReferenceID, meta.LogIndex),
|
|
|
|
Type: txn.Source,
|
|
|
|
Wallet: meta.Wallet,
|
|
|
|
Amount: txn.Amount,
|
|
|
|
Status: string(txn.Status),
|
|
|
|
Link: console.EtherscanURL(meta.ReferenceID),
|
|
|
|
Timestamp: txn.Timestamp,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-06-16 14:26:27 +01:00
|
|
|
walletPayments, err := sat.API.Console.Service.Payments().WalletPayments(reqCtx)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, expected, walletPayments.Payments)
|
|
|
|
})
|
|
|
|
}
|
2023-01-06 21:40:03 +00:00
|
|
|
|
2023-02-02 22:11:09 +00:00
|
|
|
func TestPaymentsPurchase(t *testing.T) {
|
|
|
|
testplanet.Run(t, testplanet.Config{
|
|
|
|
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 0,
|
|
|
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
|
|
|
sat := planet.Satellites[0]
|
|
|
|
p := sat.API.Console.Service.Payments()
|
|
|
|
user, err := sat.AddUser(ctx, console.CreateUser{
|
|
|
|
FullName: "Test User",
|
|
|
|
Email: "test@mail.test",
|
|
|
|
}, 1)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
userCtx, err := sat.UserContext(ctx, user.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
testDesc := "testDescription"
|
|
|
|
testPaymentMethod := "testPaymentMethod"
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
purchaseDesc string
|
|
|
|
paymentMethod string
|
|
|
|
shouldErr bool
|
|
|
|
ctx context.Context
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
"Purchase returns error with unknown user",
|
|
|
|
testDesc,
|
|
|
|
testPaymentMethod,
|
|
|
|
true,
|
|
|
|
ctx,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Purchase returns error when underlying payments.Invoices.New returns error",
|
2023-04-06 12:41:14 +01:00
|
|
|
stripe.MockInvoicesNewFailure,
|
2023-02-02 22:11:09 +00:00
|
|
|
testPaymentMethod,
|
|
|
|
true,
|
|
|
|
userCtx,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Purchase returns error when underlying payments.Invoices.Pay returns error",
|
|
|
|
testDesc,
|
2023-04-06 12:41:14 +01:00
|
|
|
stripe.MockInvoicesPayFailure,
|
2023-02-02 22:11:09 +00:00
|
|
|
true,
|
|
|
|
userCtx,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Purchase success",
|
|
|
|
testDesc,
|
|
|
|
testPaymentMethod,
|
|
|
|
false,
|
|
|
|
userCtx,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
err := p.Purchase(tt.ctx, 1000, tt.purchaseDesc, tt.paymentMethod)
|
|
|
|
if tt.shouldErr {
|
|
|
|
require.NotNil(t, err)
|
|
|
|
} else {
|
|
|
|
require.Nil(t, err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPaymentsPurchasePreexistingInvoice(t *testing.T) {
|
|
|
|
testplanet.Run(t, testplanet.Config{
|
|
|
|
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 0,
|
|
|
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
|
|
|
sat := planet.Satellites[0]
|
|
|
|
p := sat.API.Console.Service.Payments()
|
|
|
|
|
|
|
|
user, err := sat.AddUser(ctx, console.CreateUser{
|
|
|
|
FullName: "Test User",
|
|
|
|
Email: "test@mail.test",
|
|
|
|
}, 1)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
userCtx, err := sat.UserContext(ctx, user.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2023-03-22 15:28:52 +00:00
|
|
|
draftInvDesc := "testDraftDescription"
|
2023-02-02 22:11:09 +00:00
|
|
|
testPaymentMethod := "testPaymentMethod"
|
|
|
|
|
|
|
|
invs, err := sat.API.Payments.StripeService.Accounts().Invoices().List(ctx, user.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, invs, 0)
|
|
|
|
|
|
|
|
// test purchase with draft invoice
|
2023-03-22 15:28:52 +00:00
|
|
|
inv, err := sat.API.Payments.StripeService.Accounts().Invoices().Create(ctx, user.ID, 1000, draftInvDesc)
|
2023-02-02 22:11:09 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, payments.InvoiceStatusDraft, inv.Status)
|
|
|
|
|
|
|
|
draftInv := inv.ID
|
|
|
|
|
|
|
|
invs, err = sat.API.Payments.StripeService.Accounts().Invoices().List(ctx, user.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, invs, 1)
|
|
|
|
require.Equal(t, draftInv, invs[0].ID)
|
|
|
|
|
2023-04-11 23:22:45 +01:00
|
|
|
require.NoError(t, p.Purchase(userCtx, 1000, draftInvDesc, stripe.MockInvoicesPaySuccess))
|
2023-02-02 22:11:09 +00:00
|
|
|
|
|
|
|
invs, err = sat.API.Payments.StripeService.Accounts().Invoices().List(ctx, user.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, invs, 1)
|
|
|
|
require.NotEqual(t, draftInv, invs[0].ID)
|
|
|
|
require.Equal(t, payments.InvoiceStatusPaid, invs[0].Status)
|
|
|
|
|
|
|
|
// test purchase with open invoice
|
2023-03-22 15:28:52 +00:00
|
|
|
openInvDesc := "testOpenDescription"
|
|
|
|
inv, err = sat.API.Payments.StripeService.Accounts().Invoices().Create(ctx, user.ID, 1000, openInvDesc)
|
2023-02-02 22:11:09 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
openInv := inv.ID
|
|
|
|
|
|
|
|
// attempting to pay a draft invoice changes it to open if payment fails
|
2023-04-06 12:41:14 +01:00
|
|
|
_, err = sat.API.Payments.StripeService.Accounts().Invoices().Pay(ctx, inv.ID, stripe.MockInvoicesPayFailure)
|
2023-02-02 22:11:09 +00:00
|
|
|
require.Error(t, err)
|
|
|
|
|
|
|
|
invs, err = sat.API.Payments.StripeService.Accounts().Invoices().List(ctx, user.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, invs, 2)
|
|
|
|
var foundInv bool
|
|
|
|
for _, inv := range invs {
|
|
|
|
if inv.ID == openInv {
|
|
|
|
foundInv = true
|
|
|
|
require.Equal(t, payments.InvoiceStatusOpen, inv.Status)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
require.True(t, foundInv)
|
|
|
|
|
2023-04-11 23:22:45 +01:00
|
|
|
require.NoError(t, p.Purchase(userCtx, 1000, openInvDesc, stripe.MockInvoicesPaySuccess))
|
2023-02-02 22:11:09 +00:00
|
|
|
|
|
|
|
invs, err = sat.API.Payments.StripeService.Accounts().Invoices().List(ctx, user.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, invs, 2)
|
|
|
|
foundInv = false
|
|
|
|
for _, inv := range invs {
|
|
|
|
if inv.ID == openInv {
|
|
|
|
foundInv = true
|
|
|
|
require.Equal(t, payments.InvoiceStatusPaid, inv.Status)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
require.True(t, foundInv)
|
2023-03-22 15:28:52 +00:00
|
|
|
|
|
|
|
// purchase with paid invoice skips creating and or paying invoice
|
|
|
|
require.NoError(t, p.Purchase(userCtx, 1000, openInvDesc, testPaymentMethod))
|
|
|
|
|
|
|
|
invs, err = sat.API.Payments.StripeService.Accounts().Invoices().List(ctx, user.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, invs, 2)
|
2023-02-02 22:11:09 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-01-06 21:40:03 +00:00
|
|
|
func TestServiceGenMethods(t *testing.T) {
|
|
|
|
testplanet.Run(t, testplanet.Config{
|
|
|
|
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 2,
|
|
|
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
|
|
|
sat := planet.Satellites[0]
|
|
|
|
s := sat.API.Console.Service
|
|
|
|
u0 := planet.Uplinks[0]
|
|
|
|
u1 := planet.Uplinks[1]
|
|
|
|
user0Ctx, err := sat.UserContext(ctx, u0.Projects[0].Owner.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
user1Ctx, err := sat.UserContext(ctx, u1.Projects[0].Owner.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
p0ID := u0.Projects[0].ID
|
|
|
|
p, err := s.GetProject(user1Ctx, u1.Projects[0].ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
p1PublicID := p.PublicID
|
|
|
|
|
|
|
|
for _, tt := range []struct {
|
|
|
|
name string
|
|
|
|
ID uuid.UUID
|
|
|
|
ctx context.Context
|
|
|
|
uplink *testplanet.Uplink
|
|
|
|
}{
|
|
|
|
{"projectID", p0ID, user0Ctx, u0},
|
|
|
|
{"publicID", p1PublicID, user1Ctx, u1},
|
|
|
|
} {
|
|
|
|
|
|
|
|
t.Run("GenUpdateProject with "+tt.name, func(t *testing.T) {
|
|
|
|
updatedName := "name " + tt.name
|
|
|
|
updatedDescription := "desc " + tt.name
|
|
|
|
updatedStorageLimit := memory.Size(100)
|
|
|
|
updatedBandwidthLimit := memory.Size(100)
|
|
|
|
|
|
|
|
info := console.ProjectInfo{
|
|
|
|
Name: updatedName,
|
|
|
|
Description: updatedDescription,
|
|
|
|
StorageLimit: updatedStorageLimit,
|
|
|
|
BandwidthLimit: updatedBandwidthLimit,
|
|
|
|
}
|
|
|
|
updatedProject, err := s.GenUpdateProject(tt.ctx, tt.ID, info)
|
|
|
|
require.NoError(t, err.Err)
|
|
|
|
if tt.name == "projectID" {
|
|
|
|
require.Equal(t, tt.ID, updatedProject.ID)
|
|
|
|
} else {
|
|
|
|
require.Equal(t, tt.ID, updatedProject.PublicID)
|
|
|
|
}
|
|
|
|
require.Equal(t, info.Name, updatedProject.Name)
|
|
|
|
require.Equal(t, info.Description, updatedProject.Description)
|
|
|
|
})
|
|
|
|
t.Run("GenCreateAPIKey with "+tt.name, func(t *testing.T) {
|
|
|
|
request := console.CreateAPIKeyRequest{
|
|
|
|
ProjectID: tt.ID.String(),
|
|
|
|
Name: tt.name + " Key",
|
|
|
|
}
|
|
|
|
apiKey, err := s.GenCreateAPIKey(tt.ctx, request)
|
|
|
|
require.NoError(t, err.Err)
|
|
|
|
require.Equal(t, tt.ID, apiKey.KeyInfo.ProjectID)
|
|
|
|
require.Equal(t, request.Name, apiKey.KeyInfo.Name)
|
|
|
|
})
|
|
|
|
t.Run("GenGetAPIKeys with "+tt.name, func(t *testing.T) {
|
|
|
|
apiKeys, err := s.GenGetAPIKeys(tt.ctx, tt.ID, "", 10, 1, 0, 0)
|
|
|
|
require.NoError(t, err.Err)
|
|
|
|
require.NotEmpty(t, apiKeys)
|
|
|
|
for _, key := range apiKeys.APIKeys {
|
|
|
|
require.Equal(t, tt.ID, key.ProjectID)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
bucket := "testbucket"
|
|
|
|
require.NoError(t, tt.uplink.CreateBucket(tt.ctx, sat, bucket))
|
|
|
|
require.NoError(t, tt.uplink.Upload(tt.ctx, sat, bucket, "helloworld.txt", []byte("hello world")))
|
|
|
|
sat.Accounting.Tally.Loop.TriggerWait()
|
|
|
|
|
|
|
|
t.Run("GenGetSingleBucketUsageRollup with "+tt.name, func(t *testing.T) {
|
|
|
|
rollup, err := s.GenGetSingleBucketUsageRollup(tt.ctx, tt.ID, bucket, time.Now().Add(-24*time.Hour), time.Now())
|
|
|
|
require.NoError(t, err.Err)
|
|
|
|
require.NotNil(t, rollup)
|
|
|
|
require.Equal(t, tt.ID, rollup.ProjectID)
|
|
|
|
})
|
|
|
|
t.Run("GenGetBucketUsageRollups with "+tt.name, func(t *testing.T) {
|
|
|
|
rollups, err := s.GenGetBucketUsageRollups(tt.ctx, tt.ID, time.Now().Add(-24*time.Hour), time.Now())
|
|
|
|
require.NoError(t, err.Err)
|
|
|
|
require.NotEmpty(t, rollups)
|
|
|
|
for _, r := range rollups {
|
|
|
|
require.Equal(t, tt.ID, r.ProjectID)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
// create empty project for easy deletion
|
|
|
|
p, err := s.CreateProject(tt.ctx, console.ProjectInfo{
|
|
|
|
Name: "foo",
|
|
|
|
Description: "bar",
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
t.Run("GenDeleteProject with "+tt.name, func(t *testing.T) {
|
|
|
|
var id uuid.UUID
|
|
|
|
if tt.name == "projectID" {
|
|
|
|
id = p.ID
|
|
|
|
} else {
|
|
|
|
id = p.PublicID
|
|
|
|
}
|
|
|
|
httpErr := s.GenDeleteProject(tt.ctx, id)
|
|
|
|
require.NoError(t, httpErr.Err)
|
|
|
|
p, err := s.GetProject(ctx, id)
|
|
|
|
require.Error(t, err)
|
|
|
|
require.Nil(t, p)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2023-06-01 08:27:09 +01:00
|
|
|
|
|
|
|
func TestProjectInvitations(t *testing.T) {
|
|
|
|
testplanet.Run(t, testplanet.Config{
|
|
|
|
SatelliteCount: 1,
|
|
|
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
|
|
|
sat := planet.Satellites[0]
|
|
|
|
service := sat.API.Console.Service
|
|
|
|
|
|
|
|
addUser := func(t *testing.T, ctx context.Context) *console.User {
|
|
|
|
user, err := sat.AddUser(ctx, console.CreateUser{
|
|
|
|
FullName: "Test User",
|
|
|
|
Email: fmt.Sprintf("%s@mail.test", testrand.RandAlphaNumeric(16)),
|
|
|
|
}, 1)
|
|
|
|
require.NoError(t, err)
|
|
|
|
return user
|
|
|
|
}
|
|
|
|
|
|
|
|
getUserAndCtx := func(t *testing.T) (*console.User, context.Context) {
|
|
|
|
ctx := testcontext.New(t)
|
|
|
|
user := addUser(t, ctx)
|
|
|
|
userCtx, err := sat.UserContext(ctx, user.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
return user, userCtx
|
|
|
|
}
|
|
|
|
|
|
|
|
addProject := func(t *testing.T, ctx context.Context) *console.Project {
|
|
|
|
owner := addUser(t, ctx)
|
|
|
|
project, err := sat.AddProject(ctx, owner.ID, "Test Project")
|
|
|
|
require.NoError(t, err)
|
|
|
|
return project
|
|
|
|
}
|
|
|
|
|
|
|
|
addInvite := func(t *testing.T, ctx context.Context, project *console.Project, email string, createdAt time.Time) *console.ProjectInvitation {
|
|
|
|
invite, err := sat.DB.Console().ProjectInvitations().Insert(ctx, &console.ProjectInvitation{
|
|
|
|
ProjectID: project.ID,
|
|
|
|
Email: email,
|
|
|
|
InviterID: &project.OwnerID,
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
result, err := sat.DB.Testing().RawDB().ExecContext(ctx,
|
|
|
|
"UPDATE project_invitations SET created_at = $1 WHERE project_id = $2 AND email = $3",
|
|
|
|
createdAt, invite.ProjectID, strings.ToUpper(invite.Email),
|
|
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
count, err := result.RowsAffected()
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.EqualValues(t, 1, count)
|
|
|
|
|
|
|
|
return invite
|
|
|
|
}
|
|
|
|
|
|
|
|
t.Run("get invitation", func(t *testing.T) {
|
|
|
|
user, ctx := getUserAndCtx(t)
|
|
|
|
|
|
|
|
invites, err := service.GetUserProjectInvitations(ctx)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Empty(t, invites)
|
|
|
|
|
|
|
|
invite := addInvite(t, ctx, addProject(t, ctx), user.Email, time.Now())
|
|
|
|
invites, err = service.GetUserProjectInvitations(ctx)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, invites, 1)
|
|
|
|
require.Equal(t, invite.ProjectID, invites[0].ProjectID)
|
|
|
|
require.Equal(t, invite.Email, invites[0].Email)
|
|
|
|
require.Equal(t, invite.InviterID, invites[0].InviterID)
|
|
|
|
require.WithinDuration(t, invite.CreatedAt, invites[0].CreatedAt, time.Second)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("accept invitation", func(t *testing.T) {
|
|
|
|
user, ctx := getUserAndCtx(t)
|
|
|
|
proj := addProject(t, ctx)
|
|
|
|
|
|
|
|
addInvite(t, ctx, proj, user.Email, time.Now().Add(-sat.Config.Console.ProjectInvitationExpiration))
|
|
|
|
err := service.RespondToProjectInvitation(ctx, proj.ID, console.ProjectInvitationAccept)
|
|
|
|
require.True(t, console.ErrProjectInviteInvalid.Has(err))
|
|
|
|
|
|
|
|
addInvite(t, ctx, proj, user.Email, time.Now())
|
|
|
|
require.NoError(t, service.RespondToProjectInvitation(ctx, proj.ID, console.ProjectInvitationAccept))
|
|
|
|
|
|
|
|
invites, err := service.GetUserProjectInvitations(ctx)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Empty(t, invites)
|
|
|
|
|
|
|
|
memberships, err := sat.DB.Console().ProjectMembers().GetByMemberID(ctx, user.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, memberships, 1)
|
|
|
|
require.Equal(t, proj.ID, memberships[0].ProjectID)
|
|
|
|
|
|
|
|
// Ensure that accepting an invitation for a project you are already a member of doesn't return an error.
|
|
|
|
// This is because the outcome of the operation is the same as if you weren't a member.
|
|
|
|
require.NoError(t, service.RespondToProjectInvitation(ctx, proj.ID, console.ProjectInvitationAccept))
|
|
|
|
// Ensure that an error is returned if you're a member of a project whose invitation you decline.
|
|
|
|
err = service.RespondToProjectInvitation(ctx, proj.ID, console.ProjectInvitationDecline)
|
|
|
|
require.True(t, console.ErrAlreadyMember.Has(err))
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("reject invitation", func(t *testing.T) {
|
|
|
|
user, ctx := getUserAndCtx(t)
|
|
|
|
proj := addProject(t, ctx)
|
|
|
|
|
|
|
|
addInvite(t, ctx, proj, user.Email, time.Now())
|
|
|
|
require.NoError(t, service.RespondToProjectInvitation(ctx, proj.ID, console.ProjectInvitationDecline))
|
|
|
|
|
|
|
|
invites, err := service.GetUserProjectInvitations(ctx)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Empty(t, invites)
|
|
|
|
|
|
|
|
memberships, err := sat.DB.Console().ProjectMembers().GetByMemberID(ctx, user.ID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Empty(t, memberships)
|
|
|
|
|
|
|
|
// Ensure that declining an invitation for a project you are not a member of doesn't return an error.
|
|
|
|
// This is because the outcome of the operation is the same as if you were a member.
|
|
|
|
require.NoError(t, service.RespondToProjectInvitation(ctx, proj.ID, console.ProjectInvitationDecline))
|
|
|
|
// Ensure that an error is returned if you try to accept an invitation that you have already declined or doesn't exist.
|
|
|
|
err = service.RespondToProjectInvitation(ctx, proj.ID, console.ProjectInvitationAccept)
|
|
|
|
require.True(t, console.ErrProjectInviteInvalid.Has(err))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|