84b522bc06
We are in the process of creating an api to allow users to manage their accounts programmatically. We would like to use api keys for authorization. We were originally going to create an entirely new table for these api keys, but seeing as we already have 2 other tables for keys/tokens, api_keys and oauth_tokens, we thought it might be better to use one of these. We're using oauth_tokens. We create a new oidc.OAuthTokenKind for account management api keys: KindAccountManagementTokenV0. We made the key versioned because we likely want to improve the implementation in the future, but we want to get something functional out the door ASAP because the account management api feature is highly desired. Add a new method to oidc.OAuthTokens interface for revoking v0 account management api keys, RevokeAccountManagementTokenV0. Add update method to dbx implementation to allow updating the expiration. We will revoke these keys by setting the expiration to 0 so they are expired. Change-Id: Ideb8ae04b23aa55d5825b064b5e43e32eadc1fba
473 lines
14 KiB
Go
473 lines
14 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/accountmanagementapikeys"
|
|
"storj.io/storj/satellite/console/consoleauth"
|
|
"storj.io/storj/satellite/console/consoleweb/consoleql"
|
|
"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.Console().Projects(),
|
|
db.ProjectAccounting(),
|
|
pc.StorageTBPrice,
|
|
pc.EgressTBPrice,
|
|
pc.SegmentPrice,
|
|
pc.BonusRate)
|
|
require.NoError(t, err)
|
|
|
|
service, err := console.NewService(
|
|
log.Named("console"),
|
|
&consoleauth.Hmac{Secret: []byte("my-suppa-secret-key")},
|
|
db.Console(),
|
|
accountmanagementapikeys.NewService(db.OIDC().OAuthTokens(), planet.Satellites[0].Config.AccountManagementAPIKeys),
|
|
db.ProjectAccounting(),
|
|
projectUsage,
|
|
sat.API.Buckets.Service,
|
|
partnersService,
|
|
paymentsService.Accounts(),
|
|
analyticsService,
|
|
console.Config{
|
|
PasswordCost: console.TestPasswordCost,
|
|
DefaultProjectLimit: 5,
|
|
TokenExpirationTime: 24 * 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"
|
|
})
|
|
|
|
token, err := service.Token(ctx, console.AuthUser{Email: createUser.Email, Password: createUser.Password})
|
|
require.NoError(t, err)
|
|
|
|
sauth, err := service.Authorize(consoleauth.WithAPIKey(ctx, []byte(token)))
|
|
require.NoError(t, err)
|
|
|
|
authCtx := console.WithAuth(ctx, sauth)
|
|
|
|
testQuery := func(t *testing.T, query string) interface{} {
|
|
result := graphql.Do(graphql.Params{
|
|
Schema: schema,
|
|
Context: authCtx,
|
|
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(authCtx, 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(authCtx, 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(authCtx, 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(authCtx, 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(authCtx, createdProject.ID, "key1")
|
|
require.NoError(t, err)
|
|
|
|
keyInfo2, _, err := service.CreateAPIKey(authCtx, 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(authCtx, 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)
|
|
})
|
|
})
|
|
}
|