Creating common token store and moving email invites and verification to it (#6213)
Этот коммит содержится в:
коммит произвёл
Joram Wilander
родитель
0e007e344b
Коммит
9a87bb3af6
20
app/email.go
20
app/email.go
@@ -33,10 +33,10 @@ func SendChangeUsernameEmail(oldUsername, newUsername, email, locale, siteURL st
|
||||
return nil
|
||||
}
|
||||
|
||||
func SendEmailChangeVerifyEmail(userId, newUserEmail, locale, siteURL string) *model.AppError {
|
||||
func SendEmailChangeVerifyEmail(newUserEmail, locale, siteURL, token string) *model.AppError {
|
||||
T := utils.GetUserTranslations(locale)
|
||||
|
||||
link := fmt.Sprintf("%s/do_verify_email?uid=%s&hid=%s&email=%s", siteURL, userId, model.HashPassword(userId+utils.Cfg.EmailSettings.InviteSalt), url.QueryEscape(newUserEmail))
|
||||
link := fmt.Sprintf("%s/do_verify_email?token=%s&email=%s", siteURL, token, url.QueryEscape(newUserEmail))
|
||||
|
||||
subject := T("api.templates.email_change_verify_subject",
|
||||
map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"],
|
||||
@@ -77,10 +77,10 @@ func SendEmailChangeEmail(oldEmail, newEmail, locale, siteURL string) *model.App
|
||||
return nil
|
||||
}
|
||||
|
||||
func SendVerifyEmail(userId, userEmail, locale, siteURL string) *model.AppError {
|
||||
func SendVerifyEmail(userEmail, locale, siteURL, token string) *model.AppError {
|
||||
T := utils.GetUserTranslations(locale)
|
||||
|
||||
link := fmt.Sprintf("%s/do_verify_email?uid=%s&hid=%s&email=%s", siteURL, userId, model.HashPassword(userId+utils.Cfg.EmailSettings.InviteSalt), url.QueryEscape(userEmail))
|
||||
link := fmt.Sprintf("%s/do_verify_email?token=%s&email=%s", siteURL, token, url.QueryEscape(userEmail))
|
||||
|
||||
url, _ := url.Parse(siteURL)
|
||||
|
||||
@@ -144,7 +144,11 @@ func SendWelcomeEmail(userId string, email string, verified bool, locale, siteUR
|
||||
}
|
||||
|
||||
if !verified {
|
||||
link := fmt.Sprintf("%s/do_verify_email?uid=%s&hid=%s&email=%s", siteURL, userId, model.HashPassword(userId+utils.Cfg.EmailSettings.InviteSalt), url.QueryEscape(email))
|
||||
token, err := CreateVerifyEmailToken(userId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
link := fmt.Sprintf("%s/do_verify_email?token=%s&email=%s", siteURL, token.Token, url.QueryEscape(email))
|
||||
bodyPage.Props["VerifyUrl"] = link
|
||||
}
|
||||
|
||||
@@ -175,11 +179,11 @@ func SendPasswordChangeEmail(email, method, locale, siteURL string) *model.AppEr
|
||||
return nil
|
||||
}
|
||||
|
||||
func SendPasswordResetEmail(email string, recovery *model.PasswordRecovery, locale, siteURL string) (bool, *model.AppError) {
|
||||
func SendPasswordResetEmail(email string, token *model.Token, locale, siteURL string) (bool, *model.AppError) {
|
||||
|
||||
T := utils.GetUserTranslations(locale)
|
||||
|
||||
link := fmt.Sprintf("%s/reset_password_complete?code=%s", siteURL, url.QueryEscape(recovery.Code))
|
||||
link := fmt.Sprintf("%s/reset_password_complete?token=%s", siteURL, url.QueryEscape(token.Token))
|
||||
|
||||
subject := T("api.templates.reset_subject",
|
||||
map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"]})
|
||||
@@ -252,7 +256,7 @@ func SendInviteEmails(team *model.Team, senderName string, invites []string, sit
|
||||
props["name"] = team.Name
|
||||
props["time"] = fmt.Sprintf("%v", model.GetMillis())
|
||||
data := model.MapToJson(props)
|
||||
hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt))
|
||||
hash := utils.HashSha256(fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt))
|
||||
bodyPage.Props["Link"] = fmt.Sprintf("%s/signup_user_complete/?d=%s&h=%s", siteURL, url.QueryEscape(data), url.QueryEscape(hash))
|
||||
|
||||
if !utils.Cfg.EmailSettings.SendEmailNotifications {
|
||||
|
||||
@@ -62,17 +62,17 @@ func TestSendChangeUsernameEmail(t *testing.T) {
|
||||
func TestSendEmailChangeVerifyEmail(t *testing.T) {
|
||||
Setup()
|
||||
|
||||
var userId string = "5349853498543jdfvndf9834"
|
||||
var newUserEmail string = "newtest@example.com"
|
||||
var locale string = "en"
|
||||
var siteURL string = ""
|
||||
var expectedPartialMessage string = "You updated your email"
|
||||
var expectedSubject string = "[" + utils.Cfg.TeamSettings.SiteName + "] Verify new email address"
|
||||
var token string = "TEST_TOKEN"
|
||||
|
||||
//Delete all the messages before check the sample email
|
||||
utils.DeleteMailBox(newUserEmail)
|
||||
|
||||
if err := SendEmailChangeVerifyEmail(userId, newUserEmail, locale, siteURL); err != nil {
|
||||
if err := SendEmailChangeVerifyEmail(newUserEmail, locale, siteURL, token); err != nil {
|
||||
t.Log(err)
|
||||
t.Fatal("Should send change username email")
|
||||
} else {
|
||||
@@ -160,17 +160,17 @@ func TestSendEmailChangeEmail(t *testing.T) {
|
||||
func TestSendVerifyEmail(t *testing.T) {
|
||||
Setup()
|
||||
|
||||
var userId string = "5349853498543jdfvndf9834"
|
||||
var userEmail string = "test@example.com"
|
||||
var locale string = "en"
|
||||
var siteURL string = ""
|
||||
var expectedPartialMessage string = "Please verify your email address by clicking below"
|
||||
var expectedSubject string = "[" + utils.Cfg.TeamSettings.SiteName + "] Email Verification"
|
||||
var token string = "TEST_TOKEN"
|
||||
|
||||
//Delete all the messages before check the sample email
|
||||
utils.DeleteMailBox(userEmail)
|
||||
|
||||
if err := SendVerifyEmail(userId, userEmail, locale, siteURL); err != nil {
|
||||
if err := SendVerifyEmail(userEmail, locale, siteURL, token); err != nil {
|
||||
t.Log(err)
|
||||
t.Fatal("Should send change username email")
|
||||
} else {
|
||||
@@ -582,14 +582,22 @@ func TestSendPasswordReset(t *testing.T) {
|
||||
t.Log(resultsEmail.Body.Text)
|
||||
t.Fatal("Wrong Body message")
|
||||
}
|
||||
var recoveryKey *model.PasswordRecovery
|
||||
if result := <-Srv.Store.PasswordRecovery().Get(th.BasicUser.Id); result.Err != nil {
|
||||
loc := strings.Index(resultsEmail.Body.Text, "token=")
|
||||
if loc == -1 {
|
||||
t.Log(resultsEmail.Body.Text)
|
||||
t.Fatal("Code not found in email")
|
||||
}
|
||||
loc += 6
|
||||
recoveryTokenString := resultsEmail.Body.Text[loc : loc+model.TOKEN_SIZE]
|
||||
var recoveryToken *model.Token
|
||||
if result := <-Srv.Store.Token().GetByToken(recoveryTokenString); result.Err != nil {
|
||||
t.Log(recoveryTokenString)
|
||||
t.Fatal(result.Err)
|
||||
} else {
|
||||
recoveryKey = result.Data.(*model.PasswordRecovery)
|
||||
if !strings.Contains(resultsEmail.Body.Text, recoveryKey.Code) {
|
||||
recoveryToken = result.Data.(*model.Token)
|
||||
if !strings.Contains(resultsEmail.Body.Text, recoveryToken.Token) {
|
||||
t.Log(resultsEmail.Body.Text)
|
||||
t.Log(recoveryKey.Code)
|
||||
t.Log(recoveryToken.Token)
|
||||
t.Fatal("Received wrong recovery code")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ func AllowOAuthAppAccessToUser(userId string, authRequest *model.AuthorizeReques
|
||||
}
|
||||
|
||||
authData := &model.AuthData{UserId: userId, ClientId: authRequest.ClientId, CreateAt: model.GetMillis(), RedirectUri: authRequest.RedirectUri, State: authRequest.State, Scope: authRequest.Scope}
|
||||
authData.Code = model.HashPassword(fmt.Sprintf("%v:%v:%v:%v", authRequest.ClientId, authRequest.RedirectUri, authData.CreateAt, userId))
|
||||
authData.Code = utils.HashSha256(fmt.Sprintf("%v:%v:%v:%v", authRequest.ClientId, authRequest.RedirectUri, authData.CreateAt, userId))
|
||||
|
||||
// this saves the OAuth2 app as authorized
|
||||
authorizedApp := model.Preference{
|
||||
@@ -167,7 +167,7 @@ func GetOAuthAccessToken(clientId, grantType, redirectUri, code, secret, refresh
|
||||
return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.redirect_uri.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if !model.ComparePassword(code, fmt.Sprintf("%v:%v:%v:%v", clientId, redirectUri, authData.CreateAt, authData.UserId)) {
|
||||
if code != utils.HashSha256(fmt.Sprintf("%v:%v:%v:%v", clientId, redirectUri, authData.CreateAt, authData.UserId)) {
|
||||
return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.expired_code.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
@@ -530,7 +530,7 @@ func GetAuthorizationCode(service string, props map[string]string, loginHint str
|
||||
endpoint := sso.AuthEndpoint
|
||||
scope := sso.Scope
|
||||
|
||||
props["hash"] = model.HashPassword(clientId)
|
||||
props["hash"] = utils.HashSha256(clientId)
|
||||
state := b64.StdEncoding.EncodeToString([]byte(model.MapToJson(props)))
|
||||
|
||||
redirectUri := utils.GetSiteURL() + "/signup/" + service + "/complete"
|
||||
@@ -563,7 +563,7 @@ func AuthorizeOAuthUser(service, code, state, redirectUri string) (io.ReadCloser
|
||||
|
||||
stateProps := model.MapFromJson(strings.NewReader(stateStr))
|
||||
|
||||
if !model.ComparePassword(stateProps["hash"], sso.Id) {
|
||||
if stateProps["hash"] != utils.HashSha256(sso.Id) {
|
||||
return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "")
|
||||
}
|
||||
|
||||
|
||||
@@ -198,7 +198,7 @@ func AddUserToTeamByTeamId(teamId string, user *model.User) *model.AppError {
|
||||
func AddUserToTeamByHash(userId string, hash string, data string) (*model.Team, *model.AppError) {
|
||||
props := model.MapFromJson(strings.NewReader(data))
|
||||
|
||||
if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt)) {
|
||||
if hash != utils.HashSha256(fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt)) {
|
||||
return nil, model.NewLocAppError("JoinUserToTeamByHash", "api.user.create_user.signup_link_invalid.app_error", nil, "")
|
||||
}
|
||||
|
||||
@@ -757,7 +757,7 @@ func GetTeamIdFromQuery(query url.Values) (string, *model.AppError) {
|
||||
data := query.Get("d")
|
||||
props := model.MapFromJson(strings.NewReader(data))
|
||||
|
||||
if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt)) {
|
||||
if hash != utils.HashSha256(fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt)) {
|
||||
return "", model.NewAppError("GetTeamIdFromQuery", "api.oauth.singup_with_oauth.invalid_link.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
|
||||
121
app/user.go
121
app/user.go
@@ -30,6 +30,13 @@ import (
|
||||
"github.com/mattermost/platform/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
TOKEN_TYPE_PASSWORD_RECOVERY = "password_recovery"
|
||||
TOKEN_TYPE_VERIFY_EMAIL = "verify_email"
|
||||
PASSWORD_RECOVER_EXPIRY_TIME = 1000 * 60 * 60 // 1 hour
|
||||
VERIFY_EMAIL_EXPIRY_TIME = 1000 * 60 * 60 // 1 hour
|
||||
)
|
||||
|
||||
func CreateUserWithHash(user *model.User, hash string, data string) (*model.User, *model.AppError) {
|
||||
if err := IsUserSignUpAllowed(); err != nil {
|
||||
return nil, err
|
||||
@@ -37,7 +44,7 @@ func CreateUserWithHash(user *model.User, hash string, data string) (*model.User
|
||||
|
||||
props := model.MapFromJson(strings.NewReader(data))
|
||||
|
||||
if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt)) {
|
||||
if hash != utils.HashSha256(fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt)) {
|
||||
return nil, model.NewLocAppError("CreateUserWithHash", "api.user.create_user.signup_link_invalid.app_error", nil, "")
|
||||
}
|
||||
|
||||
@@ -978,11 +985,9 @@ func UpdateUser(user *model.User, sendNotifications bool) (*model.User, *model.A
|
||||
}()
|
||||
|
||||
if utils.Cfg.EmailSettings.RequireEmailVerification {
|
||||
go func() {
|
||||
if err := SendEmailChangeVerifyEmail(rusers[0].Id, rusers[0].Email, rusers[0].Locale, utils.GetSiteURL()); err != nil {
|
||||
l4g.Error(err.Error())
|
||||
}
|
||||
}()
|
||||
if err := SendEmailVerification(rusers[0]); err != nil {
|
||||
l4g.Error(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1084,19 +1089,19 @@ func UpdatePasswordSendEmail(user *model.User, newPassword, method string) *mode
|
||||
return nil
|
||||
}
|
||||
|
||||
func ResetPasswordFromCode(code, newPassword string) *model.AppError {
|
||||
var recovery *model.PasswordRecovery
|
||||
func ResetPasswordFromToken(userSuppliedTokenString, newPassword string) *model.AppError {
|
||||
var token *model.Token
|
||||
var err *model.AppError
|
||||
if recovery, err = GetPasswordRecovery(code); err != nil {
|
||||
if token, err = GetPasswordRecoveryToken(userSuppliedTokenString); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if model.GetMillis()-recovery.CreateAt >= model.PASSWORD_RECOVER_EXPIRY_TIME {
|
||||
if model.GetMillis()-token.CreateAt >= PASSWORD_RECOVER_EXPIRY_TIME {
|
||||
return model.NewAppError("resetPassword", "api.user.reset_password.link_expired.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
var user *model.User
|
||||
if user, err = GetUser(recovery.UserId); err != nil {
|
||||
if user, err = GetUser(token.Extra); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1110,7 +1115,7 @@ func ResetPasswordFromCode(code, newPassword string) *model.AppError {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := DeletePasswordRecoveryForUser(recovery.UserId); err != nil {
|
||||
if err := DeleteToken(token); err != nil {
|
||||
l4g.Error(err.Error())
|
||||
}
|
||||
|
||||
@@ -1128,39 +1133,42 @@ func SendPasswordReset(email string, siteURL string) (bool, *model.AppError) {
|
||||
return false, model.NewAppError("SendPasswordReset", "api.user.send_password_reset.sso.app_error", nil, "userId="+user.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
var recovery *model.PasswordRecovery
|
||||
if recovery, err = CreatePasswordRecovery(user.Id); err != nil {
|
||||
var token *model.Token
|
||||
if token, err = CreatePasswordRecoveryToken(user.Id); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if _, err := SendPasswordResetEmail(email, recovery, user.Locale, siteURL); err != nil {
|
||||
if _, err := SendPasswordResetEmail(email, token, user.Locale, siteURL); err != nil {
|
||||
return false, model.NewLocAppError("SendPasswordReset", "api.user.send_password_reset.send.app_error", nil, "err="+err.Message)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func CreatePasswordRecovery(userId string) (*model.PasswordRecovery, *model.AppError) {
|
||||
recovery := &model.PasswordRecovery{}
|
||||
recovery.UserId = userId
|
||||
func CreatePasswordRecoveryToken(userId string) (*model.Token, *model.AppError) {
|
||||
token := model.NewToken(TOKEN_TYPE_PASSWORD_RECOVERY, userId)
|
||||
|
||||
if result := <-Srv.Store.PasswordRecovery().SaveOrUpdate(recovery); result.Err != nil {
|
||||
if result := <-Srv.Store.Token().Save(token); result.Err != nil {
|
||||
return nil, result.Err
|
||||
}
|
||||
|
||||
return recovery, nil
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func GetPasswordRecovery(code string) (*model.PasswordRecovery, *model.AppError) {
|
||||
if result := <-Srv.Store.PasswordRecovery().GetByCode(code); result.Err != nil {
|
||||
return nil, model.NewAppError("GetPasswordRecovery", "api.user.reset_password.invalid_link.app_error", nil, result.Err.Error(), http.StatusBadRequest)
|
||||
func GetPasswordRecoveryToken(token string) (*model.Token, *model.AppError) {
|
||||
if result := <-Srv.Store.Token().GetByToken(token); result.Err != nil {
|
||||
return nil, model.NewAppError("GetPasswordRecoveryToken", "api.user.reset_password.invalid_link.app_error", nil, result.Err.Error(), http.StatusBadRequest)
|
||||
} else {
|
||||
return result.Data.(*model.PasswordRecovery), nil
|
||||
token := result.Data.(*model.Token)
|
||||
if token.Type != TOKEN_TYPE_PASSWORD_RECOVERY {
|
||||
return nil, model.NewAppError("GetPasswordRecoveryToken", "api.user.reset_password.broken_token.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
}
|
||||
|
||||
func DeletePasswordRecoveryForUser(userId string) *model.AppError {
|
||||
if result := <-Srv.Store.PasswordRecovery().Delete(userId); result.Err != nil {
|
||||
func DeleteToken(token *model.Token) *model.AppError {
|
||||
if result := <-Srv.Store.Token().Delete(token.Token); result.Err != nil {
|
||||
return result.Err
|
||||
}
|
||||
|
||||
@@ -1250,10 +1258,6 @@ func PermanentDeleteUser(user *model.User) *model.AppError {
|
||||
return result.Err
|
||||
}
|
||||
|
||||
if result := <-Srv.Store.PasswordRecovery().Delete(user.Id); result.Err != nil {
|
||||
return result.Err
|
||||
}
|
||||
|
||||
l4g.Warn(utils.T("api.user.permanent_delete_user.deleted.warn"), user.Email, user.Id)
|
||||
|
||||
return nil
|
||||
@@ -1272,6 +1276,63 @@ func PermanentDeleteAllUsers() *model.AppError {
|
||||
return nil
|
||||
}
|
||||
|
||||
func SendEmailVerification(user *model.User) *model.AppError {
|
||||
token, err := CreateVerifyEmailToken(user.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := GetStatus(user.Id); err != nil {
|
||||
go SendVerifyEmail(user.Email, user.Locale, utils.GetSiteURL(), token.Token)
|
||||
} else {
|
||||
go SendEmailChangeVerifyEmail(user.Email, user.Locale, utils.GetSiteURL(), token.Token)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func VerifyEmailFromToken(userSuppliedTokenString string) *model.AppError {
|
||||
var token *model.Token
|
||||
var err *model.AppError
|
||||
if token, err = GetVerifyEmailToken(userSuppliedTokenString); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if model.GetMillis()-token.CreateAt >= PASSWORD_RECOVER_EXPIRY_TIME {
|
||||
return model.NewAppError("resetPassword", "api.user.reset_password.link_expired.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
if err := VerifyUserEmail(token.Extra); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := DeleteToken(token); err != nil {
|
||||
l4g.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateVerifyEmailToken(userId string) (*model.Token, *model.AppError) {
|
||||
token := model.NewToken(TOKEN_TYPE_VERIFY_EMAIL, userId)
|
||||
|
||||
if result := <-Srv.Store.Token().Save(token); result.Err != nil {
|
||||
return nil, result.Err
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func GetVerifyEmailToken(token string) (*model.Token, *model.AppError) {
|
||||
if result := <-Srv.Store.Token().GetByToken(token); result.Err != nil {
|
||||
return nil, model.NewAppError("GetVerifyEmailToken", "api.user.verify_email.bad_link.app_error", nil, result.Err.Error(), http.StatusBadRequest)
|
||||
} else {
|
||||
token := result.Data.(*model.Token)
|
||||
if token.Type != TOKEN_TYPE_VERIFY_EMAIL {
|
||||
return nil, model.NewAppError("GetVerifyEmailToken", "api.user.verify_email.broken_token.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
}
|
||||
|
||||
func VerifyUserEmail(userId string) *model.AppError {
|
||||
if err := (<-Srv.Store.User().VerifyEmail(userId)).Err; err != nil {
|
||||
return err
|
||||
|
||||
Ссылка в новой задаче
Block a user