2019-01-24 16:26:36 +00:00
|
|
|
// Copyright (C) 2019 Storj Labs, Inc.
|
2018-11-08 14:19:42 +00:00
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package satellitedb
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2021-07-13 18:21:16 +01:00
|
|
|
"encoding/json"
|
2019-09-10 15:00:33 +01:00
|
|
|
"strings"
|
2018-11-08 14:19:42 +00:00
|
|
|
|
|
|
|
"github.com/zeebo/errs"
|
|
|
|
|
2021-11-01 15:27:32 +00:00
|
|
|
"storj.io/common/memory"
|
2020-03-30 10:08:50 +01:00
|
|
|
"storj.io/common/uuid"
|
2019-01-15 13:03:24 +00:00
|
|
|
"storj.io/storj/satellite/console"
|
2020-01-15 02:29:51 +00:00
|
|
|
"storj.io/storj/satellite/satellitedb/dbx"
|
2018-11-08 14:19:42 +00:00
|
|
|
)
|
|
|
|
|
2019-11-04 14:37:39 +00:00
|
|
|
// ensures that users implements console.Users.
|
|
|
|
var _ console.Users = (*users)(nil)
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// implementation of Users interface repository using spacemonkeygo/dbx orm.
|
2018-11-08 14:19:42 +00:00
|
|
|
type users struct {
|
2018-12-10 12:29:01 +00:00
|
|
|
db dbx.Methods
|
2018-11-08 14:19:42 +00:00
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// Get is a method for querying user from the database by id.
|
2019-06-04 12:55:38 +01:00
|
|
|
func (users *users) Get(ctx context.Context, id uuid.UUID) (_ *console.User, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2018-11-12 09:14:16 +00:00
|
|
|
user, err := users.db.Get_User_By_Id(ctx, dbx.User_Id(id[:]))
|
2021-11-18 18:54:39 +00:00
|
|
|
|
2018-11-08 14:19:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-06-04 12:55:38 +01:00
|
|
|
return userFromDBX(ctx, user)
|
2018-11-08 14:19:42 +00:00
|
|
|
}
|
|
|
|
|
2021-11-18 18:54:39 +00:00
|
|
|
// GetByEmailWithUnverified is a method for querying users by email from the database.
|
|
|
|
func (users *users) GetByEmailWithUnverified(ctx context.Context, email string) (verified *console.User, unverified []console.User, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
usersDbx, err := users.db.All_User_By_NormalizedEmail(ctx, dbx.User_NormalizedEmail(normalizeEmail(email)))
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var errors errs.Group
|
|
|
|
for _, userDbx := range usersDbx {
|
|
|
|
u, err := userFromDBX(ctx, userDbx)
|
|
|
|
if err != nil {
|
|
|
|
errors.Add(err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if u.Status == console.Active {
|
|
|
|
verified = u
|
|
|
|
} else {
|
|
|
|
unverified = append(unverified, *u)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return verified, unverified, errors.Err()
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetByEmail is a method for querying user by verified email from the database.
|
2019-06-04 12:55:38 +01:00
|
|
|
func (users *users) GetByEmail(ctx context.Context, email string) (_ *console.User, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2019-09-10 15:00:33 +01:00
|
|
|
user, err := users.db.Get_User_By_NormalizedEmail_And_Status_Not_Number(ctx, dbx.User_NormalizedEmail(normalizeEmail(email)))
|
2018-11-08 14:19:42 +00:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-06-04 12:55:38 +01:00
|
|
|
return userFromDBX(ctx, user)
|
2018-11-08 14:19:42 +00:00
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// Insert is a method for inserting user into the database.
|
2019-06-04 12:55:38 +01:00
|
|
|
func (users *users) Insert(ctx context.Context, user *console.User) (_ *console.User, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2019-11-20 19:16:27 +00:00
|
|
|
|
|
|
|
if user.ID.IsZero() {
|
|
|
|
return nil, errs.New("user id is not set")
|
2018-11-13 08:27:42 +00:00
|
|
|
}
|
|
|
|
|
2019-07-17 21:53:14 +01:00
|
|
|
optional := dbx.User_Create_Fields{
|
2021-10-12 14:54:05 +01:00
|
|
|
ShortName: dbx.User_ShortName(user.ShortName),
|
|
|
|
IsProfessional: dbx.User_IsProfessional(user.IsProfessional),
|
|
|
|
SignupPromoCode: dbx.User_SignupPromoCode(user.SignupPromoCode),
|
2019-07-17 21:53:14 +01:00
|
|
|
}
|
|
|
|
if !user.PartnerID.IsZero() {
|
|
|
|
optional.PartnerId = dbx.User_PartnerId(user.PartnerID[:])
|
|
|
|
}
|
2021-09-23 00:38:18 +01:00
|
|
|
if user.UserAgent != nil {
|
|
|
|
optional.UserAgent = dbx.User_UserAgent(user.UserAgent)
|
|
|
|
}
|
2020-07-15 17:49:37 +01:00
|
|
|
if user.ProjectLimit != 0 {
|
|
|
|
optional.ProjectLimit = dbx.User_ProjectLimit(user.ProjectLimit)
|
|
|
|
}
|
2021-11-01 15:27:32 +00:00
|
|
|
if user.ProjectStorageLimit != 0 {
|
|
|
|
optional.ProjectStorageLimit = dbx.User_ProjectStorageLimit(user.ProjectStorageLimit)
|
|
|
|
}
|
|
|
|
if user.ProjectBandwidthLimit != 0 {
|
|
|
|
optional.ProjectBandwidthLimit = dbx.User_ProjectBandwidthLimit(user.ProjectBandwidthLimit)
|
|
|
|
}
|
2021-12-06 19:06:50 +00:00
|
|
|
if user.ProjectSegmentLimit != 0 {
|
|
|
|
optional.ProjectSegmentLimit = dbx.User_ProjectSegmentLimit(user.ProjectSegmentLimit)
|
|
|
|
}
|
2021-02-04 15:00:15 +00:00
|
|
|
if user.IsProfessional {
|
|
|
|
optional.Position = dbx.User_Position(user.Position)
|
|
|
|
optional.CompanyName = dbx.User_CompanyName(user.CompanyName)
|
|
|
|
optional.WorkingOn = dbx.User_WorkingOn(user.WorkingOn)
|
2021-02-10 15:55:38 +00:00
|
|
|
optional.EmployeeCount = dbx.User_EmployeeCount(user.EmployeeCount)
|
2021-04-27 19:40:03 +01:00
|
|
|
optional.HaveSalesContact = dbx.User_HaveSalesContact(user.HaveSalesContact)
|
2021-02-04 15:00:15 +00:00
|
|
|
}
|
2019-07-17 21:53:14 +01:00
|
|
|
|
2018-11-09 12:05:24 +00:00
|
|
|
createdUser, err := users.db.Create_User(ctx,
|
2019-11-20 19:16:27 +00:00
|
|
|
dbx.User_Id(user.ID[:]),
|
2019-02-11 10:33:56 +00:00
|
|
|
dbx.User_Email(user.Email),
|
2019-09-10 15:00:33 +01:00
|
|
|
dbx.User_NormalizedEmail(normalizeEmail(user.Email)),
|
2019-06-06 17:07:14 +01:00
|
|
|
dbx.User_FullName(user.FullName),
|
2019-01-30 15:04:40 +00:00
|
|
|
dbx.User_PasswordHash(user.PasswordHash),
|
2019-07-17 21:53:14 +01:00
|
|
|
optional,
|
2019-01-30 15:04:40 +00:00
|
|
|
)
|
2018-11-08 14:19:42 +00:00
|
|
|
|
2018-11-09 12:05:24 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-06-04 12:55:38 +01:00
|
|
|
return userFromDBX(ctx, createdUser)
|
2018-11-08 14:19:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Delete is a method for deleting user by Id from the database.
|
2019-06-04 12:55:38 +01:00
|
|
|
func (users *users) Delete(ctx context.Context, id uuid.UUID) (err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
_, err = users.db.Delete_User_By_Id(ctx, dbx.User_Id(id[:]))
|
2018-11-08 14:19:42 +00:00
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// Update is a method for updating user entity.
|
2019-06-04 12:55:38 +01:00
|
|
|
func (users *users) Update(ctx context.Context, user *console.User) (err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2019-07-17 21:53:14 +01:00
|
|
|
|
2021-07-13 18:21:16 +01:00
|
|
|
updateFields, err := toUpdateUser(user)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-06-04 12:55:38 +01:00
|
|
|
_, err = users.db.Update_User_By_Id(
|
2018-11-28 10:31:15 +00:00
|
|
|
ctx,
|
2018-11-12 09:14:16 +00:00
|
|
|
dbx.User_Id(user.ID[:]),
|
2021-07-13 18:21:16 +01:00
|
|
|
*updateFields,
|
2018-11-28 10:31:15 +00:00
|
|
|
)
|
2018-11-08 14:19:42 +00:00
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-07-01 00:13:45 +01:00
|
|
|
// UpdatePaidTier sets whether the user is in the paid tier.
|
2022-02-28 21:37:46 +00:00
|
|
|
func (users *users) UpdatePaidTier(ctx context.Context, id uuid.UUID, paidTier bool, projectBandwidthLimit, projectStorageLimit memory.Size, projectSegmentLimit int64, projectLimit int) (err error) {
|
2021-07-01 00:13:45 +01:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
_, err = users.db.Update_User_By_Id(
|
|
|
|
ctx,
|
|
|
|
dbx.User_Id(id[:]),
|
|
|
|
dbx.User_Update_Fields{
|
2021-11-01 15:27:32 +00:00
|
|
|
PaidTier: dbx.User_PaidTier(paidTier),
|
2022-02-28 21:37:46 +00:00
|
|
|
ProjectLimit: dbx.User_ProjectLimit(projectLimit),
|
2021-11-01 15:27:32 +00:00
|
|
|
ProjectBandwidthLimit: dbx.User_ProjectBandwidthLimit(projectBandwidthLimit.Int64()),
|
|
|
|
ProjectStorageLimit: dbx.User_ProjectStorageLimit(projectStorageLimit.Int64()),
|
2021-12-06 19:06:50 +00:00
|
|
|
ProjectSegmentLimit: dbx.User_ProjectSegmentLimit(projectSegmentLimit),
|
2021-07-01 00:13:45 +01:00
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// GetProjectLimit is a method to get the users project limit.
|
2020-07-15 16:14:09 +01:00
|
|
|
func (users *users) GetProjectLimit(ctx context.Context, id uuid.UUID) (limit int, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
row, err := users.db.Get_User_ProjectLimit_By_Id(ctx, dbx.User_Id(id[:]))
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
return row.ProjectLimit, nil
|
|
|
|
}
|
|
|
|
|
2021-11-01 15:27:32 +00:00
|
|
|
// GetUserProjectLimits is a method to get the users storage and bandwidth limits for new projects.
|
|
|
|
func (users *users) GetUserProjectLimits(ctx context.Context, id uuid.UUID) (limits *console.ProjectLimits, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
2021-12-06 19:06:50 +00:00
|
|
|
row, err := users.db.Get_User_ProjectStorageLimit_User_ProjectBandwidthLimit_User_ProjectSegmentLimit_By_Id(ctx, dbx.User_Id(id[:]))
|
2021-11-01 15:27:32 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return limitsFromDBX(ctx, row)
|
|
|
|
}
|
|
|
|
|
2022-02-04 17:31:24 +00:00
|
|
|
func (users *users) GetUserPaidTier(ctx context.Context, id uuid.UUID) (isPaid bool, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
row, err := users.db.Get_User_PaidTier_By_Id(ctx, dbx.User_Id(id[:]))
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
return row.PaidTier, nil
|
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// toUpdateUser creates dbx.User_Update_Fields with only non-empty fields as updatable.
|
2021-07-13 18:21:16 +01:00
|
|
|
func toUpdateUser(user *console.User) (*dbx.User_Update_Fields, error) {
|
2018-11-28 10:31:15 +00:00
|
|
|
update := dbx.User_Update_Fields{
|
2021-11-01 15:27:32 +00:00
|
|
|
FullName: dbx.User_FullName(user.FullName),
|
|
|
|
ShortName: dbx.User_ShortName(user.ShortName),
|
|
|
|
Email: dbx.User_Email(user.Email),
|
|
|
|
NormalizedEmail: dbx.User_NormalizedEmail(normalizeEmail(user.Email)),
|
|
|
|
Status: dbx.User_Status(int(user.Status)),
|
|
|
|
ProjectLimit: dbx.User_ProjectLimit(user.ProjectLimit),
|
|
|
|
ProjectStorageLimit: dbx.User_ProjectStorageLimit(user.ProjectStorageLimit),
|
|
|
|
ProjectBandwidthLimit: dbx.User_ProjectBandwidthLimit(user.ProjectBandwidthLimit),
|
2021-12-06 19:06:50 +00:00
|
|
|
ProjectSegmentLimit: dbx.User_ProjectSegmentLimit(user.ProjectSegmentLimit),
|
2021-11-01 15:27:32 +00:00
|
|
|
PaidTier: dbx.User_PaidTier(user.PaidTier),
|
|
|
|
MfaEnabled: dbx.User_MfaEnabled(user.MFAEnabled),
|
2021-07-13 18:21:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
recoveryBytes, err := json.Marshal(user.MFARecoveryCodes)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2018-11-28 10:31:15 +00:00
|
|
|
}
|
2021-07-13 18:21:16 +01:00
|
|
|
update.MfaRecoveryCodes = dbx.User_MfaRecoveryCodes(string(recoveryBytes))
|
|
|
|
update.MfaSecretKey = dbx.User_MfaSecretKey(user.MFASecretKey)
|
2021-12-22 15:43:37 +00:00
|
|
|
update.LastVerificationReminder = dbx.User_LastVerificationReminder(user.LastVerificationReminder)
|
2018-11-28 10:31:15 +00:00
|
|
|
|
|
|
|
// extra password check to update only calculated hash from service
|
2018-12-10 15:57:06 +00:00
|
|
|
if len(user.PasswordHash) != 0 {
|
2018-11-28 10:31:15 +00:00
|
|
|
update.PasswordHash = dbx.User_PasswordHash(user.PasswordHash)
|
|
|
|
}
|
|
|
|
|
2021-07-13 18:21:16 +01:00
|
|
|
return &update, nil
|
2018-11-28 10:31:15 +00:00
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// userFromDBX is used for creating User entity from autogenerated dbx.User struct.
|
2019-06-04 12:55:38 +01:00
|
|
|
func userFromDBX(ctx context.Context, user *dbx.User) (_ *console.User, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2018-11-08 14:19:42 +00:00
|
|
|
if user == nil {
|
|
|
|
return nil, errs.New("user parameter is nil")
|
|
|
|
}
|
|
|
|
|
2020-03-31 17:49:16 +01:00
|
|
|
id, err := uuid.FromBytes(user.Id)
|
2018-11-08 14:19:42 +00:00
|
|
|
if err != nil {
|
2018-11-12 09:14:16 +00:00
|
|
|
return nil, err
|
2018-11-08 14:19:42 +00:00
|
|
|
}
|
|
|
|
|
2021-07-13 18:21:16 +01:00
|
|
|
var recoveryCodes []string
|
|
|
|
if user.MfaRecoveryCodes != nil {
|
|
|
|
err = json.Unmarshal([]byte(*user.MfaRecoveryCodes), &recoveryCodes)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-30 15:04:40 +00:00
|
|
|
result := console.User{
|
2021-11-01 15:27:32 +00:00
|
|
|
ID: id,
|
|
|
|
FullName: user.FullName,
|
|
|
|
Email: user.Email,
|
|
|
|
PasswordHash: user.PasswordHash,
|
|
|
|
Status: console.UserStatus(user.Status),
|
|
|
|
CreatedAt: user.CreatedAt,
|
|
|
|
ProjectLimit: user.ProjectLimit,
|
|
|
|
ProjectBandwidthLimit: user.ProjectBandwidthLimit,
|
|
|
|
ProjectStorageLimit: user.ProjectStorageLimit,
|
2021-12-06 19:06:50 +00:00
|
|
|
ProjectSegmentLimit: user.ProjectSegmentLimit,
|
2021-11-01 15:27:32 +00:00
|
|
|
PaidTier: user.PaidTier,
|
|
|
|
IsProfessional: user.IsProfessional,
|
|
|
|
HaveSalesContact: user.HaveSalesContact,
|
|
|
|
MFAEnabled: user.MfaEnabled,
|
2019-01-30 15:04:40 +00:00
|
|
|
}
|
|
|
|
|
2019-07-17 21:53:14 +01:00
|
|
|
if user.PartnerId != nil {
|
2020-03-31 17:49:16 +01:00
|
|
|
result.PartnerID, err = uuid.FromBytes(user.PartnerId)
|
2019-07-17 21:53:14 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-23 00:38:18 +01:00
|
|
|
if user.UserAgent != nil {
|
|
|
|
result.UserAgent = user.UserAgent
|
|
|
|
}
|
|
|
|
|
2019-03-27 12:33:32 +00:00
|
|
|
if user.ShortName != nil {
|
|
|
|
result.ShortName = *user.ShortName
|
|
|
|
}
|
|
|
|
|
2021-02-04 15:00:15 +00:00
|
|
|
if user.Position != nil {
|
|
|
|
result.Position = *user.Position
|
|
|
|
}
|
|
|
|
|
|
|
|
if user.CompanyName != nil {
|
|
|
|
result.CompanyName = *user.CompanyName
|
|
|
|
}
|
|
|
|
|
|
|
|
if user.WorkingOn != nil {
|
|
|
|
result.WorkingOn = *user.WorkingOn
|
|
|
|
}
|
|
|
|
|
2021-02-10 15:55:38 +00:00
|
|
|
if user.EmployeeCount != nil {
|
|
|
|
result.EmployeeCount = *user.EmployeeCount
|
|
|
|
}
|
|
|
|
|
2021-07-13 18:21:16 +01:00
|
|
|
if user.MfaSecretKey != nil {
|
|
|
|
result.MFASecretKey = *user.MfaSecretKey
|
|
|
|
}
|
|
|
|
|
|
|
|
if user.MfaRecoveryCodes != nil {
|
|
|
|
result.MFARecoveryCodes = recoveryCodes
|
|
|
|
}
|
|
|
|
|
2021-10-12 14:54:05 +01:00
|
|
|
if user.SignupPromoCode != nil {
|
|
|
|
result.SignupPromoCode = *user.SignupPromoCode
|
|
|
|
}
|
|
|
|
|
2021-12-22 15:43:37 +00:00
|
|
|
if user.LastVerificationReminder != nil {
|
|
|
|
result.LastVerificationReminder = *user.LastVerificationReminder
|
|
|
|
}
|
|
|
|
|
2019-01-30 15:04:40 +00:00
|
|
|
return &result, nil
|
2018-11-08 14:19:42 +00:00
|
|
|
}
|
2019-09-10 15:00:33 +01:00
|
|
|
|
2021-11-01 15:27:32 +00:00
|
|
|
// limitsFromDBX is used for creating user project limits entity from autogenerated dbx.User struct.
|
2021-12-06 19:06:50 +00:00
|
|
|
func limitsFromDBX(ctx context.Context, limits *dbx.ProjectStorageLimit_ProjectBandwidthLimit_ProjectSegmentLimit_Row) (_ *console.ProjectLimits, err error) {
|
2021-11-01 15:27:32 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
if limits == nil {
|
|
|
|
return nil, errs.New("user parameter is nil")
|
|
|
|
}
|
|
|
|
|
|
|
|
result := console.ProjectLimits{
|
|
|
|
ProjectBandwidthLimit: memory.Size(limits.ProjectBandwidthLimit),
|
|
|
|
ProjectStorageLimit: memory.Size(limits.ProjectStorageLimit),
|
2021-12-06 19:06:50 +00:00
|
|
|
ProjectSegmentLimit: limits.ProjectSegmentLimit,
|
2021-11-01 15:27:32 +00:00
|
|
|
}
|
|
|
|
return &result, nil
|
|
|
|
}
|
|
|
|
|
2019-09-10 15:00:33 +01:00
|
|
|
func normalizeEmail(email string) string {
|
|
|
|
return strings.ToUpper(email)
|
|
|
|
}
|