storj/satellite/satellitedb/users_test.go
Jeremy Wharton f61230a670 satellite/console/dbcleanup: create console DB cleanup chore
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
2023-05-05 19:11:53 +00:00

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)
})
}