satellite/console: add endpoint to get user settings

This change adds an endpoint that gets a user's settings. It will
create a new settings entry if no settings exists. There's also a new
endpoint to change a user's onboarding status.

Issue: https://github.com/storj/storj/issues/5661

Change-Id: I9941bb9d61994af46244003f3ef4fcfe7d36918e
This commit is contained in:
Wilfred Asomani 2023-03-16 11:42:01 +00:00 committed by Storj Robot
parent be567021d9
commit 41bfbbe772
5 changed files with 234 additions and 0 deletions

View File

@ -912,6 +912,54 @@ func (a *Auth) RefreshSession(w http.ResponseWriter, r *http.Request) {
}
}
// GetUserSettings gets a user's settings.
func (a *Auth) GetUserSettings(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
defer mon.Task()(&ctx)(&err)
settings, err := a.service.GetUserSettings(ctx)
if err != nil {
a.serveJSONError(w, err)
return
}
err = json.NewEncoder(w).Encode(settings)
if err != nil {
a.log.Error("could not encode settings", zap.Error(ErrAuthAPI.Wrap(err)))
return
}
}
// SetOnboardingStatus updates a user's onboarding status.
func (a *Auth) SetOnboardingStatus(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
defer mon.Task()(&ctx)(&err)
var updateInfo struct {
OnboardingStart *bool `json:"onboardingStart"`
OnboardingEnd *bool `json:"onboardingEnd"`
OnboardingStep *string `json:"onboardingStep"`
}
err = json.NewDecoder(r.Body).Decode(&updateInfo)
if err != nil {
a.serveJSONError(w, err)
return
}
err = a.service.SetUserSettings(ctx, console.UpsertUserSettingsRequest{
OnboardingStart: updateInfo.OnboardingStart,
OnboardingEnd: updateInfo.OnboardingEnd,
OnboardingStep: updateInfo.OnboardingStep,
})
if err != nil {
a.serveJSONError(w, err)
return
}
}
// serveJSONError writes JSON error to response output stream.
func (a *Auth) serveJSONError(w http.ResponseWriter, err error) {
status := a.getStatusCode(err)

View File

@ -97,6 +97,84 @@ func TestAuth(t *testing.T) {
require.False(test.t, freezestatus.Frozen)
}
{ // Test_UserSettings
testGetSettings := func(expected struct {
SessionDuration *string
OnboardingStart bool
OnboardingEnd bool
OnboardingStep *string
}) {
resp, body := test.request(http.MethodGet, "/auth/account/settings", nil)
var settings struct {
SessionDuration *string
OnboardingStart bool
OnboardingEnd bool
OnboardingStep *string
}
require.Equal(t, http.StatusOK, resp.StatusCode)
require.NoError(test.t, json.Unmarshal([]byte(body), &settings))
require.Equal(test.t, expected.OnboardingStart, settings.OnboardingStart)
require.Equal(test.t, expected.OnboardingEnd, settings.OnboardingEnd)
require.Equal(test.t, expected.OnboardingStep, settings.OnboardingStep)
require.Equal(test.t, expected.SessionDuration, settings.SessionDuration)
}
testGetSettings(struct {
SessionDuration *string
OnboardingStart bool
OnboardingEnd bool
OnboardingStep *string
}{
SessionDuration: nil,
OnboardingStart: false,
OnboardingEnd: false,
OnboardingStep: nil,
})
resp, _ := test.request(http.MethodPatch, "/auth/account/onboarding",
test.toJSON(map[string]interface{}{
"onboardingStart": true,
"onboardingEnd": false,
"onboardingStep": "cli",
}))
require.Equal(t, http.StatusOK, resp.StatusCode)
step := "cli"
testGetSettings(struct {
SessionDuration *string
OnboardingStart bool
OnboardingEnd bool
OnboardingStep *string
}{
SessionDuration: nil,
OnboardingStart: true,
OnboardingEnd: false,
OnboardingStep: &step,
})
resp, _ = test.request(http.MethodPatch, "/auth/account/onboarding",
test.toJSON(map[string]interface{}{
"onboardingStart": nil,
"onboardingEnd": nil,
"onboardingStep": nil,
}))
require.Equal(t, http.StatusOK, resp.StatusCode)
// having passed nil to /auth/account/onboarding shouldn't have changed existing values.
testGetSettings(struct {
SessionDuration *string
OnboardingStart bool
OnboardingEnd bool
OnboardingStep *string
}{
SessionDuration: nil,
OnboardingStart: true,
OnboardingEnd: false,
OnboardingStep: &step,
})
}
{ // Logout
resp, _ := test.request(http.MethodPost, "/auth/logout", nil)
cookie := findCookie(resp, "_tokenKey")

View File

@ -283,6 +283,8 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, oidc
authRouter.Handle("/account/change-email", server.withAuth(http.HandlerFunc(authController.ChangeEmail))).Methods(http.MethodPost)
authRouter.Handle("/account/change-password", server.withAuth(server.userIDRateLimiter.Limit(http.HandlerFunc(authController.ChangePassword)))).Methods(http.MethodPost)
authRouter.Handle("/account/freezestatus", server.withAuth(http.HandlerFunc(authController.IsAccountFrozen))).Methods(http.MethodGet)
authRouter.Handle("/account/settings", server.withAuth(http.HandlerFunc(authController.GetUserSettings))).Methods(http.MethodGet)
authRouter.Handle("/account/onboarding", server.withAuth(http.HandlerFunc(authController.SetOnboardingStatus))).Methods(http.MethodPatch)
authRouter.Handle("/account/delete", server.withAuth(http.HandlerFunc(authController.DeleteAccount))).Methods(http.MethodPost)
authRouter.Handle("/mfa/enable", server.withAuth(http.HandlerFunc(authController.EnableUserMFA))).Methods(http.MethodPost)
authRouter.Handle("/mfa/disable", server.withAuth(http.HandlerFunc(authController.DisableUserMFA))).Methods(http.MethodPost)

View File

@ -3239,3 +3239,47 @@ func (s *Service) VerifyForgotPasswordCaptcha(ctx context.Context, responseToken
}
return true, nil
}
// GetUserSettings fetches a user's settings. It creates default settings if none exists.
func (s *Service) GetUserSettings(ctx context.Context) (settings *UserSettings, err error) {
defer mon.Task()(&ctx)(&err)
user, err := s.getUserAndAuditLog(ctx, "get user settings")
if err != nil {
return nil, Error.Wrap(err)
}
settings, err = s.store.Users().GetSettings(ctx, user.ID)
if err != nil {
if !errs.Is(err, sql.ErrNoRows) {
return nil, Error.Wrap(err)
}
err = s.store.Users().UpsertSettings(ctx, user.ID, UpsertUserSettingsRequest{})
if err != nil {
return nil, Error.Wrap(err)
}
settings, err = s.store.Users().GetSettings(ctx, user.ID)
if err != nil {
return nil, Error.Wrap(err)
}
}
return settings, nil
}
// SetUserSettings updates a user's settings.
func (s *Service) SetUserSettings(ctx context.Context, request UpsertUserSettingsRequest) (err error) {
defer mon.Task()(&ctx)(&err)
user, err := s.getUserAndAuditLog(ctx, "get user settings")
if err != nil {
return Error.Wrap(err)
}
err = s.store.Users().UpsertSettings(ctx, user.ID, request)
if err != nil {
return Error.Wrap(err)
}
return nil
}

View File

@ -8,6 +8,7 @@ import (
"database/sql"
"encoding/json"
"fmt"
"math/rand"
"sort"
"testing"
"time"
@ -1069,6 +1070,67 @@ func TestRefreshSessionToken(t *testing.T) {
})
}
func TestUserSettings(t *testing.T) {
testplanet.Run(t, testplanet.Config{
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 1,
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
sat := planet.Satellites[0]
srv := sat.API.Console.Service
userDB := sat.DB.Console().Users()
user, _, err := srv.GetUserByEmailWithUnverified(ctx, planet.Uplinks[0].User[sat.ID()].Email)
require.NoError(t, err)
userCtx, err := sat.UserContext(ctx, user.ID)
require.NoError(t, err)
_, err = userDB.GetSettings(userCtx, user.ID)
require.Error(t, err)
settings, err := srv.GetUserSettings(userCtx)
require.NoError(t, err)
require.Equal(t, false, settings.OnboardingStart)
require.Equal(t, false, settings.OnboardingEnd)
require.Nil(t, settings.OnboardingStep)
require.Nil(t, settings.SessionDuration)
onboardingBool := true
onboardingStep := "Overview"
sessionDur := time.Duration(rand.Int63()).Round(time.Minute)
sessionDurPtr := &sessionDur
err = srv.SetUserSettings(userCtx, console.UpsertUserSettingsRequest{
SessionDuration: &sessionDurPtr,
OnboardingStart: &onboardingBool,
OnboardingEnd: &onboardingBool,
OnboardingStep: &onboardingStep,
})
require.NoError(t, err)
settings, err = userDB.GetSettings(userCtx, user.ID)
require.NoError(t, err)
require.Equal(t, onboardingBool, settings.OnboardingStart)
require.Equal(t, onboardingBool, settings.OnboardingEnd)
require.Equal(t, &onboardingStep, settings.OnboardingStep)
require.Equal(t, sessionDurPtr, settings.SessionDuration)
// passing nil should not override existing values
err = srv.SetUserSettings(userCtx, console.UpsertUserSettingsRequest{
SessionDuration: nil,
OnboardingStart: nil,
OnboardingEnd: nil,
OnboardingStep: nil,
})
require.NoError(t, err)
settings, err = userDB.GetSettings(userCtx, user.ID)
require.NoError(t, err)
require.Equal(t, onboardingBool, settings.OnboardingStart)
require.Equal(t, onboardingBool, settings.OnboardingEnd)
require.Equal(t, &onboardingStep, settings.OnboardingStep)
require.Equal(t, sessionDurPtr, settings.SessionDuration)
})
}
func TestRESTKeys(t *testing.T) {
testplanet.Run(t, testplanet.Config{
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 1,