From 26d3362eca2e83c98e28b04f206d739bf1c2e708 Mon Sep 17 00:00:00 2001 From: Daniel Schalla Date: Thu, 21 Feb 2019 00:48:28 +0100 Subject: [PATCH] Invalidate password recovery tokens on eMail change (#10302) --- app/user.go | 36 ++++++++++++++++++++++++++++++++---- app/user_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/app/user.go b/app/user.go index 96493cb4d0..df442779e4 100644 --- a/app/user.go +++ b/app/user.go @@ -1161,11 +1161,25 @@ func (a *App) ResetPasswordFromToken(userSuppliedTokenString, newPassword string return model.NewAppError("resetPassword", "api.user.reset_password.link_expired.app_error", nil, "", http.StatusBadRequest) } - user, err := a.GetUser(token.Extra) + tokenData := struct { + UserId string + Email string + }{} + + err2 := json.Unmarshal([]byte(token.Extra), &tokenData) + if err2 != nil { + return model.NewAppError("resetPassword", "api.user.reset_password.token_parse.error", nil, "", http.StatusInternalServerError) + } + + user, err := a.GetUser(tokenData.UserId) if err != nil { return err } + if user.Email != tokenData.Email { + return model.NewAppError("resetPassword", "api.user.reset_password.link_expired.app_error", nil, "", http.StatusBadRequest) + } + if user.IsSSOUser() { return model.NewAppError("ResetPasswordFromCode", "api.user.reset_password.sso.app_error", nil, "userId="+user.Id, http.StatusBadRequest) } @@ -1193,7 +1207,7 @@ func (a *App) SendPasswordReset(email string, siteURL string) (bool, *model.AppE return false, model.NewAppError("SendPasswordReset", "api.user.send_password_reset.sso.app_error", nil, "userId="+user.Id, http.StatusBadRequest) } - token, err := a.CreatePasswordRecoveryToken(user.Id) + token, err := a.CreatePasswordRecoveryToken(user.Id, user.Email) if err != nil { return false, err } @@ -1201,8 +1215,22 @@ func (a *App) SendPasswordReset(email string, siteURL string) (bool, *model.AppE return a.SendPasswordResetEmail(user.Email, token, user.Locale, siteURL) } -func (a *App) CreatePasswordRecoveryToken(userId string) (*model.Token, *model.AppError) { - token := model.NewToken(TOKEN_TYPE_PASSWORD_RECOVERY, userId) +func (a *App) CreatePasswordRecoveryToken(userId, email string) (*model.Token, *model.AppError) { + + tokenExtra := struct { + UserId string + Email string + }{ + userId, + email, + } + jsonData, err := json.Marshal(tokenExtra) + + if err != nil { + return nil, model.NewAppError("CreatePasswordRecoveryToken", "api.user.create_password_token.error", nil, "", http.StatusInternalServerError) + } + + token := model.NewToken(TOKEN_TYPE_PASSWORD_RECOVERY, string(jsonData)) if result := <-a.Srv.Store.Token().Save(token); result.Err != nil { return nil, result.Err diff --git a/app/user_test.go b/app/user_test.go index 5527bec12c..03061e79ab 100644 --- a/app/user_test.go +++ b/app/user_test.go @@ -622,3 +622,41 @@ func TestPermanentDeleteUser(t *testing.T) { t.Fatal("GetFileInfo after DeleteUser is nil") } } + +func TestPasswordRecovery(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + + token, err := th.App.CreatePasswordRecoveryToken(th.BasicUser.Id, th.BasicUser.Email) + assert.Nil(t, err) + + tokenData := struct { + UserId string + Email string + }{} + + err2 := json.Unmarshal([]byte(token.Extra), &tokenData) + assert.Nil(t, err2) + assert.Equal(t, th.BasicUser.Id, tokenData.UserId) + assert.Equal(t, th.BasicUser.Email, tokenData.Email) + + // Password token with same eMail as during creation + err = th.App.ResetPasswordFromToken(token.Token, "abcdefgh") + assert.Nil(t, err) + + // Password token with modified eMail after creation + token, err = th.App.CreatePasswordRecoveryToken(th.BasicUser.Id, th.BasicUser.Email) + assert.Nil(t, err) + + th.App.UpdateConfig(func (c *model.Config){ + *c.EmailSettings.RequireEmailVerification = false + }) + + th.BasicUser.Email = th.MakeEmail() + _, err = th.App.UpdateUser(th.BasicUser, false) + assert.Nil(t, err) + + err = th.App.ResetPasswordFromToken(token.Token, "abcdefgh") + assert.NotNil(t, err) +} +