f61230a670
A chore responsible for purging data from the console DB has been implemented. Currently, it removes old records for unverified user accounts. We plan to extend this functionality to include expired project member invitations in the future. Resolves #5790 References #5816 Change-Id: I1f3ef62fc96c10a42a383804b3b1d2846d7813f7
439 lines
13 KiB
Go
439 lines
13 KiB
Go
// Copyright (C) 2019 Storj Labs, Inc.
|
|
// See LICENSE for copying information.
|
|
|
|
package satellitedb_test
|
|
|
|
import (
|
|
"database/sql"
|
|
"math/rand"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"storj.io/common/testcontext"
|
|
"storj.io/common/testrand"
|
|
"storj.io/common/uuid"
|
|
"storj.io/storj/satellite"
|
|
"storj.io/storj/satellite/console"
|
|
"storj.io/storj/satellite/satellitedb/satellitedbtest"
|
|
)
|
|
|
|
func TestGetUnverifiedNeedingReminderCutoff(t *testing.T) {
|
|
satellitedbtest.Run(t, func(ctx *testcontext.Context, t *testing.T, db satellite.DB) {
|
|
users := db.Console().Users()
|
|
|
|
id := testrand.UUID()
|
|
_, err := users.Insert(ctx, &console.User{
|
|
ID: id,
|
|
FullName: "test",
|
|
Email: "userone@mail.test",
|
|
PasswordHash: []byte("testpassword"),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
u, err := users.Get(ctx, id)
|
|
require.NoError(t, err)
|
|
require.Equal(t, console.UserStatus(0), u.Status)
|
|
|
|
now := time.Now()
|
|
reminders := now.Add(time.Hour)
|
|
|
|
// to get a reminder, created_at needs be after cutoff.
|
|
// since we don't have control over created_at, make cutoff in the future to test that
|
|
// user doesn't get a reminder.
|
|
cutoff := now.Add(time.Hour)
|
|
|
|
needingReminder, err := users.GetUnverifiedNeedingReminder(ctx, reminders, reminders, cutoff)
|
|
require.NoError(t, err)
|
|
require.Len(t, needingReminder, 0)
|
|
|
|
// change cutoff so user created_at is after it.
|
|
// user should get a reminder.
|
|
cutoff = now.Add(-time.Hour)
|
|
|
|
needingReminder, err = users.GetUnverifiedNeedingReminder(ctx, now, now, cutoff)
|
|
require.NoError(t, err)
|
|
require.Len(t, needingReminder, 1)
|
|
})
|
|
}
|
|
|
|
func TestUpdateUser(t *testing.T) {
|
|
satellitedbtest.Run(t, func(ctx *testcontext.Context, t *testing.T, db satellite.DB) {
|
|
users := db.Console().Users()
|
|
id := testrand.UUID()
|
|
u, err := users.Insert(ctx, &console.User{
|
|
ID: id,
|
|
FullName: "testFullName",
|
|
ShortName: "testShortName",
|
|
Email: "test@storj.test",
|
|
PasswordHash: []byte("testPasswordHash"),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
newInfo := console.User{
|
|
FullName: "updatedFullName",
|
|
ShortName: "updatedShortName",
|
|
PasswordHash: []byte("updatedPasswordHash"),
|
|
ProjectLimit: 1,
|
|
ProjectBandwidthLimit: 1,
|
|
ProjectStorageLimit: 1,
|
|
ProjectSegmentLimit: 1,
|
|
PaidTier: true,
|
|
MFAEnabled: true,
|
|
MFASecretKey: "secretKey",
|
|
MFARecoveryCodes: []string{"code1", "code2"},
|
|
FailedLoginCount: 1,
|
|
LoginLockoutExpiration: time.Now().Truncate(time.Second),
|
|
}
|
|
|
|
require.NotEqual(t, u.FullName, newInfo.FullName)
|
|
require.NotEqual(t, u.ShortName, newInfo.ShortName)
|
|
require.NotEqual(t, u.PasswordHash, newInfo.PasswordHash)
|
|
require.NotEqual(t, u.ProjectLimit, newInfo.ProjectLimit)
|
|
require.NotEqual(t, u.ProjectBandwidthLimit, newInfo.ProjectBandwidthLimit)
|
|
require.NotEqual(t, u.ProjectStorageLimit, newInfo.ProjectStorageLimit)
|
|
require.NotEqual(t, u.ProjectSegmentLimit, newInfo.ProjectSegmentLimit)
|
|
require.NotEqual(t, u.PaidTier, newInfo.PaidTier)
|
|
require.NotEqual(t, u.MFAEnabled, newInfo.MFAEnabled)
|
|
require.NotEqual(t, u.MFASecretKey, newInfo.MFASecretKey)
|
|
require.NotEqual(t, u.MFARecoveryCodes, newInfo.MFARecoveryCodes)
|
|
require.NotEqual(t, u.FailedLoginCount, newInfo.FailedLoginCount)
|
|
require.NotEqual(t, u.LoginLockoutExpiration, newInfo.LoginLockoutExpiration)
|
|
|
|
// update just fullname
|
|
updateReq := console.UpdateUserRequest{
|
|
FullName: &newInfo.FullName,
|
|
}
|
|
|
|
err = users.Update(ctx, id, updateReq)
|
|
require.NoError(t, err)
|
|
|
|
updatedUser, err := users.Get(ctx, id)
|
|
require.NoError(t, err)
|
|
|
|
u.FullName = newInfo.FullName
|
|
require.Equal(t, u, updatedUser)
|
|
|
|
// update just shortname
|
|
shortNamePtr := &newInfo.ShortName
|
|
updateReq = console.UpdateUserRequest{
|
|
ShortName: &shortNamePtr,
|
|
}
|
|
|
|
err = users.Update(ctx, id, updateReq)
|
|
require.NoError(t, err)
|
|
|
|
updatedUser, err = users.Get(ctx, id)
|
|
require.NoError(t, err)
|
|
|
|
u.ShortName = newInfo.ShortName
|
|
require.Equal(t, u, updatedUser)
|
|
|
|
// update just password hash
|
|
updateReq = console.UpdateUserRequest{
|
|
PasswordHash: newInfo.PasswordHash,
|
|
}
|
|
|
|
err = users.Update(ctx, id, updateReq)
|
|
require.NoError(t, err)
|
|
|
|
updatedUser, err = users.Get(ctx, id)
|
|
require.NoError(t, err)
|
|
|
|
u.PasswordHash = newInfo.PasswordHash
|
|
require.Equal(t, u, updatedUser)
|
|
|
|
// update just project limit
|
|
updateReq = console.UpdateUserRequest{
|
|
ProjectLimit: &newInfo.ProjectLimit,
|
|
}
|
|
|
|
err = users.Update(ctx, id, updateReq)
|
|
require.NoError(t, err)
|
|
|
|
updatedUser, err = users.Get(ctx, id)
|
|
require.NoError(t, err)
|
|
|
|
u.ProjectLimit = newInfo.ProjectLimit
|
|
require.Equal(t, u, updatedUser)
|
|
|
|
// update just project bw limit
|
|
updateReq = console.UpdateUserRequest{
|
|
ProjectBandwidthLimit: &newInfo.ProjectBandwidthLimit,
|
|
}
|
|
|
|
err = users.Update(ctx, id, updateReq)
|
|
require.NoError(t, err)
|
|
|
|
updatedUser, err = users.Get(ctx, id)
|
|
require.NoError(t, err)
|
|
|
|
u.ProjectBandwidthLimit = newInfo.ProjectBandwidthLimit
|
|
require.Equal(t, u, updatedUser)
|
|
|
|
// update just project storage limit
|
|
updateReq = console.UpdateUserRequest{
|
|
ProjectStorageLimit: &newInfo.ProjectStorageLimit,
|
|
}
|
|
|
|
err = users.Update(ctx, id, updateReq)
|
|
require.NoError(t, err)
|
|
|
|
updatedUser, err = users.Get(ctx, id)
|
|
require.NoError(t, err)
|
|
|
|
u.ProjectStorageLimit = newInfo.ProjectStorageLimit
|
|
require.Equal(t, u, updatedUser)
|
|
|
|
// update just project segment limit
|
|
updateReq = console.UpdateUserRequest{
|
|
ProjectSegmentLimit: &newInfo.ProjectSegmentLimit,
|
|
}
|
|
|
|
err = users.Update(ctx, id, updateReq)
|
|
require.NoError(t, err)
|
|
|
|
updatedUser, err = users.Get(ctx, id)
|
|
require.NoError(t, err)
|
|
|
|
u.ProjectSegmentLimit = newInfo.ProjectSegmentLimit
|
|
require.Equal(t, u, updatedUser)
|
|
|
|
// update just paid tier
|
|
updateReq = console.UpdateUserRequest{
|
|
PaidTier: &newInfo.PaidTier,
|
|
}
|
|
|
|
err = users.Update(ctx, id, updateReq)
|
|
require.NoError(t, err)
|
|
|
|
updatedUser, err = users.Get(ctx, id)
|
|
require.NoError(t, err)
|
|
|
|
u.PaidTier = newInfo.PaidTier
|
|
require.Equal(t, u, updatedUser)
|
|
|
|
// update just mfa enabled
|
|
updateReq = console.UpdateUserRequest{
|
|
MFAEnabled: &newInfo.MFAEnabled,
|
|
}
|
|
|
|
err = users.Update(ctx, id, updateReq)
|
|
require.NoError(t, err)
|
|
|
|
updatedUser, err = users.Get(ctx, id)
|
|
require.NoError(t, err)
|
|
|
|
u.MFAEnabled = newInfo.MFAEnabled
|
|
require.Equal(t, u, updatedUser)
|
|
|
|
// update just mfa secret key
|
|
secretKeyPtr := &newInfo.MFASecretKey
|
|
updateReq = console.UpdateUserRequest{
|
|
MFASecretKey: &secretKeyPtr,
|
|
}
|
|
|
|
err = users.Update(ctx, id, updateReq)
|
|
require.NoError(t, err)
|
|
|
|
updatedUser, err = users.Get(ctx, id)
|
|
require.NoError(t, err)
|
|
|
|
u.MFASecretKey = newInfo.MFASecretKey
|
|
require.Equal(t, u, updatedUser)
|
|
|
|
// update just mfa recovery codes
|
|
updateReq = console.UpdateUserRequest{
|
|
MFARecoveryCodes: &newInfo.MFARecoveryCodes,
|
|
}
|
|
|
|
err = users.Update(ctx, id, updateReq)
|
|
require.NoError(t, err)
|
|
|
|
updatedUser, err = users.Get(ctx, id)
|
|
require.NoError(t, err)
|
|
|
|
u.MFARecoveryCodes = newInfo.MFARecoveryCodes
|
|
require.Equal(t, u, updatedUser)
|
|
|
|
// update just failed login count
|
|
updateReq = console.UpdateUserRequest{
|
|
FailedLoginCount: &newInfo.FailedLoginCount,
|
|
}
|
|
|
|
err = users.Update(ctx, id, updateReq)
|
|
require.NoError(t, err)
|
|
|
|
updatedUser, err = users.Get(ctx, id)
|
|
require.NoError(t, err)
|
|
|
|
u.FailedLoginCount = newInfo.FailedLoginCount
|
|
require.Equal(t, u, updatedUser)
|
|
|
|
// update just login lockout expiration
|
|
loginLockoutExpPtr := &newInfo.LoginLockoutExpiration
|
|
updateReq = console.UpdateUserRequest{
|
|
LoginLockoutExpiration: &loginLockoutExpPtr,
|
|
}
|
|
|
|
err = users.Update(ctx, id, updateReq)
|
|
require.NoError(t, err)
|
|
|
|
updatedUser, err = users.Get(ctx, id)
|
|
require.NoError(t, err)
|
|
|
|
u.LoginLockoutExpiration = newInfo.LoginLockoutExpiration
|
|
require.Equal(t, u, updatedUser)
|
|
})
|
|
}
|
|
|
|
func TestUpdateUserProjectLimits(t *testing.T) {
|
|
satellitedbtest.Run(t, func(ctx *testcontext.Context, t *testing.T, db satellite.DB) {
|
|
limits := console.UsageLimits{Storage: rand.Int63(), Bandwidth: rand.Int63(), Segment: rand.Int63()}
|
|
usersRepo := db.Console().Users()
|
|
|
|
user, err := usersRepo.Insert(ctx, &console.User{
|
|
ID: testrand.UUID(),
|
|
FullName: "User",
|
|
Email: "test@mail.test",
|
|
PasswordHash: []byte("123a123"),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
err = usersRepo.UpdateUserProjectLimits(ctx, user.ID, limits)
|
|
require.NoError(t, err)
|
|
|
|
user, err = usersRepo.Get(ctx, user.ID)
|
|
require.NoError(t, err)
|
|
require.Equal(t, limits.Bandwidth, user.ProjectBandwidthLimit)
|
|
require.Equal(t, limits.Storage, user.ProjectStorageLimit)
|
|
require.Equal(t, limits.Segment, user.ProjectSegmentLimit)
|
|
})
|
|
}
|
|
|
|
func TestUserSettings(t *testing.T) {
|
|
satellitedbtest.Run(t, func(ctx *testcontext.Context, t *testing.T, db satellite.DB) {
|
|
users := db.Console().Users()
|
|
id := testrand.UUID()
|
|
sessionDur := time.Duration(rand.Int63()).Round(time.Minute)
|
|
sessionDurPtr := &sessionDur
|
|
var nilDur *time.Duration
|
|
|
|
_, err := users.GetSettings(ctx, id)
|
|
require.ErrorIs(t, err, sql.ErrNoRows)
|
|
|
|
for _, tt := range []struct {
|
|
name string
|
|
upserted **time.Duration
|
|
expected *time.Duration
|
|
}{
|
|
{"update when given pointer to non-nil value", &sessionDurPtr, sessionDurPtr},
|
|
{"ignore when given nil pointer", nil, sessionDurPtr},
|
|
{"nullify when given pointer to nil", &nilDur, nil},
|
|
} {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
require.NoError(t, users.UpsertSettings(ctx, id, console.UpsertUserSettingsRequest{
|
|
SessionDuration: tt.upserted,
|
|
}))
|
|
settings, err := users.GetSettings(ctx, id)
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.expected, settings.SessionDuration)
|
|
})
|
|
}
|
|
|
|
t.Run("test onboarding", func(t *testing.T) {
|
|
id = testrand.UUID()
|
|
require.NoError(t, users.UpsertSettings(ctx, id, console.UpsertUserSettingsRequest{}))
|
|
settings, err := users.GetSettings(ctx, id)
|
|
require.NoError(t, err)
|
|
require.False(t, settings.OnboardingStart)
|
|
require.False(t, settings.OnboardingEnd)
|
|
require.Nil(t, settings.OnboardingStep)
|
|
|
|
newBool := true
|
|
newStep := "Overview"
|
|
require.NoError(t, users.UpsertSettings(ctx, id, console.UpsertUserSettingsRequest{
|
|
OnboardingStart: &newBool,
|
|
OnboardingEnd: &newBool,
|
|
OnboardingStep: &newStep,
|
|
}))
|
|
settings, err = users.GetSettings(ctx, id)
|
|
require.NoError(t, err)
|
|
require.Equal(t, newBool, settings.OnboardingStart)
|
|
require.Equal(t, newBool, settings.OnboardingEnd)
|
|
require.Equal(t, &newStep, settings.OnboardingStep)
|
|
})
|
|
|
|
t.Run("test passphrase prompt", func(t *testing.T) {
|
|
id = testrand.UUID()
|
|
require.NoError(t, users.UpsertSettings(ctx, id, console.UpsertUserSettingsRequest{}))
|
|
settings, err := users.GetSettings(ctx, id)
|
|
require.NoError(t, err)
|
|
require.True(t, settings.PassphrasePrompt)
|
|
|
|
newBool := false
|
|
require.NoError(t, users.UpsertSettings(ctx, id, console.UpsertUserSettingsRequest{
|
|
PassphrasePrompt: &newBool,
|
|
}))
|
|
settings, err = users.GetSettings(ctx, id)
|
|
require.NoError(t, err)
|
|
require.Equal(t, newBool, settings.PassphrasePrompt)
|
|
|
|
require.NoError(t, users.UpsertSettings(ctx, id, console.UpsertUserSettingsRequest{}))
|
|
settings, err = users.GetSettings(ctx, id)
|
|
require.NoError(t, err)
|
|
require.Equal(t, newBool, settings.PassphrasePrompt)
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestDeleteUnverifiedBefore(t *testing.T) {
|
|
maxUnverifiedAge := time.Hour
|
|
now := time.Now()
|
|
expiration := now.Add(-maxUnverifiedAge)
|
|
|
|
satellitedbtest.Run(t, func(ctx *testcontext.Context, t *testing.T, db satellite.DB) {
|
|
usersDB := db.Console().Users()
|
|
now := time.Now()
|
|
|
|
// Only positive page sizes should be allowed.
|
|
require.Error(t, usersDB.DeleteUnverifiedBefore(ctx, time.Time{}, 0, 0))
|
|
require.Error(t, usersDB.DeleteUnverifiedBefore(ctx, time.Time{}, 0, -1))
|
|
|
|
createUser := func(status console.UserStatus, createdAt time.Time) uuid.UUID {
|
|
user, err := usersDB.Insert(ctx, &console.User{
|
|
ID: testrand.UUID(),
|
|
PasswordHash: testrand.Bytes(8),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
result, err := db.Testing().RawDB().ExecContext(ctx,
|
|
"UPDATE users SET created_at = $1, status = $2 WHERE id = $3",
|
|
createdAt, status, user.ID,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
count, err := result.RowsAffected()
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, 1, count)
|
|
|
|
return user.ID
|
|
}
|
|
|
|
oldActive := createUser(console.Active, expiration.Add(-time.Second))
|
|
newUnverified := createUser(console.Inactive, now)
|
|
oldUnverified := createUser(console.Inactive, expiration.Add(-time.Second))
|
|
|
|
require.NoError(t, usersDB.DeleteUnverifiedBefore(ctx, expiration, 0, 1))
|
|
|
|
// Ensure that the old, unverified user record was deleted and the others remain.
|
|
_, err := usersDB.Get(ctx, oldUnverified)
|
|
require.ErrorIs(t, err, sql.ErrNoRows)
|
|
_, err = usersDB.Get(ctx, newUnverified)
|
|
require.NoError(t, err)
|
|
_, err = usersDB.Get(ctx, oldActive)
|
|
require.NoError(t, err)
|
|
})
|
|
}
|