satellite/{db,console}: support v2 app account set up

This change modifies the register endpoint handler to not require name
for signups from the v2 app and adds a new endpoint for completing
account information (e.g. name). This is to support the new signup and
account setup flow of the v2 app.

Issue: #6470

Change-Id: I256e1c804fcdbc8ce05aa82d6bc4b0263f55daa5
This commit is contained in:
Wilfred Asomani 2023-11-08 15:24:48 +00:00 committed by Storj Robot
parent 1ba314428f
commit f749b8ff51
7 changed files with 120 additions and 4 deletions

View File

@ -52,7 +52,7 @@ func (server *Server) addUser(w http.ResponseWriter, r *http.Request) {
SignupPromoCode: input.SignupPromoCode, SignupPromoCode: input.SignupPromoCode,
} }
err = user.IsValid() err = user.IsValid(false)
if err != nil { if err != nil {
sendJSONError(w, "user data is not valid", sendJSONError(w, "user data is not valid",
err.Error(), http.StatusBadRequest) err.Error(), http.StatusBadRequest)

View File

@ -331,6 +331,8 @@ func (a *Auth) Register(w http.ResponseWriter, r *http.Request) {
SignupPromoCode: registerData.SignupPromoCode, SignupPromoCode: registerData.SignupPromoCode,
ActivationCode: code, ActivationCode: code,
SignupId: requestID, SignupId: requestID,
// signup from the v2 app doesn't require name.
AllowNoName: strings.Contains(r.Referer(), "v2"),
}, },
secret, secret,
) )
@ -579,6 +581,25 @@ func (a *Auth) UpdateAccount(w http.ResponseWriter, r *http.Request) {
} }
} }
// SetupAccount updates user's full name and short name.
func (a *Auth) SetupAccount(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
defer mon.Task()(&ctx)(&err)
var updatedInfo console.SetUpAccountRequest
err = json.NewDecoder(r.Body).Decode(&updatedInfo)
if err != nil {
a.serveJSONError(ctx, w, err)
return
}
if err = a.service.SetupAccount(ctx, updatedInfo); err != nil {
a.serveJSONError(ctx, w, err)
}
}
// GetAccount gets authorized user and take it's params. // GetAccount gets authorized user and take it's params.
func (a *Auth) GetAccount(w http.ResponseWriter, r *http.Request) { func (a *Auth) GetAccount(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()

View File

@ -305,6 +305,7 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, oidc
authRouter.Use(server.withCORS) authRouter.Use(server.withCORS)
authRouter.Handle("/account", server.withAuth(http.HandlerFunc(authController.GetAccount))).Methods(http.MethodGet, http.MethodOptions) authRouter.Handle("/account", server.withAuth(http.HandlerFunc(authController.GetAccount))).Methods(http.MethodGet, http.MethodOptions)
authRouter.Handle("/account", server.withAuth(http.HandlerFunc(authController.UpdateAccount))).Methods(http.MethodPatch, http.MethodOptions) authRouter.Handle("/account", server.withAuth(http.HandlerFunc(authController.UpdateAccount))).Methods(http.MethodPatch, http.MethodOptions)
authRouter.Handle("/account/setup", server.withAuth(http.HandlerFunc(authController.SetupAccount))).Methods(http.MethodPatch, http.MethodOptions)
authRouter.Handle("/account/change-email", server.withAuth(http.HandlerFunc(authController.ChangeEmail))).Methods(http.MethodPost, http.MethodOptions) authRouter.Handle("/account/change-email", server.withAuth(http.HandlerFunc(authController.ChangeEmail))).Methods(http.MethodPost, http.MethodOptions)
authRouter.Handle("/account/change-password", server.withAuth(server.userIDRateLimiter.Limit(http.HandlerFunc(authController.ChangePassword)))).Methods(http.MethodPost, http.MethodOptions) authRouter.Handle("/account/change-password", server.withAuth(server.userIDRateLimiter.Limit(http.HandlerFunc(authController.ChangePassword)))).Methods(http.MethodPost, http.MethodOptions)
authRouter.Handle("/account/freezestatus", server.withAuth(http.HandlerFunc(authController.GetFreezeStatus))).Methods(http.MethodGet, http.MethodOptions) authRouter.Handle("/account/freezestatus", server.withAuth(http.HandlerFunc(authController.GetFreezeStatus))).Methods(http.MethodGet, http.MethodOptions)

View File

@ -806,7 +806,7 @@ func (s *Service) CreateUser(ctx context.Context, user CreateUser, tokenSecret R
captchaScore = score captchaScore = score
} }
if err := user.IsValid(); err != nil { if err := user.IsValid(user.AllowNoName); err != nil {
// NOTE: error is already wrapped with an appropriated class. // NOTE: error is already wrapped with an appropriated class.
return nil, err return nil, err
} }
@ -1492,6 +1492,36 @@ func (s *Service) UpdateAccount(ctx context.Context, fullName string, shortName
return nil return nil
} }
// SetupAccount completes User's information.
func (s *Service) SetupAccount(ctx context.Context, requestData SetUpAccountRequest) (err error) {
defer mon.Task()(&ctx)(&err)
user, err := s.getUserAndAuditLog(ctx, "update account")
if err != nil {
return Error.Wrap(err)
}
// validate fullName
err = ValidateFullName(requestData.FullName)
if err != nil {
return ErrValidation.Wrap(err)
}
err = s.store.Users().Update(ctx, user.ID, UpdateUserRequest{
FullName: &requestData.FullName,
IsProfessional: &requestData.IsProfessional,
Position: requestData.Position,
CompanyName: requestData.CompanyName,
EmployeeCount: requestData.EmployeeCount,
})
if err != nil {
return Error.Wrap(err)
}
// TODO: track setup account
return nil
}
// ChangeEmail updates email for a given user. // ChangeEmail updates email for a given user.
func (s *Service) ChangeEmail(ctx context.Context, newEmail string) (err error) { func (s *Service) ChangeEmail(ctx context.Context, newEmail string) (err error) {
defer mon.Task()(&ctx)(&err) defer mon.Task()(&ctx)(&err)

View File

@ -115,18 +115,24 @@ type CreateUser struct {
SignupPromoCode string `json:"signupPromoCode"` SignupPromoCode string `json:"signupPromoCode"`
ActivationCode string `json:"-"` ActivationCode string `json:"-"`
SignupId string `json:"-"` SignupId string `json:"-"`
AllowNoName bool `json:"-"`
} }
// IsValid checks CreateUser validity and returns error describing whats wrong. // IsValid checks CreateUser validity and returns error describing whats wrong.
// The returned error has the class ErrValiation. // The returned error has the class ErrValiation.
func (user *CreateUser) IsValid() error { func (user *CreateUser) IsValid(allowNoName bool) error {
errgrp := errs.Group{} errgrp := errs.Group{}
errgrp.Add( errgrp.Add(
ValidateFullName(user.FullName),
ValidateNewPassword(user.Password), ValidateNewPassword(user.Password),
) )
if !allowNoName {
errgrp.Add(
ValidateFullName(user.FullName),
)
}
// validate email // validate email
_, err := mail.ParseAddress(user.Email) _, err := mail.ParseAddress(user.Email)
errgrp.Add(err) errgrp.Add(err)
@ -283,6 +289,12 @@ type UpdateUserRequest struct {
FullName *string FullName *string
ShortName **string ShortName **string
Position *string
CompanyName *string
WorkingOn *string
IsProfessional *bool
EmployeeCount *string
Email *string Email *string
PasswordHash []byte PasswordHash []byte
@ -328,3 +340,13 @@ type UpsertUserSettingsRequest struct {
PassphrasePrompt *bool PassphrasePrompt *bool
OnboardingStep *string OnboardingStep *string
} }
// SetUpAccountRequest holds data for completing account setup.
type SetUpAccountRequest struct {
FullName string `json:"fullName"`
IsProfessional bool `json:"isProfessional"`
Position *string `json:"position"`
CompanyName *string `json:"companyName"`
EmployeeCount *string `json:"employeeCount"`
StorageNeeds *string `json:"storageNeeds"`
}

View File

@ -630,6 +630,19 @@ func toUpdateUser(request console.UpdateUserRequest) (*dbx.User_Update_Fields, e
update.SignupId = dbx.User_SignupId(*request.SignupId) update.SignupId = dbx.User_SignupId(*request.SignupId)
} }
if request.IsProfessional != nil {
update.IsProfessional = dbx.User_IsProfessional(*request.IsProfessional)
}
if request.Position != nil {
update.Position = dbx.User_Position(*request.Position)
}
if request.CompanyName != nil {
update.CompanyName = dbx.User_CompanyName(*request.CompanyName)
}
if request.EmployeeCount != nil {
update.EmployeeCount = dbx.User_EmployeeCount(*request.EmployeeCount)
}
return &update, nil return &update, nil
} }

