storj/satellite/console/consoleweb/consoleql/query_test.go
Jeremy Wharton 3f26cc599f satellite/console,web/satellite: invalidate sessions after inactivity
Sessions now expire after a much shorter amount of time, requiring
clients to issue API requests for session extension. This is handled
behind the scenes as the user interacts with the page, but once session
expiration is imminent, a modal appears which informs the user of his
inactivity and presents him with the choice of loging out or preserving
his session.

Change-Id: I68008d45859c814a835d65d882ad5ad2199d618e
2022-08-23 15:51:05 +00:00

481 lines
15 KiB
Go

// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
package consoleql_test
import (
"fmt"
"testing"
"time"
"github.com/graphql-go/graphql"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest"
"storj.io/common/testcontext"
"storj.io/common/testrand"
"storj.io/storj/private/testplanet"
"storj.io/storj/private/testredis"
"storj.io/storj/satellite/accounting"
"storj.io/storj/satellite/accounting/live"
"storj.io/storj/satellite/analytics"
"storj.io/storj/satellite/console"
"storj.io/storj/satellite/console/consoleauth"
"storj.io/storj/satellite/console/consoleweb/consoleql"
"storj.io/storj/satellite/console/restkeys"
"storj.io/storj/satellite/mailservice"
"storj.io/storj/satellite/payments"
"storj.io/storj/satellite/payments/paymentsconfig"
"storj.io/storj/satellite/payments/stripecoinpayments"
"storj.io/storj/satellite/rewards"
)
func TestGraphqlQuery(t *testing.T) {
testplanet.Run(t, testplanet.Config{SatelliteCount: 1}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
sat := planet.Satellites[0]
db := sat.DB
log := zaptest.NewLogger(t)
partnersService := rewards.NewPartnersService(
log.Named("partners"),
rewards.DefaultPartnersDB,
)
analyticsService := analytics.NewService(log, analytics.Config{}, "test-satellite")
redis, err := testredis.Mini(ctx)
require.NoError(t, err)
defer ctx.Check(redis.Close)
cache, err := live.OpenCache(ctx, log.Named("cache"), live.Config{StorageBackend: "redis://" + redis.Addr() + "?db=0"})
require.NoError(t, err)
projectLimitCache := accounting.NewProjectLimitCache(db.ProjectAccounting(), 0, 0, 0, accounting.ProjectLimitConfig{CacheCapacity: 100})
projectUsage := accounting.NewService(db.ProjectAccounting(), cache, projectLimitCache, *sat.Metabase.DB, 5*time.Minute, -10*time.Second)
// TODO maybe switch this test to testplanet to avoid defining config and Stripe service
pc := paymentsconfig.Config{
StorageTBPrice: "10",
EgressTBPrice: "45",
SegmentPrice: "0.0000022",
}
paymentsService, err := stripecoinpayments.NewService(
log.Named("payments.stripe:service"),
stripecoinpayments.NewStripeMock(
testrand.NodeID(),
db.StripeCoinPayments().Customers(),
db.Console().Users(),
),
pc.StripeCoinPayments,
db.StripeCoinPayments(),
db.Wallets(),
db.Billing(),
db.Console().Projects(),
db.ProjectAccounting(),
pc.StorageTBPrice,
pc.EgressTBPrice,
pc.SegmentPrice,
pc.BonusRate)
require.NoError(t, err)
service, err := console.NewService(
log.Named("console"),
db.Console(),
restkeys.NewService(db.OIDC().OAuthTokens(), planet.Satellites[0].Config.RESTKeys),
db.ProjectAccounting(),
projectUsage,
sat.API.Buckets.Service,
partnersService,
paymentsService.Accounts(),
// TODO: do we need a payment deposit wallet here?
nil,
analyticsService,
consoleauth.NewService(consoleauth.Config{
TokenExpirationTime: 24 * time.Hour,
}, &consoleauth.Hmac{Secret: []byte("my-suppa-secret-key")}),
nil,
"",
console.Config{
PasswordCost: console.TestPasswordCost,
DefaultProjectLimit: 5,
Session: console.SessionConfig{
Duration: time.Hour,
},
},
)
require.NoError(t, err)
mailService, err := mailservice.New(log, &discardSender{}, "testdata")
require.NoError(t, err)
defer ctx.Check(mailService.Close)
rootObject := make(map[string]interface{})
rootObject["origin"] = "http://doesntmatter.com/"
rootObject[consoleql.ActivationPath] = "?activationToken="
rootObject[consoleql.LetUsKnowURL] = "letUsKnowURL"
rootObject[consoleql.ContactInfoURL] = "contactInfoURL"
rootObject[consoleql.TermsAndConditionsURL] = "termsAndConditionsURL"
creator := consoleql.TypeCreator{}
err = creator.Create(log, service, mailService)
require.NoError(t, err)
schema, err := graphql.NewSchema(graphql.SchemaConfig{
Query: creator.RootQuery(),
Mutation: creator.RootMutation(),
})
require.NoError(t, err)
createUser := console.CreateUser{
FullName: "John",
ShortName: "",
Email: "mtest@mail.test",
Password: "123a123",
SignupPromoCode: "promo1",
}
regToken, err := service.CreateRegToken(ctx, 2)
require.NoError(t, err)
rootUser, err := service.CreateUser(ctx, createUser, regToken.Secret)
require.NoError(t, err)
couponType, err := paymentsService.Accounts().Setup(ctx, rootUser.ID, rootUser.Email, rootUser.SignupPromoCode)
var signupCouponType payments.CouponType = payments.SignupCoupon
require.NoError(t, err)
assert.Equal(t, signupCouponType, couponType)
t.Run("Activation", func(t *testing.T) {
activationToken, err := service.GenerateActivationToken(
ctx,
rootUser.ID,
"mtest@mail.test",
)
require.NoError(t, err)
_, err = service.ActivateAccount(ctx, activationToken)
require.NoError(t, err)
rootUser.Email = "mtest@mail.test"
})
tokenInfo, err := service.Token(ctx, console.AuthUser{Email: createUser.Email, Password: createUser.Password})
require.NoError(t, err)
userCtx, err := service.TokenAuth(ctx, tokenInfo.Token, time.Now())
require.NoError(t, err)
testQuery := func(t *testing.T, query string) interface{} {
result := graphql.Do(graphql.Params{
Schema: schema,
Context: userCtx,
RequestString: query,
RootObject: rootObject,
})
for _, err := range result.Errors {
assert.NoError(t, err)
}
require.False(t, result.HasErrors())
return result.Data
}
createdProject, err := service.CreateProject(userCtx, console.ProjectInfo{
Name: "TestProject",
})
require.NoError(t, err)
// "query {project(id:\"%s\"){id,name,members(offset:0, limit:50){user{fullName,shortName,email}},apiKeys{name,id,createdAt,projectID}}}"
t.Run("Project query base info", func(t *testing.T) {
query := fmt.Sprintf(
"query {project(id:\"%s\"){id,name,description,createdAt}}",
createdProject.ID.String(),
)
result := testQuery(t, query)
data := result.(map[string]interface{})
project := data[consoleql.ProjectQuery].(map[string]interface{})
assert.Equal(t, createdProject.ID.String(), project[consoleql.FieldID])
assert.Equal(t, createdProject.Name, project[consoleql.FieldName])
assert.Equal(t, createdProject.Description, project[consoleql.FieldDescription])
createdAt := time.Time{}
err := createdAt.UnmarshalText([]byte(project[consoleql.FieldCreatedAt].(string)))
assert.NoError(t, err)
assert.True(t, createdProject.CreatedAt.Equal(createdAt))
})
regTokenUser1, err := service.CreateRegToken(ctx, 2)
require.NoError(t, err)
user1, err := service.CreateUser(userCtx, console.CreateUser{
FullName: "Mickey Last",
ShortName: "Last",
Password: "123a123",
Email: "muu1@mail.test",
}, regTokenUser1.Secret)
require.NoError(t, err)
t.Run("Activation", func(t *testing.T) {
activationToken1, err := service.GenerateActivationToken(
ctx,
user1.ID,
"muu1@mail.test",
)
require.NoError(t, err)
_, err = service.ActivateAccount(ctx, activationToken1)
require.NoError(t, err)
user1.Email = "muu1@mail.test"
})
regTokenUser2, err := service.CreateRegToken(ctx, 2)
require.NoError(t, err)
user2, err := service.CreateUser(userCtx, console.CreateUser{
FullName: "Dubas Name",
ShortName: "Name",
Email: "muu2@mail.test",
Password: "123a123",
}, regTokenUser2.Secret)
require.NoError(t, err)
t.Run("Activation", func(t *testing.T) {
activationToken2, err := service.GenerateActivationToken(
ctx,
user2.ID,
"muu2@mail.test",
)
require.NoError(t, err)
_, err = service.ActivateAccount(ctx, activationToken2)
require.NoError(t, err)
user2.Email = "muu2@mail.test"
})
users, err := service.AddProjectMembers(userCtx, createdProject.ID, []string{
user1.Email,
user2.Email,
})
require.NoError(t, err)
assert.Equal(t, 2, len(users))
t.Run("Project query team members", func(t *testing.T) {
query := fmt.Sprintf(
"query {project(id: \"%s\") {members( cursor: { limit: %d, search: \"%s\", page: %d, order: %d, orderDirection: %d } ) { projectMembers{ user { id, fullName, shortName, email, createdAt }, joinedAt }, search, limit, order, offset, pageCount, currentPage, totalCount } } }",
createdProject.ID.String(),
5,
"",
1,
1,
2)
result := testQuery(t, query)
data := result.(map[string]interface{})
project := data[consoleql.ProjectQuery].(map[string]interface{})
members := project[consoleql.FieldMembers].(map[string]interface{})
projectMembers := members[consoleql.FieldProjectMembers].([]interface{})
assert.Equal(t, 3, len(projectMembers))
testUser := func(t *testing.T, actual map[string]interface{}, expected *console.User) {
assert.Equal(t, expected.Email, actual[consoleql.FieldEmail])
assert.Equal(t, expected.FullName, actual[consoleql.FieldFullName])
assert.Equal(t, expected.ShortName, actual[consoleql.FieldShortName])
createdAt := time.Time{}
err := createdAt.UnmarshalText([]byte(actual[consoleql.FieldCreatedAt].(string)))
assert.NoError(t, err)
assert.True(t, expected.CreatedAt.Equal(createdAt))
}
var foundRoot, foundU1, foundU2 bool
for _, entry := range projectMembers {
member := entry.(map[string]interface{})
user := member[consoleql.UserType].(map[string]interface{})
id := user[consoleql.FieldID].(string)
switch id {
case rootUser.ID.String():
foundRoot = true
testUser(t, user, rootUser)
case user1.ID.String():
foundU1 = true
testUser(t, user, user1)
case user2.ID.String():
foundU2 = true
testUser(t, user, user2)
}
}
assert.True(t, foundRoot)
assert.True(t, foundU1)
assert.True(t, foundU2)
})
keyInfo1, _, err := service.CreateAPIKey(userCtx, createdProject.ID, "key1")
require.NoError(t, err)
keyInfo2, _, err := service.CreateAPIKey(userCtx, createdProject.ID, "key2")
require.NoError(t, err)
t.Run("Project query api keys", func(t *testing.T) {
query := fmt.Sprintf(
"query {project(id: \"%s\") {apiKeys( cursor: { limit: %d, search: \"%s\", page: %d, order: %d, orderDirection: %d } ) { apiKeys { id, name, createdAt, projectID }, search, limit, order, offset, pageCount, currentPage, totalCount } } }",
createdProject.ID.String(),
5,
"",
1,
1,
2)
result := testQuery(t, query)
data := result.(map[string]interface{})
project := data[consoleql.ProjectQuery].(map[string]interface{})
keys := project[consoleql.FieldAPIKeys].(map[string]interface{})
apiKeys := keys[consoleql.FieldAPIKeys].([]interface{})
assert.Equal(t, 2, len(apiKeys))
testAPIKey := func(t *testing.T, actual map[string]interface{}, expected *console.APIKeyInfo) {
assert.Equal(t, expected.Name, actual[consoleql.FieldName])
assert.Equal(t, expected.ProjectID.String(), actual[consoleql.FieldProjectID])
createdAt := time.Time{}
err := createdAt.UnmarshalText([]byte(actual[consoleql.FieldCreatedAt].(string)))
assert.NoError(t, err)
assert.True(t, expected.CreatedAt.Equal(createdAt))
}
var foundKey1, foundKey2 bool
for _, entry := range apiKeys {
key := entry.(map[string]interface{})
id := key[consoleql.FieldID].(string)
switch id {
case keyInfo1.ID.String():
foundKey1 = true
testAPIKey(t, key, keyInfo1)
case keyInfo2.ID.String():
foundKey2 = true
testAPIKey(t, key, keyInfo2)
}
}
assert.True(t, foundKey1)
assert.True(t, foundKey2)
})
project2, err := service.CreateProject(userCtx, console.ProjectInfo{
Name: "Project2",
Description: "Test desc",
})
require.NoError(t, err)
t.Run("MyProjects query", func(t *testing.T) {
query := "query {myProjects{id,name,description,createdAt}}"
result := testQuery(t, query)
data := result.(map[string]interface{})
projectsList := data[consoleql.MyProjectsQuery].([]interface{})
assert.Equal(t, 2, len(projectsList))
testProject := func(t *testing.T, actual map[string]interface{}, expected *console.Project) {
assert.Equal(t, expected.Name, actual[consoleql.FieldName])
assert.Equal(t, expected.Description, actual[consoleql.FieldDescription])
createdAt := time.Time{}
err := createdAt.UnmarshalText([]byte(actual[consoleql.FieldCreatedAt].(string)))
assert.NoError(t, err)
assert.True(t, expected.CreatedAt.Equal(createdAt))
}
var foundProj1, foundProj2 bool
for _, entry := range projectsList {
project := entry.(map[string]interface{})
id := project[consoleql.FieldID].(string)
switch id {
case createdProject.ID.String():
foundProj1 = true
testProject(t, project, createdProject)
case project2.ID.String():
foundProj2 = true
testProject(t, project, project2)
}
}
assert.True(t, foundProj1)
assert.True(t, foundProj2)
})
t.Run("OwnedProjects query", func(t *testing.T) {
query := fmt.Sprintf(
"query {ownedProjects( cursor: { limit: %d, page: %d } ) {projects{id, name, ownerId, description, createdAt, memberCount}, limit, offset, pageCount, currentPage, totalCount } }",
5,
1,
)
result := testQuery(t, query)
data := result.(map[string]interface{})
projectsPage := data[consoleql.OwnedProjectsQuery].(map[string]interface{})
projectsList := projectsPage[consoleql.FieldProjects].([]interface{})
assert.Len(t, projectsList, 2)
assert.EqualValues(t, 1, projectsPage[consoleql.FieldCurrentPage])
assert.EqualValues(t, 0, projectsPage[consoleql.OffsetArg])
assert.EqualValues(t, 5, projectsPage[consoleql.LimitArg])
assert.EqualValues(t, 1, projectsPage[consoleql.FieldPageCount])
assert.EqualValues(t, 2, projectsPage[consoleql.FieldTotalCount])
testProject := func(t *testing.T, actual map[string]interface{}, expected *console.Project, expectedNumMembers int) {
assert.Equal(t, expected.Name, actual[consoleql.FieldName])
assert.Equal(t, expected.Description, actual[consoleql.FieldDescription])
createdAt := time.Time{}
err := createdAt.UnmarshalText([]byte(actual[consoleql.FieldCreatedAt].(string)))
assert.NoError(t, err)
assert.True(t, expected.CreatedAt.Equal(createdAt))
assert.EqualValues(t, expectedNumMembers, actual[consoleql.FieldMemberCount])
}
var foundProj1, foundProj2 bool
for _, entry := range projectsList {
project := entry.(map[string]interface{})
id := project[consoleql.FieldID].(string)
switch id {
case createdProject.ID.String():
foundProj1 = true
testProject(t, project, createdProject, 3)
case project2.ID.String():
foundProj2 = true
testProject(t, project, project2, 1)
}
}
assert.True(t, foundProj1)
assert.True(t, foundProj2)
})
})
}