User: email verification completion (#85259)
* TempUser: Include InvitedById in TempUserDTO * Extract email verfication completion flow to service
This commit is contained in:
+3
-79
@@ -4,11 +4,9 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/mail"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
@@ -17,7 +15,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/login"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/team"
|
||||
tempuser "github.com/grafana/grafana/pkg/services/temp_user"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
@@ -275,7 +272,7 @@ func (hs *HTTPServer) handleUpdateUser(ctx context.Context, cmd user.UpdateUserC
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) verifyEmailUpdate(ctx context.Context, email string, field user.UpdateEmailActionType, usr *user.User) response.Response {
|
||||
if err := hs.userVerifier.VerifyEmail(ctx, user.VerifyEmailCommand{
|
||||
if err := hs.userVerifier.Start(ctx, user.StartVerifyEmailCommand{
|
||||
User: *usr,
|
||||
Email: email,
|
||||
Action: field,
|
||||
@@ -295,37 +292,15 @@ func (hs *HTTPServer) verifyEmailUpdate(ctx context.Context, email string, field
|
||||
// Responses:
|
||||
// 302: okResponse
|
||||
func (hs *HTTPServer) UpdateUserEmail(c *contextmodel.ReqContext) response.Response {
|
||||
var err error
|
||||
|
||||
q := c.Req.URL.Query()
|
||||
code, err := url.QueryUnescape(q.Get("code"))
|
||||
code, err := url.QueryUnescape(c.Req.URL.Query().Get("code"))
|
||||
if err != nil || code == "" {
|
||||
return hs.RedirectResponseWithError(c, errors.New("bad request data"))
|
||||
}
|
||||
|
||||
tempUser, err := hs.validateEmailCode(c.Req.Context(), code)
|
||||
if err != nil {
|
||||
if err := hs.userVerifier.Complete(c.Req.Context(), user.CompleteEmailVerifyCommand{Code: code}); err != nil {
|
||||
return hs.RedirectResponseWithError(c, err)
|
||||
}
|
||||
|
||||
cmd, err := hs.updateCmdFromEmailVerification(c.Req.Context(), tempUser)
|
||||
if err != nil {
|
||||
return hs.RedirectResponseWithError(c, err)
|
||||
}
|
||||
|
||||
if err := hs.userService.Update(c.Req.Context(), cmd); err != nil {
|
||||
if errors.Is(err, user.ErrCaseInsensitive) {
|
||||
return hs.RedirectResponseWithError(c, errors.New("update would result in user login conflict"))
|
||||
}
|
||||
return hs.RedirectResponseWithError(c, errors.New("failed to update user"))
|
||||
}
|
||||
|
||||
// Mark temp user as completed
|
||||
updateTmpUserCmd := tempuser.UpdateTempUserStatusCommand{Code: code, Status: tempuser.TmpUserEmailUpdateCompleted}
|
||||
if err := hs.tempUserService.UpdateTempUserStatus(c.Req.Context(), &updateTmpUserCmd); err != nil {
|
||||
return hs.RedirectResponseWithError(c, errors.New("failed to update verification status"))
|
||||
}
|
||||
|
||||
return response.Redirect(hs.Cfg.AppSubURL + "/profile")
|
||||
}
|
||||
|
||||
@@ -694,57 +669,6 @@ func getUserID(c *contextmodel.ReqContext) (int64, *response.NormalResponse) {
|
||||
return userID, nil
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) updateCmdFromEmailVerification(ctx context.Context, tempUser *tempuser.TempUserDTO) (*user.UpdateUserCommand, error) {
|
||||
userQuery := user.GetUserByLoginQuery{LoginOrEmail: tempUser.InvitedByLogin}
|
||||
usr, err := hs.userService.GetByLogin(ctx, &userQuery)
|
||||
if err != nil {
|
||||
if errors.Is(err, user.ErrUserNotFound) {
|
||||
return nil, user.ErrUserNotFound
|
||||
}
|
||||
return nil, errors.New("failed to get user")
|
||||
}
|
||||
|
||||
cmd := &user.UpdateUserCommand{UserID: usr.ID, Email: tempUser.Email}
|
||||
|
||||
switch tempUser.Name {
|
||||
case string(user.EmailUpdateAction):
|
||||
// User updated the email field
|
||||
if _, err := mail.ParseAddress(usr.Login); err == nil {
|
||||
// If username was also an email, we update it to keep it in sync with the email field
|
||||
cmd.Login = tempUser.Email
|
||||
}
|
||||
case string(user.LoginUpdateAction):
|
||||
// User updated the username field with a new email
|
||||
cmd.Login = tempUser.Email
|
||||
default:
|
||||
return nil, errors.New("trying to update email on unknown field")
|
||||
}
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) validateEmailCode(ctx context.Context, code string) (*tempuser.TempUserDTO, error) {
|
||||
tempUserQuery := tempuser.GetTempUserByCodeQuery{Code: code}
|
||||
tempUser, err := hs.tempUserService.GetTempUserByCode(ctx, &tempUserQuery)
|
||||
if err != nil {
|
||||
if errors.Is(err, tempuser.ErrTempUserNotFound) {
|
||||
return nil, errors.New("invalid email verification code")
|
||||
}
|
||||
return nil, errors.New("failed to read temp user")
|
||||
}
|
||||
|
||||
if tempUser.Status != tempuser.TmpUserEmailUpdateStarted {
|
||||
return nil, errors.New("invalid email verification code")
|
||||
}
|
||||
if !tempUser.EmailSent {
|
||||
return nil, errors.New("verification email was not recorded as sent")
|
||||
}
|
||||
if tempUser.EmailSentOn.Add(hs.Cfg.VerificationEmailMaxLifetime).Before(time.Now()) {
|
||||
return nil, errors.New("invalid email verification code")
|
||||
}
|
||||
|
||||
return tempUser, nil
|
||||
}
|
||||
|
||||
// swagger:parameters searchUsers
|
||||
type SearchUsersParams struct {
|
||||
// Limit the maximum number of users to return per page
|
||||
|
||||
@@ -397,7 +397,7 @@ func setupUpdateEmailTests(t *testing.T, cfg *setting.Cfg) (*user.User, *HTTPSer
|
||||
require.NoError(t, err)
|
||||
|
||||
nsMock := notifications.MockNotificationService()
|
||||
verifier := userimpl.ProvideVerifier(userSvc, tempUserService, nsMock)
|
||||
verifier := userimpl.ProvideVerifier(cfg, userSvc, tempUserService, nsMock)
|
||||
|
||||
hs := &HTTPServer{
|
||||
Cfg: cfg,
|
||||
@@ -620,7 +620,7 @@ func TestUser_UpdateEmail(t *testing.T) {
|
||||
hs.tempUserService = tempUserSvc
|
||||
hs.NotificationService = nsMock
|
||||
hs.SecretsService = fakes.NewFakeSecretsService()
|
||||
hs.userVerifier = userimpl.ProvideVerifier(userSvc, tempUserSvc, nsMock)
|
||||
hs.userVerifier = userimpl.ProvideVerifier(settings, userSvc, tempUserSvc, nsMock)
|
||||
// User is internal
|
||||
hs.authInfoService = &authinfotest.FakeService{ExpectedError: user.ErrUserNotFound}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user