View File

@ -88,6 +88,11 @@ func TestUpdateUser(t *testing.T) {
FailedLoginCount: 1, FailedLoginCount: 1,
LoginLockoutExpiration: time.Now().Truncate(time.Second), LoginLockoutExpiration: time.Now().Truncate(time.Second),
DefaultPlacement: 13, DefaultPlacement: 13,
IsProfessional: true,
Position: "Engineer",
CompanyName: "Storj",
EmployeeCount: "1-200",
} }
require.NotEqual(t, u.FullName, newInfo.FullName) require.NotEqual(t, u.FullName, newInfo.FullName)
@ -104,6 +109,10 @@ func TestUpdateUser(t *testing.T) {
require.NotEqual(t, u.FailedLoginCount, newInfo.FailedLoginCount) require.NotEqual(t, u.FailedLoginCount, newInfo.FailedLoginCount)
require.NotEqual(t, u.LoginLockoutExpiration, newInfo.LoginLockoutExpiration) require.NotEqual(t, u.LoginLockoutExpiration, newInfo.LoginLockoutExpiration)
require.NotEqual(t, u.DefaultPlacement, newInfo.DefaultPlacement) require.NotEqual(t, u.DefaultPlacement, newInfo.DefaultPlacement)
require.NotEqual(t, u.IsProfessional, newInfo.IsProfessional)
require.NotEqual(t, u.Position, newInfo.Position)
require.NotEqual(t, u.CompanyName, newInfo.CompanyName)
require.NotEqual(t, u.EmployeeCount, newInfo.EmployeeCount)
// update just fullname // update just fullname
updateReq := console.UpdateUserRequest{ updateReq := console.UpdateUserRequest{
@ -304,6 +313,26 @@ func TestUpdateUser(t *testing.T) {
u.DefaultPlacement = newInfo.DefaultPlacement u.DefaultPlacement = newInfo.DefaultPlacement
require.Equal(t, u, updatedUser) require.Equal(t, u, updatedUser)
// update professional info
updateReq = console.UpdateUserRequest{
IsProfessional: &newInfo.IsProfessional,
Position: &newInfo.Position,
CompanyName: &newInfo.CompanyName,
EmployeeCount: &newInfo.EmployeeCount,
}
err = users.Update(ctx, id, updateReq)
require.NoError(t, err)
updatedUser, err = users.Get(ctx, id)
require.NoError(t, err)
u.IsProfessional = newInfo.IsProfessional
u.Position = newInfo.Position
u.CompanyName = newInfo.CompanyName
u.EmployeeCount = newInfo.EmployeeCount
require.Equal(t, u, updatedUser)
}) })
} }