Creating common token store and moving email invites and verification to it (#6213)
Этот коммит содержится в:
коммит произвёл
Joram Wilander
родитель
0e007e344b
Коммит
9a87bb3af6
@@ -5,16 +5,17 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"github.com/mattermost/platform/app"
|
|
||||||
"github.com/mattermost/platform/einterfaces"
|
|
||||||
"github.com/mattermost/platform/model"
|
|
||||||
"github.com/mattermost/platform/utils"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mattermost/platform/app"
|
||||||
|
"github.com/mattermost/platform/einterfaces"
|
||||||
|
"github.com/mattermost/platform/model"
|
||||||
|
"github.com/mattermost/platform/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOAuthRegisterApp(t *testing.T) {
|
func TestOAuthRegisterApp(t *testing.T) {
|
||||||
@@ -735,7 +736,7 @@ func TestOAuthComplete(t *testing.T) {
|
|||||||
closeBody(r)
|
closeBody(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
stateProps["hash"] = model.HashPassword(utils.Cfg.GitLabSettings.Id)
|
stateProps["hash"] = utils.HashSha256(utils.Cfg.GitLabSettings.Id)
|
||||||
state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps)))
|
state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps)))
|
||||||
if r, err := HttpGet(Client.Url+"/login/gitlab/complete?code=123&state="+url.QueryEscape(state), Client.HttpClient, "", true); err == nil {
|
if r, err := HttpGet(Client.Url+"/login/gitlab/complete?code=123&state="+url.QueryEscape(state), Client.HttpClient, "", true); err == nil {
|
||||||
t.Fatal("should have failed - no connection")
|
t.Fatal("should have failed - no connection")
|
||||||
@@ -771,7 +772,7 @@ func TestOAuthComplete(t *testing.T) {
|
|||||||
stateProps["action"] = model.OAUTH_ACTION_EMAIL_TO_SSO
|
stateProps["action"] = model.OAUTH_ACTION_EMAIL_TO_SSO
|
||||||
delete(stateProps, "team_id")
|
delete(stateProps, "team_id")
|
||||||
stateProps["redirect_to"] = utils.Cfg.GitLabSettings.AuthEndpoint
|
stateProps["redirect_to"] = utils.Cfg.GitLabSettings.AuthEndpoint
|
||||||
stateProps["hash"] = model.HashPassword(utils.Cfg.GitLabSettings.Id)
|
stateProps["hash"] = utils.HashSha256(utils.Cfg.GitLabSettings.Id)
|
||||||
stateProps["redirect_to"] = "/oauth/authorize"
|
stateProps["redirect_to"] = "/oauth/authorize"
|
||||||
state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps)))
|
state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps)))
|
||||||
if r, err := HttpGet(Client.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), Client.HttpClient, "", false); err == nil {
|
if r, err := HttpGet(Client.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), Client.HttpClient, "", false); err == nil {
|
||||||
|
|||||||
17
api/user.go
17
api/user.go
@@ -34,8 +34,8 @@ func InitUser() {
|
|||||||
BaseRoutes.Users.Handle("/logout", ApiAppHandler(logout)).Methods("POST")
|
BaseRoutes.Users.Handle("/logout", ApiAppHandler(logout)).Methods("POST")
|
||||||
BaseRoutes.Users.Handle("/revoke_session", ApiUserRequired(revokeSession)).Methods("POST")
|
BaseRoutes.Users.Handle("/revoke_session", ApiUserRequired(revokeSession)).Methods("POST")
|
||||||
BaseRoutes.Users.Handle("/attach_device", ApiUserRequired(attachDeviceId)).Methods("POST")
|
BaseRoutes.Users.Handle("/attach_device", ApiUserRequired(attachDeviceId)).Methods("POST")
|
||||||
BaseRoutes.Users.Handle("/verify_email", ApiAppHandler(verifyEmail)).Methods("POST")
|
//DEPRICATED FOR SECURITY USE APIV4 BaseRoutes.Users.Handle("/verify_email", ApiAppHandler(verifyEmail)).Methods("POST")
|
||||||
BaseRoutes.Users.Handle("/resend_verification", ApiAppHandler(resendVerification)).Methods("POST")
|
//DEPRICATED FOR SECURITY USE APIV4 BaseRoutes.Users.Handle("/resend_verification", ApiAppHandler(resendVerification)).Methods("POST")
|
||||||
BaseRoutes.Users.Handle("/newimage", ApiUserRequired(uploadProfileImage)).Methods("POST")
|
BaseRoutes.Users.Handle("/newimage", ApiUserRequired(uploadProfileImage)).Methods("POST")
|
||||||
BaseRoutes.Users.Handle("/me", ApiUserRequired(getMe)).Methods("GET")
|
BaseRoutes.Users.Handle("/me", ApiUserRequired(getMe)).Methods("GET")
|
||||||
BaseRoutes.Users.Handle("/initial_load", ApiAppHandler(getInitialLoad)).Methods("GET")
|
BaseRoutes.Users.Handle("/initial_load", ApiAppHandler(getInitialLoad)).Methods("GET")
|
||||||
@@ -767,22 +767,22 @@ func resetPassword(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
props := model.MapFromJson(r.Body)
|
props := model.MapFromJson(r.Body)
|
||||||
|
|
||||||
code := props["code"]
|
code := props["code"]
|
||||||
if len(code) != model.PASSWORD_RECOVERY_CODE_SIZE {
|
if len(code) != model.TOKEN_SIZE {
|
||||||
c.SetInvalidParam("resetPassword", "code")
|
c.SetInvalidParam("resetPassword", "code")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
newPassword := props["new_password"]
|
newPassword := props["new_password"]
|
||||||
|
|
||||||
c.LogAudit("attempt - code=" + code)
|
c.LogAudit("attempt - token=" + code)
|
||||||
|
|
||||||
if err := app.ResetPasswordFromCode(code, newPassword); err != nil {
|
if err := app.ResetPasswordFromToken(code, newPassword); err != nil {
|
||||||
c.LogAudit("fail - code=" + code)
|
c.LogAudit("fail - token=" + code)
|
||||||
c.Err = err
|
c.Err = err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.LogAudit("success - code=" + code)
|
c.LogAudit("success - token=" + code)
|
||||||
|
|
||||||
rdata := map[string]string{}
|
rdata := map[string]string{}
|
||||||
rdata["status"] = "ok"
|
rdata["status"] = "ok"
|
||||||
@@ -992,6 +992,7 @@ func ldapToEmail(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Write([]byte(model.MapToJson(map[string]string{"follow_link": link})))
|
w.Write([]byte(model.MapToJson(map[string]string{"follow_link": link})))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Disabling for security reasons. Use apiv4
|
||||||
func verifyEmail(c *Context, w http.ResponseWriter, r *http.Request) {
|
func verifyEmail(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
props := model.MapFromJson(r.Body)
|
props := model.MapFromJson(r.Body)
|
||||||
|
|
||||||
@@ -1039,7 +1040,7 @@ func resendVerification(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
go app.SendEmailChangeVerifyEmail(user.Id, user.Email, user.Locale, utils.GetSiteURL())
|
go app.SendEmailChangeVerifyEmail(user.Id, user.Email, user.Locale, utils.GetSiteURL())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
func generateMfaSecret(c *Context, w http.ResponseWriter, r *http.Request) {
|
func generateMfaSecret(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
secret, err := app.GenerateMfaSecret(c.Session.UserId)
|
secret, err := app.GenerateMfaSecret(c.Session.UserId)
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ func TestLogin(t *testing.T) {
|
|||||||
props["display_name"] = rteam2.Data.(*model.Team).DisplayName
|
props["display_name"] = rteam2.Data.(*model.Team).DisplayName
|
||||||
props["time"] = fmt.Sprintf("%v", model.GetMillis())
|
props["time"] = fmt.Sprintf("%v", model.GetMillis())
|
||||||
data := model.MapToJson(props)
|
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))
|
||||||
|
|
||||||
ruser2, err := Client.CreateUserFromSignup(&user2, data, hash)
|
ruser2, err := Client.CreateUserFromSignup(&user2, data, hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1316,13 +1316,6 @@ func TestResetPassword(t *testing.T) {
|
|||||||
|
|
||||||
Client.Must(Client.SendPasswordReset(user.Email))
|
Client.Must(Client.SendPasswordReset(user.Email))
|
||||||
|
|
||||||
var recovery *model.PasswordRecovery
|
|
||||||
if result := <-app.Srv.Store.PasswordRecovery().Get(user.Id); result.Err != nil {
|
|
||||||
t.Fatal(result.Err)
|
|
||||||
} else {
|
|
||||||
recovery = result.Data.(*model.PasswordRecovery)
|
|
||||||
}
|
|
||||||
|
|
||||||
//Check if the email was send to the rigth email address and the recovery key match
|
//Check if the email was send to the rigth email address and the recovery key match
|
||||||
var resultsMailbox utils.JSONMessageHeaderInbucket
|
var resultsMailbox utils.JSONMessageHeaderInbucket
|
||||||
err := utils.RetryInbucket(5, func() error {
|
err := utils.RetryInbucket(5, func() error {
|
||||||
@@ -1335,25 +1328,42 @@ func TestResetPassword(t *testing.T) {
|
|||||||
t.Log("No email was received, maybe due load on the server. Disabling this verification")
|
t.Log("No email was received, maybe due load on the server. Disabling this verification")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var recoveryTokenString string
|
||||||
if err == nil && len(resultsMailbox) > 0 {
|
if err == nil && len(resultsMailbox) > 0 {
|
||||||
if !strings.ContainsAny(resultsMailbox[0].To[0], user.Email) {
|
if !strings.ContainsAny(resultsMailbox[0].To[0], user.Email) {
|
||||||
t.Fatal("Wrong To recipient")
|
t.Fatal("Wrong To recipient")
|
||||||
} else {
|
} else {
|
||||||
if resultsEmail, err := utils.GetMessageFromMailbox(user.Email, resultsMailbox[0].ID); err == nil {
|
if resultsEmail, err := utils.GetMessageFromMailbox(user.Email, resultsMailbox[0].ID); err == nil {
|
||||||
if !strings.Contains(resultsEmail.Body.Text, recovery.Code) {
|
loc := strings.Index(resultsEmail.Body.Text, "token=")
|
||||||
|
if loc == -1 {
|
||||||
|
t.Log(recoveryTokenString)
|
||||||
t.Log(resultsEmail.Body.Text)
|
t.Log(resultsEmail.Body.Text)
|
||||||
t.Log(recovery.Code)
|
t.Fatal("Code not found in email")
|
||||||
t.Fatal("Received wrong recovery code")
|
|
||||||
}
|
}
|
||||||
|
loc += 6
|
||||||
|
recoveryTokenString = resultsEmail.Body.Text[loc : loc+model.TOKEN_SIZE]
|
||||||
|
t.Log(resultsEmail.Body.Text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := Client.ResetPassword(recovery.Code, ""); err == nil {
|
var recoveryToken *model.Token
|
||||||
|
if result := <-app.Srv.Store.Token().GetByToken(recoveryTokenString); result.Err != nil {
|
||||||
|
t.Log(recoveryTokenString)
|
||||||
|
t.Fatal(result.Err)
|
||||||
|
} else {
|
||||||
|
recoveryToken = result.Data.(*model.Token)
|
||||||
|
}
|
||||||
|
|
||||||
|
if recoveryToken.Token != recoveryTokenString {
|
||||||
|
t.Fatal("Did not send the correct token. DB: "+recoveryToken.Token, " Sent: "+recoveryTokenString)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := Client.ResetPassword(recoveryToken.Token, ""); err == nil {
|
||||||
t.Fatal("Should have errored - no password")
|
t.Fatal("Should have errored - no password")
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := Client.ResetPassword(recovery.Code, "newp"); err == nil {
|
if _, err := Client.ResetPassword(recoveryToken.Token, "newp"); err == nil {
|
||||||
t.Fatal("Should have errored - password too short")
|
t.Fatal("Should have errored - password too short")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1366,38 +1376,26 @@ func TestResetPassword(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
code := ""
|
code := ""
|
||||||
for i := 0; i < model.PASSWORD_RECOVERY_CODE_SIZE; i++ {
|
for i := 0; i < model.TOKEN_SIZE; i++ {
|
||||||
code += "a"
|
code += "a"
|
||||||
}
|
}
|
||||||
if _, err := Client.ResetPassword(code, "newpwd1"); err == nil {
|
if _, err := Client.ResetPassword(code, "newpwd1"); err == nil {
|
||||||
t.Fatal("Should have errored - bad code")
|
t.Fatal("Should have errored - bad code")
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := Client.ResetPassword(recovery.Code, "newpwd1"); err != nil {
|
if _, err := Client.ResetPassword(recoveryToken.Token, "newpwd1"); err != nil {
|
||||||
t.Log(recovery.Code)
|
t.Log(recoveryToken.Token)
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
Client.Logout()
|
/*authData := model.NewId()
|
||||||
Client.Must(Client.LoginById(user.Id, "newpwd1"))
|
|
||||||
Client.SetTeamId(team.Id)
|
|
||||||
|
|
||||||
Client.Must(Client.SendPasswordReset(user.Email))
|
|
||||||
|
|
||||||
if result := <-app.Srv.Store.PasswordRecovery().Get(user.Id); result.Err != nil {
|
|
||||||
t.Fatal(result.Err)
|
|
||||||
} else {
|
|
||||||
recovery = result.Data.(*model.PasswordRecovery)
|
|
||||||
}
|
|
||||||
|
|
||||||
authData := model.NewId()
|
|
||||||
if result := <-app.Srv.Store.User().UpdateAuthData(user.Id, "random", &authData, "", true); result.Err != nil {
|
if result := <-app.Srv.Store.User().UpdateAuthData(user.Id, "random", &authData, "", true); result.Err != nil {
|
||||||
t.Fatal(result.Err)
|
t.Fatal(result.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := Client.ResetPassword(recovery.Code, "newpwd1"); err == nil {
|
if _, err := Client.ResetPassword(recovery.Code, "newpwd1"); err == nil {
|
||||||
t.Fatal("Should have errored - sso user")
|
t.Fatal("Should have errored - sso user")
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUserUpdateNotify(t *testing.T) {
|
func TestUserUpdateNotify(t *testing.T) {
|
||||||
|
|||||||
@@ -876,7 +876,7 @@ func TestAddTeamMember(t *testing.T) {
|
|||||||
dataObject["id"] = team.Id
|
dataObject["id"] = team.Id
|
||||||
|
|
||||||
data := model.MapToJson(dataObject)
|
data := model.MapToJson(dataObject)
|
||||||
hashed := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt))
|
hashed := utils.HashSha256(fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt))
|
||||||
|
|
||||||
tm, resp = Client.AddTeamMember(team.Id, "", hashed, data, "")
|
tm, resp = Client.AddTeamMember(team.Id, "", hashed, data, "")
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
@@ -906,7 +906,7 @@ func TestAddTeamMember(t *testing.T) {
|
|||||||
// expired data of more than 50 hours
|
// expired data of more than 50 hours
|
||||||
dataObject["time"] = fmt.Sprintf("%v", model.GetMillis()-1000*60*60*50)
|
dataObject["time"] = fmt.Sprintf("%v", model.GetMillis()-1000*60*60*50)
|
||||||
data = model.MapToJson(dataObject)
|
data = model.MapToJson(dataObject)
|
||||||
hashed = model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt))
|
hashed = utils.HashSha256(fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt))
|
||||||
|
|
||||||
tm, resp = Client.AddTeamMember(team.Id, "", hashed, data, "")
|
tm, resp = Client.AddTeamMember(team.Id, "", hashed, data, "")
|
||||||
CheckNotFoundStatus(t, resp)
|
CheckNotFoundStatus(t, resp)
|
||||||
@@ -914,7 +914,7 @@ func TestAddTeamMember(t *testing.T) {
|
|||||||
// invalid team id
|
// invalid team id
|
||||||
dataObject["id"] = GenerateTestId()
|
dataObject["id"] = GenerateTestId()
|
||||||
data = model.MapToJson(dataObject)
|
data = model.MapToJson(dataObject)
|
||||||
hashed = model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt))
|
hashed = utils.HashSha256(fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt))
|
||||||
|
|
||||||
tm, resp = Client.AddTeamMember(team.Id, "", hashed, data, "")
|
tm, resp = Client.AddTeamMember(team.Id, "", hashed, data, "")
|
||||||
CheckNotFoundStatus(t, resp)
|
CheckNotFoundStatus(t, resp)
|
||||||
|
|||||||
55
api4/user.go
55
api4/user.go
@@ -768,23 +768,23 @@ func updatePassword(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
func resetPassword(c *Context, w http.ResponseWriter, r *http.Request) {
|
func resetPassword(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
props := model.MapFromJson(r.Body)
|
props := model.MapFromJson(r.Body)
|
||||||
|
|
||||||
code := props["code"]
|
token := props["token"]
|
||||||
if len(code) != model.PASSWORD_RECOVERY_CODE_SIZE {
|
if len(token) != model.TOKEN_SIZE {
|
||||||
c.SetInvalidParam("code")
|
c.SetInvalidParam("token")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
newPassword := props["new_password"]
|
newPassword := props["new_password"]
|
||||||
|
|
||||||
c.LogAudit("attempt - code=" + code)
|
c.LogAudit("attempt - token=" + token)
|
||||||
|
|
||||||
if err := app.ResetPasswordFromCode(code, newPassword); err != nil {
|
if err := app.ResetPasswordFromToken(token, newPassword); err != nil {
|
||||||
c.LogAudit("fail - code=" + code)
|
c.LogAudit("fail - token=" + token)
|
||||||
c.Err = err
|
c.Err = err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.LogAudit("success - code=" + code)
|
c.LogAudit("success - token=" + token)
|
||||||
|
|
||||||
ReturnStatusOK(w)
|
ReturnStatusOK(w)
|
||||||
}
|
}
|
||||||
@@ -985,32 +985,21 @@ func getUserAudits(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
func verifyUserEmail(c *Context, w http.ResponseWriter, r *http.Request) {
|
func verifyUserEmail(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
props := model.MapFromJson(r.Body)
|
props := model.MapFromJson(r.Body)
|
||||||
|
|
||||||
userId := props["user_id"]
|
token := props["token"]
|
||||||
if len(userId) != 26 {
|
if len(token) != model.TOKEN_SIZE {
|
||||||
c.SetInvalidParam("user_id")
|
c.SetInvalidParam("token")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hashedId := props["hash_id"]
|
if err := app.VerifyEmailFromToken(token); err != nil {
|
||||||
if len(hashedId) == 0 {
|
c.Err = model.NewLocAppError("verifyUserEmail", "api.user.verify_email.bad_link.app_error", nil, err.Error())
|
||||||
c.SetInvalidParam("hash_id")
|
c.Err.StatusCode = http.StatusBadRequest
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
c.LogAudit("Email Verified")
|
||||||
|
ReturnStatusOK(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hashed := model.HashPassword(hashedId)
|
|
||||||
if model.ComparePassword(hashed, userId+utils.Cfg.EmailSettings.InviteSalt) {
|
|
||||||
if c.Err = app.VerifyUserEmail(userId); c.Err != nil {
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
c.LogAudit("Email Verified")
|
|
||||||
ReturnStatusOK(w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Err = model.NewLocAppError("verifyUserEmail", "api.user.verify_email.bad_link.app_error", nil, "")
|
|
||||||
c.Err.StatusCode = http.StatusBadRequest
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendVerificationEmail(c *Context, w http.ResponseWriter, r *http.Request) {
|
func sendVerificationEmail(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -1029,10 +1018,12 @@ func sendVerificationEmail(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := app.GetStatus(user.Id); err != nil {
|
app.SendEmailVerification(user)
|
||||||
go app.SendVerifyEmail(user.Id, user.Email, user.Locale, utils.GetSiteURL())
|
if err != nil {
|
||||||
} else {
|
// Don't want to leak whether the email is valid or not
|
||||||
go app.SendEmailChangeVerifyEmail(user.Id, user.Email, user.Locale, utils.GetSiteURL())
|
l4g.Error("Unable to create email verification token: " + err.Error())
|
||||||
|
ReturnStatusOK(w)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ReturnStatusOK(w)
|
ReturnStatusOK(w)
|
||||||
|
|||||||
@@ -1413,13 +1413,6 @@ func TestResetPassword(t *testing.T) {
|
|||||||
t.Fatal("should have succeeded")
|
t.Fatal("should have succeeded")
|
||||||
}
|
}
|
||||||
|
|
||||||
var recovery *model.PasswordRecovery
|
|
||||||
if result := <-app.Srv.Store.PasswordRecovery().Get(user.Id); result.Err != nil {
|
|
||||||
t.Fatal(result.Err)
|
|
||||||
} else {
|
|
||||||
recovery = result.Data.(*model.PasswordRecovery)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the email was send to the right email address and the recovery key match
|
// Check if the email was send to the right email address and the recovery key match
|
||||||
var resultsMailbox utils.JSONMessageHeaderInbucket
|
var resultsMailbox utils.JSONMessageHeaderInbucket
|
||||||
err := utils.RetryInbucket(5, func() error {
|
err := utils.RetryInbucket(5, func() error {
|
||||||
@@ -1431,24 +1424,36 @@ func TestResetPassword(t *testing.T) {
|
|||||||
t.Log(err)
|
t.Log(err)
|
||||||
t.Log("No email was received, maybe due load on the server. Disabling this verification")
|
t.Log("No email was received, maybe due load on the server. Disabling this verification")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var recoveryTokenString string
|
||||||
if err == nil && len(resultsMailbox) > 0 {
|
if err == nil && len(resultsMailbox) > 0 {
|
||||||
if !strings.ContainsAny(resultsMailbox[0].To[0], user.Email) {
|
if !strings.ContainsAny(resultsMailbox[0].To[0], user.Email) {
|
||||||
t.Fatal("Wrong To recipient")
|
t.Fatal("Wrong To recipient")
|
||||||
} else {
|
} else {
|
||||||
if resultsEmail, err := utils.GetMessageFromMailbox(user.Email, resultsMailbox[0].ID); err == nil {
|
if resultsEmail, err := utils.GetMessageFromMailbox(user.Email, resultsMailbox[0].ID); err == nil {
|
||||||
if !strings.Contains(resultsEmail.Body.Text, recovery.Code) {
|
loc := strings.Index(resultsEmail.Body.Text, "token=")
|
||||||
|
if loc == -1 {
|
||||||
t.Log(resultsEmail.Body.Text)
|
t.Log(resultsEmail.Body.Text)
|
||||||
t.Log(recovery.Code)
|
t.Fatal("Code not found in email")
|
||||||
t.Fatal("Received wrong recovery code")
|
|
||||||
}
|
}
|
||||||
|
loc += 6
|
||||||
|
recoveryTokenString = resultsEmail.Body.Text[loc : loc+model.TOKEN_SIZE]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, resp = Client.ResetPassword(recovery.Code, "")
|
var recoveryToken *model.Token
|
||||||
|
if result := <-app.Srv.Store.Token().GetByToken(recoveryTokenString); result.Err != nil {
|
||||||
|
t.Log(recoveryTokenString)
|
||||||
|
t.Fatal(result.Err)
|
||||||
|
} else {
|
||||||
|
recoveryToken = result.Data.(*model.Token)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, resp = Client.ResetPassword(recoveryToken.Token, "")
|
||||||
CheckBadRequestStatus(t, resp)
|
CheckBadRequestStatus(t, resp)
|
||||||
|
|
||||||
_, resp = Client.ResetPassword(recovery.Code, "newp")
|
_, resp = Client.ResetPassword(recoveryToken.Token, "newp")
|
||||||
CheckBadRequestStatus(t, resp)
|
CheckBadRequestStatus(t, resp)
|
||||||
|
|
||||||
_, resp = Client.ResetPassword("", "newpwd")
|
_, resp = Client.ResetPassword("", "newpwd")
|
||||||
@@ -1458,14 +1463,14 @@ func TestResetPassword(t *testing.T) {
|
|||||||
CheckBadRequestStatus(t, resp)
|
CheckBadRequestStatus(t, resp)
|
||||||
|
|
||||||
code := ""
|
code := ""
|
||||||
for i := 0; i < model.PASSWORD_RECOVERY_CODE_SIZE; i++ {
|
for i := 0; i < model.TOKEN_SIZE; i++ {
|
||||||
code += "a"
|
code += "a"
|
||||||
}
|
}
|
||||||
|
|
||||||
_, resp = Client.ResetPassword(code, "newpwd")
|
_, resp = Client.ResetPassword(code, "newpwd")
|
||||||
CheckBadRequestStatus(t, resp)
|
CheckBadRequestStatus(t, resp)
|
||||||
|
|
||||||
success, resp = Client.ResetPassword(recovery.Code, "newpwd")
|
success, resp = Client.ResetPassword(recoveryToken.Token, "newpwd")
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
if !success {
|
if !success {
|
||||||
t.Fatal("should have succeeded")
|
t.Fatal("should have succeeded")
|
||||||
@@ -1474,16 +1479,16 @@ func TestResetPassword(t *testing.T) {
|
|||||||
Client.Login(user.Email, "newpwd")
|
Client.Login(user.Email, "newpwd")
|
||||||
Client.Logout()
|
Client.Logout()
|
||||||
|
|
||||||
_, resp = Client.ResetPassword(recovery.Code, "newpwd")
|
_, resp = Client.ResetPassword(recoveryToken.Token, "newpwd")
|
||||||
CheckBadRequestStatus(t, resp)
|
CheckBadRequestStatus(t, resp)
|
||||||
|
|
||||||
authData := model.NewId()
|
/*authData := model.NewId()
|
||||||
if result := <-app.Srv.Store.User().UpdateAuthData(user.Id, "random", &authData, "", true); result.Err != nil {
|
if result := <-app.Srv.Store.User().UpdateAuthData(user.Id, "random", &authData, "", true); result.Err != nil {
|
||||||
t.Fatal(result.Err)
|
t.Fatal(result.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, resp = Client.SendPasswordResetEmail(user.Email)
|
_, resp = Client.SendPasswordResetEmail(user.Email)
|
||||||
CheckBadRequestStatus(t, resp)
|
CheckBadRequestStatus(t, resp)*/
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetSessions(t *testing.T) {
|
func TestGetSessions(t *testing.T) {
|
||||||
@@ -1646,15 +1651,18 @@ func TestVerifyUserEmail(t *testing.T) {
|
|||||||
|
|
||||||
ruser, resp := Client.CreateUser(&user)
|
ruser, resp := Client.CreateUser(&user)
|
||||||
|
|
||||||
hashId := ruser.Id + utils.Cfg.EmailSettings.InviteSalt
|
token, err := app.CreateVerifyEmailToken(ruser.Id)
|
||||||
_, resp = Client.VerifyUserEmail(ruser.Id, hashId)
|
if err != nil {
|
||||||
|
t.Fatal("Unable to create email verify token")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, resp = Client.VerifyUserEmail(token.Token)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
|
|
||||||
hashId = ruser.Id + GenerateTestId()
|
_, resp = Client.VerifyUserEmail(GenerateTestId())
|
||||||
_, resp = Client.VerifyUserEmail(ruser.Id, hashId)
|
|
||||||
CheckBadRequestStatus(t, resp)
|
CheckBadRequestStatus(t, resp)
|
||||||
|
|
||||||
_, resp = Client.VerifyUserEmail(ruser.Id, "")
|
_, resp = Client.VerifyUserEmail("")
|
||||||
CheckBadRequestStatus(t, resp)
|
CheckBadRequestStatus(t, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
20
app/email.go
20
app/email.go
@@ -33,10 +33,10 @@ func SendChangeUsernameEmail(oldUsername, newUsername, email, locale, siteURL st
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func SendEmailChangeVerifyEmail(userId, newUserEmail, locale, siteURL string) *model.AppError {
|
func SendEmailChangeVerifyEmail(newUserEmail, locale, siteURL, token string) *model.AppError {
|
||||||
T := utils.GetUserTranslations(locale)
|
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",
|
subject := T("api.templates.email_change_verify_subject",
|
||||||
map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"],
|
map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"],
|
||||||
@@ -77,10 +77,10 @@ func SendEmailChangeEmail(oldEmail, newEmail, locale, siteURL string) *model.App
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func SendVerifyEmail(userId, userEmail, locale, siteURL string) *model.AppError {
|
func SendVerifyEmail(userEmail, locale, siteURL, token string) *model.AppError {
|
||||||
T := utils.GetUserTranslations(locale)
|
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)
|
url, _ := url.Parse(siteURL)
|
||||||
|
|
||||||
@@ -144,7 +144,11 @@ func SendWelcomeEmail(userId string, email string, verified bool, locale, siteUR
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !verified {
|
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
|
bodyPage.Props["VerifyUrl"] = link
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,11 +179,11 @@ func SendPasswordChangeEmail(email, method, locale, siteURL string) *model.AppEr
|
|||||||
return nil
|
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)
|
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",
|
subject := T("api.templates.reset_subject",
|
||||||
map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"]})
|
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["name"] = team.Name
|
||||||
props["time"] = fmt.Sprintf("%v", model.GetMillis())
|
props["time"] = fmt.Sprintf("%v", model.GetMillis())
|
||||||
data := model.MapToJson(props)
|
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))
|
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 {
|
if !utils.Cfg.EmailSettings.SendEmailNotifications {
|
||||||
|
|||||||
@@ -62,17 +62,17 @@ func TestSendChangeUsernameEmail(t *testing.T) {
|
|||||||
func TestSendEmailChangeVerifyEmail(t *testing.T) {
|
func TestSendEmailChangeVerifyEmail(t *testing.T) {
|
||||||
Setup()
|
Setup()
|
||||||
|
|
||||||
var userId string = "5349853498543jdfvndf9834"
|
|
||||||
var newUserEmail string = "newtest@example.com"
|
var newUserEmail string = "newtest@example.com"
|
||||||
var locale string = "en"
|
var locale string = "en"
|
||||||
var siteURL string = ""
|
var siteURL string = ""
|
||||||
var expectedPartialMessage string = "You updated your email"
|
var expectedPartialMessage string = "You updated your email"
|
||||||
var expectedSubject string = "[" + utils.Cfg.TeamSettings.SiteName + "] Verify new email address"
|
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
|
//Delete all the messages before check the sample email
|
||||||
utils.DeleteMailBox(newUserEmail)
|
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.Log(err)
|
||||||
t.Fatal("Should send change username email")
|
t.Fatal("Should send change username email")
|
||||||
} else {
|
} else {
|
||||||
@@ -160,17 +160,17 @@ func TestSendEmailChangeEmail(t *testing.T) {
|
|||||||
func TestSendVerifyEmail(t *testing.T) {
|
func TestSendVerifyEmail(t *testing.T) {
|
||||||
Setup()
|
Setup()
|
||||||
|
|
||||||
var userId string = "5349853498543jdfvndf9834"
|
|
||||||
var userEmail string = "test@example.com"
|
var userEmail string = "test@example.com"
|
||||||
var locale string = "en"
|
var locale string = "en"
|
||||||
var siteURL string = ""
|
var siteURL string = ""
|
||||||
var expectedPartialMessage string = "Please verify your email address by clicking below"
|
var expectedPartialMessage string = "Please verify your email address by clicking below"
|
||||||
var expectedSubject string = "[" + utils.Cfg.TeamSettings.SiteName + "] Email Verification"
|
var expectedSubject string = "[" + utils.Cfg.TeamSettings.SiteName + "] Email Verification"
|
||||||
|
var token string = "TEST_TOKEN"
|
||||||
|
|
||||||
//Delete all the messages before check the sample email
|
//Delete all the messages before check the sample email
|
||||||
utils.DeleteMailBox(userEmail)
|
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.Log(err)
|
||||||
t.Fatal("Should send change username email")
|
t.Fatal("Should send change username email")
|
||||||
} else {
|
} else {
|
||||||
@@ -582,14 +582,22 @@ func TestSendPasswordReset(t *testing.T) {
|
|||||||
t.Log(resultsEmail.Body.Text)
|
t.Log(resultsEmail.Body.Text)
|
||||||
t.Fatal("Wrong Body message")
|
t.Fatal("Wrong Body message")
|
||||||
}
|
}
|
||||||
var recoveryKey *model.PasswordRecovery
|
loc := strings.Index(resultsEmail.Body.Text, "token=")
|
||||||
if result := <-Srv.Store.PasswordRecovery().Get(th.BasicUser.Id); result.Err != nil {
|
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)
|
t.Fatal(result.Err)
|
||||||
} else {
|
} else {
|
||||||
recoveryKey = result.Data.(*model.PasswordRecovery)
|
recoveryToken = result.Data.(*model.Token)
|
||||||
if !strings.Contains(resultsEmail.Body.Text, recoveryKey.Code) {
|
if !strings.Contains(resultsEmail.Body.Text, recoveryToken.Token) {
|
||||||
t.Log(resultsEmail.Body.Text)
|
t.Log(resultsEmail.Body.Text)
|
||||||
t.Log(recoveryKey.Code)
|
t.Log(recoveryToken.Token)
|
||||||
t.Fatal("Received wrong recovery code")
|
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 := &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
|
// this saves the OAuth2 app as authorized
|
||||||
authorizedApp := model.Preference{
|
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)
|
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)
|
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
|
endpoint := sso.AuthEndpoint
|
||||||
scope := sso.Scope
|
scope := sso.Scope
|
||||||
|
|
||||||
props["hash"] = model.HashPassword(clientId)
|
props["hash"] = utils.HashSha256(clientId)
|
||||||
state := b64.StdEncoding.EncodeToString([]byte(model.MapToJson(props)))
|
state := b64.StdEncoding.EncodeToString([]byte(model.MapToJson(props)))
|
||||||
|
|
||||||
redirectUri := utils.GetSiteURL() + "/signup/" + service + "/complete"
|
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))
|
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, "")
|
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) {
|
func AddUserToTeamByHash(userId string, hash string, data string) (*model.Team, *model.AppError) {
|
||||||
props := model.MapFromJson(strings.NewReader(data))
|
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, "")
|
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")
|
data := query.Get("d")
|
||||||
props := model.MapFromJson(strings.NewReader(data))
|
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)
|
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"
|
"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) {
|
func CreateUserWithHash(user *model.User, hash string, data string) (*model.User, *model.AppError) {
|
||||||
if err := IsUserSignUpAllowed(); err != nil {
|
if err := IsUserSignUpAllowed(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -37,7 +44,7 @@ func CreateUserWithHash(user *model.User, hash string, data string) (*model.User
|
|||||||
|
|
||||||
props := model.MapFromJson(strings.NewReader(data))
|
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, "")
|
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 {
|
if utils.Cfg.EmailSettings.RequireEmailVerification {
|
||||||
go func() {
|
if err := SendEmailVerification(rusers[0]); err != nil {
|
||||||
if err := SendEmailChangeVerifyEmail(rusers[0].Id, rusers[0].Email, rusers[0].Locale, utils.GetSiteURL()); err != nil {
|
l4g.Error(err.Error())
|
||||||
l4g.Error(err.Error())
|
}
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1084,19 +1089,19 @@ func UpdatePasswordSendEmail(user *model.User, newPassword, method string) *mode
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ResetPasswordFromCode(code, newPassword string) *model.AppError {
|
func ResetPasswordFromToken(userSuppliedTokenString, newPassword string) *model.AppError {
|
||||||
var recovery *model.PasswordRecovery
|
var token *model.Token
|
||||||
var err *model.AppError
|
var err *model.AppError
|
||||||
if recovery, err = GetPasswordRecovery(code); err != nil {
|
if token, err = GetPasswordRecoveryToken(userSuppliedTokenString); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} 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)
|
return model.NewAppError("resetPassword", "api.user.reset_password.link_expired.app_error", nil, "", http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var user *model.User
|
var user *model.User
|
||||||
if user, err = GetUser(recovery.UserId); err != nil {
|
if user, err = GetUser(token.Extra); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1110,7 +1115,7 @@ func ResetPasswordFromCode(code, newPassword string) *model.AppError {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := DeletePasswordRecoveryForUser(recovery.UserId); err != nil {
|
if err := DeleteToken(token); err != nil {
|
||||||
l4g.Error(err.Error())
|
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)
|
return false, model.NewAppError("SendPasswordReset", "api.user.send_password_reset.sso.app_error", nil, "userId="+user.Id, http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
var recovery *model.PasswordRecovery
|
var token *model.Token
|
||||||
if recovery, err = CreatePasswordRecovery(user.Id); err != nil {
|
if token, err = CreatePasswordRecoveryToken(user.Id); err != nil {
|
||||||
return false, err
|
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 false, model.NewLocAppError("SendPasswordReset", "api.user.send_password_reset.send.app_error", nil, "err="+err.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreatePasswordRecovery(userId string) (*model.PasswordRecovery, *model.AppError) {
|
func CreatePasswordRecoveryToken(userId string) (*model.Token, *model.AppError) {
|
||||||
recovery := &model.PasswordRecovery{}
|
token := model.NewToken(TOKEN_TYPE_PASSWORD_RECOVERY, userId)
|
||||||
recovery.UserId = 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 nil, result.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
return recovery, nil
|
return token, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPasswordRecovery(code string) (*model.PasswordRecovery, *model.AppError) {
|
func GetPasswordRecoveryToken(token string) (*model.Token, *model.AppError) {
|
||||||
if result := <-Srv.Store.PasswordRecovery().GetByCode(code); result.Err != nil {
|
if result := <-Srv.Store.Token().GetByToken(token); result.Err != nil {
|
||||||
return nil, model.NewAppError("GetPasswordRecovery", "api.user.reset_password.invalid_link.app_error", nil, result.Err.Error(), http.StatusBadRequest)
|
return nil, model.NewAppError("GetPasswordRecoveryToken", "api.user.reset_password.invalid_link.app_error", nil, result.Err.Error(), http.StatusBadRequest)
|
||||||
} else {
|
} 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 {
|
func DeleteToken(token *model.Token) *model.AppError {
|
||||||
if result := <-Srv.Store.PasswordRecovery().Delete(userId); result.Err != nil {
|
if result := <-Srv.Store.Token().Delete(token.Token); result.Err != nil {
|
||||||
return result.Err
|
return result.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1250,10 +1258,6 @@ func PermanentDeleteUser(user *model.User) *model.AppError {
|
|||||||
return result.Err
|
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)
|
l4g.Warn(utils.T("api.user.permanent_delete_user.deleted.warn"), user.Email, user.Id)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -1272,6 +1276,63 @@ func PermanentDeleteAllUsers() *model.AppError {
|
|||||||
return nil
|
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 {
|
func VerifyUserEmail(userId string) *model.AppError {
|
||||||
if err := (<-Srv.Store.User().VerifyEmail(userId)).Err; err != nil {
|
if err := (<-Srv.Store.User().VerifyEmail(userId)).Err; err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ func runServer(configFileLocation string) {
|
|||||||
go runSecurityJob()
|
go runSecurityJob()
|
||||||
go runDiagnosticsJob()
|
go runDiagnosticsJob()
|
||||||
|
|
||||||
|
go runTokenCleanupJob()
|
||||||
|
|
||||||
if complianceI := einterfaces.GetComplianceInterface(); complianceI != nil {
|
if complianceI := einterfaces.GetComplianceInterface(); complianceI != nil {
|
||||||
complianceI.StartComplianceDailyJob()
|
complianceI.StartComplianceDailyJob()
|
||||||
}
|
}
|
||||||
@@ -139,6 +141,11 @@ func runDiagnosticsJob() {
|
|||||||
model.CreateRecurringTask("Diagnostics", doDiagnostics, time.Hour*24)
|
model.CreateRecurringTask("Diagnostics", doDiagnostics, time.Hour*24)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runTokenCleanupJob() {
|
||||||
|
doTokenCleanup()
|
||||||
|
model.CreateRecurringTask("Token Cleanup", doTokenCleanup, time.Hour*1)
|
||||||
|
}
|
||||||
|
|
||||||
func resetStatuses() {
|
func resetStatuses() {
|
||||||
if result := <-app.Srv.Store.Status().ResetAll(); result.Err != nil {
|
if result := <-app.Srv.Store.Status().ResetAll(); result.Err != nil {
|
||||||
l4g.Error(utils.T("mattermost.reset_status.error"), result.Err.Error())
|
l4g.Error(utils.T("mattermost.reset_status.error"), result.Err.Error())
|
||||||
@@ -169,3 +176,7 @@ func doDiagnostics() {
|
|||||||
app.SendDailyDiagnostics()
|
app.SendDailyDiagnostics()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func doTokenCleanup() {
|
||||||
|
app.Srv.Store.Token().Cleanup()
|
||||||
|
}
|
||||||
|
|||||||
16
i18n/en.json
16
i18n/en.json
@@ -3575,6 +3575,14 @@
|
|||||||
"id": "mattermost.working_dir",
|
"id": "mattermost.working_dir",
|
||||||
"translation": "Current working directory is %v"
|
"translation": "Current working directory is %v"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "model.token.is_valid.size",
|
||||||
|
"translation": "Invalid token."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "model.token.is_valid.expiry",
|
||||||
|
"translation": "Invalid token expiry"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "model.access.is_valid.access_token.app_error",
|
"id": "model.access.is_valid.access_token.app_error",
|
||||||
"translation": "Invalid access token"
|
"translation": "Invalid access token"
|
||||||
@@ -4411,6 +4419,10 @@
|
|||||||
"id": "model.user.is_valid.auth_data.app_error",
|
"id": "model.user.is_valid.auth_data.app_error",
|
||||||
"translation": "Invalid auth data"
|
"translation": "Invalid auth data"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "model.user.is_valid.password_limit.app_error",
|
||||||
|
"translation": "Unable to set a password over 72 charactors due to the limitations of bcrypt."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "model.user.is_valid.auth_data_pwd.app_error",
|
"id": "model.user.is_valid.auth_data_pwd.app_error",
|
||||||
"translation": "Invalid user, password and auth data cannot both be set"
|
"translation": "Invalid user, password and auth data cannot both be set"
|
||||||
@@ -4591,10 +4603,6 @@
|
|||||||
"id": "store.sql.dialect_driver.panic",
|
"id": "store.sql.dialect_driver.panic",
|
||||||
"translation": "Failed to create dialect specific driver %v"
|
"translation": "Failed to create dialect specific driver %v"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "store.sql.drop_column.critical",
|
|
||||||
"translation": "Failed to drop column %v"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "store.sql.incorrect_mac",
|
"id": "store.sql.incorrect_mac",
|
||||||
"translation": "Incorrect MAC for the given ciphertext"
|
"translation": "Incorrect MAC for the given ciphertext"
|
||||||
|
|||||||
@@ -751,8 +751,8 @@ func (c *Client4) SendPasswordResetEmail(email string) (bool, *Response) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ResetPassword uses a recovery code to update reset a user's password.
|
// ResetPassword uses a recovery code to update reset a user's password.
|
||||||
func (c *Client4) ResetPassword(code, newPassword string) (bool, *Response) {
|
func (c *Client4) ResetPassword(token, newPassword string) (bool, *Response) {
|
||||||
requestBody := map[string]string{"code": code, "new_password": newPassword}
|
requestBody := map[string]string{"token": token, "new_password": newPassword}
|
||||||
if r, err := c.DoApiPost(c.GetUsersRoute()+"/password/reset", MapToJson(requestBody)); err != nil {
|
if r, err := c.DoApiPost(c.GetUsersRoute()+"/password/reset", MapToJson(requestBody)); err != nil {
|
||||||
return false, &Response{StatusCode: r.StatusCode, Error: err}
|
return false, &Response{StatusCode: r.StatusCode, Error: err}
|
||||||
} else {
|
} else {
|
||||||
@@ -821,9 +821,9 @@ func (c *Client4) GetUserAudits(userId string, page int, perPage int, etag strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyUserEmail will verify a user's email using user id and hash strings.
|
// VerifyUserEmail will verify a user's email using the supplied token.
|
||||||
func (c *Client4) VerifyUserEmail(userId, hashId string) (bool, *Response) {
|
func (c *Client4) VerifyUserEmail(token string) (bool, *Response) {
|
||||||
requestBody := map[string]string{"user_id": userId, "hash_id": hashId}
|
requestBody := map[string]string{"token": token}
|
||||||
if r, err := c.DoApiPost(c.GetUsersRoute()+"/email/verify", MapToJson(requestBody)); err != nil {
|
if r, err := c.DoApiPost(c.GetUsersRoute()+"/email/verify", MapToJson(requestBody)); err != nil {
|
||||||
return false, &Response{StatusCode: r.StatusCode, Error: err}
|
return false, &Response{StatusCode: r.StatusCode, Error: err}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See License.txt for license information.
|
|
||||||
|
|
||||||
package model
|
|
||||||
|
|
||||||
const (
|
|
||||||
PASSWORD_RECOVERY_CODE_SIZE = 128
|
|
||||||
PASSWORD_RECOVER_EXPIRY_TIME = 1000 * 60 * 60 // 1 hour
|
|
||||||
)
|
|
||||||
|
|
||||||
type PasswordRecovery struct {
|
|
||||||
UserId string
|
|
||||||
Code string
|
|
||||||
CreateAt int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PasswordRecovery) IsValid() *AppError {
|
|
||||||
|
|
||||||
if len(p.UserId) != 26 {
|
|
||||||
return NewLocAppError("User.IsValid", "model.password_recovery.is_valid.user_id.app_error", nil, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(p.Code) != PASSWORD_RECOVERY_CODE_SIZE {
|
|
||||||
return NewLocAppError("User.IsValid", "model.password_recovery.is_valid.code.app_error", nil, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.CreateAt == 0 {
|
|
||||||
return NewLocAppError("User.IsValid", "model.password_recovery.is_valid.create_at.app_error", nil, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PasswordRecovery) PreSave() {
|
|
||||||
p.Code = NewRandomString(PASSWORD_RECOVERY_CODE_SIZE)
|
|
||||||
p.CreateAt = GetMillis()
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See License.txt for license information.
|
|
||||||
|
|
||||||
package model
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPasswordRecoveryIsValid(t *testing.T) {
|
|
||||||
// Valid example.
|
|
||||||
p := PasswordRecovery{
|
|
||||||
UserId: NewId(),
|
|
||||||
Code: strings.Repeat("a", 128),
|
|
||||||
CreateAt: GetMillis(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := p.IsValid(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Various invalid ones.
|
|
||||||
p.UserId = "abc"
|
|
||||||
if err := p.IsValid(); err == nil {
|
|
||||||
t.Fatal("Should have failed validation")
|
|
||||||
}
|
|
||||||
|
|
||||||
p.UserId = NewId()
|
|
||||||
p.Code = "abc"
|
|
||||||
if err := p.IsValid(); err == nil {
|
|
||||||
t.Fatal("Should have failed validation")
|
|
||||||
}
|
|
||||||
|
|
||||||
p.Code = strings.Repeat("a", 128)
|
|
||||||
p.CreateAt = 0
|
|
||||||
if err := p.IsValid(); err == nil {
|
|
||||||
t.Fatal("Should have failed validation")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPasswordRecoveryPreSave(t *testing.T) {
|
|
||||||
p := PasswordRecovery{
|
|
||||||
UserId: NewId(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check it's valid after running PreSave
|
|
||||||
p.PreSave()
|
|
||||||
|
|
||||||
if err := p.IsValid(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
39
model/token.go
Обычный файл
39
model/token.go
Обычный файл
@@ -0,0 +1,39 @@
|
|||||||
|
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
const (
|
||||||
|
TOKEN_SIZE = 128
|
||||||
|
MAX_TOKEN_EXIPRY_TIME = 1000 * 60 * 60 * 24 // 24 hour
|
||||||
|
)
|
||||||
|
|
||||||
|
type Token struct {
|
||||||
|
Token string
|
||||||
|
CreateAt int64
|
||||||
|
Type string
|
||||||
|
Extra string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewToken(tokentype, extra string) *Token {
|
||||||
|
return &Token{
|
||||||
|
Token: NewRandomString(TOKEN_SIZE),
|
||||||
|
CreateAt: GetMillis(),
|
||||||
|
Type: tokentype,
|
||||||
|
Extra: extra,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Token) IsValid() *AppError {
|
||||||
|
if len(t.Token) != TOKEN_SIZE {
|
||||||
|
return NewAppError("Token.IsValid", "model.token.is_valid.size", nil, "", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.CreateAt == 0 {
|
||||||
|
return NewAppError("Token.IsValid", "model.token.is_valid.expiry", nil, "", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -130,6 +130,10 @@ func (u *User) IsValid() *AppError {
|
|||||||
return InvalidUserError("auth_data_pwd", u.Id)
|
return InvalidUserError("auth_data_pwd", u.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(u.Password) > 72 {
|
||||||
|
return InvalidUserError("password_limit", u.Id)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,128 +0,0 @@
|
|||||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See License.txt for license information.
|
|
||||||
|
|
||||||
package store
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/mattermost/platform/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SqlPasswordRecoveryStore struct {
|
|
||||||
*SqlStore
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSqlPasswordRecoveryStore(sqlStore *SqlStore) PasswordRecoveryStore {
|
|
||||||
s := &SqlPasswordRecoveryStore{sqlStore}
|
|
||||||
|
|
||||||
for _, db := range sqlStore.GetAllConns() {
|
|
||||||
table := db.AddTableWithName(model.PasswordRecovery{}, "PasswordRecovery").SetKeys(false, "UserId")
|
|
||||||
table.ColMap("UserId").SetMaxSize(26)
|
|
||||||
table.ColMap("Code").SetMaxSize(128)
|
|
||||||
}
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SqlPasswordRecoveryStore) CreateIndexesIfNotExists() {
|
|
||||||
s.CreateIndexIfNotExists("idx_password_recovery_code", "PasswordRecovery", "Code")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SqlPasswordRecoveryStore) SaveOrUpdate(recovery *model.PasswordRecovery) StoreChannel {
|
|
||||||
|
|
||||||
storeChannel := make(StoreChannel, 1)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
result := StoreResult{}
|
|
||||||
|
|
||||||
recovery.PreSave()
|
|
||||||
if result.Err = recovery.IsValid(); result.Err != nil {
|
|
||||||
storeChannel <- result
|
|
||||||
close(storeChannel)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.GetReplica().SelectOne(&model.PasswordRecovery{}, "SELECT * FROM PasswordRecovery WHERE UserId = :UserId", map[string]interface{}{"UserId": recovery.UserId}); err == nil {
|
|
||||||
if _, err := s.GetMaster().Update(recovery); err != nil {
|
|
||||||
result.Err = model.NewLocAppError("SqlPasswordRecoveryStore.SaveOrUpdate", "store.sql_recover.update.app_error", nil, "")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := s.GetMaster().Insert(recovery); err != nil {
|
|
||||||
result.Err = model.NewLocAppError("SqlPasswordRecoveryStore.SaveOrUpdate", "store.sql_recover.save.app_error", nil, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
storeChannel <- result
|
|
||||||
close(storeChannel)
|
|
||||||
}()
|
|
||||||
|
|
||||||
return storeChannel
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SqlPasswordRecoveryStore) Delete(userId string) StoreChannel {
|
|
||||||
|
|
||||||
storeChannel := make(StoreChannel, 1)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
result := StoreResult{}
|
|
||||||
|
|
||||||
if _, err := s.GetMaster().Exec("DELETE FROM PasswordRecovery WHERE UserId = :UserId", map[string]interface{}{"UserId": userId}); err != nil {
|
|
||||||
result.Err = model.NewLocAppError("SqlPasswordRecoveryStore.Delete", "store.sql_recover.delete.app_error", nil, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
storeChannel <- result
|
|
||||||
close(storeChannel)
|
|
||||||
}()
|
|
||||||
|
|
||||||
return storeChannel
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SqlPasswordRecoveryStore) Get(userId string) StoreChannel {
|
|
||||||
|
|
||||||
storeChannel := make(StoreChannel, 1)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
result := StoreResult{}
|
|
||||||
|
|
||||||
recovery := model.PasswordRecovery{}
|
|
||||||
|
|
||||||
if err := s.GetReplica().SelectOne(&recovery, "SELECT * FROM PasswordRecovery WHERE UserId = :UserId", map[string]interface{}{"UserId": userId}); err != nil {
|
|
||||||
result.Err = model.NewLocAppError("SqlPasswordRecoveryStore.Get", "store.sql_recover.get.app_error", nil, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
result.Data = &recovery
|
|
||||||
|
|
||||||
storeChannel <- result
|
|
||||||
close(storeChannel)
|
|
||||||
}()
|
|
||||||
|
|
||||||
return storeChannel
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SqlPasswordRecoveryStore) GetByCode(code string) StoreChannel {
|
|
||||||
|
|
||||||
storeChannel := make(StoreChannel, 1)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
result := StoreResult{}
|
|
||||||
|
|
||||||
recovery := model.PasswordRecovery{}
|
|
||||||
|
|
||||||
if err := s.GetReplica().SelectOne(&recovery, "SELECT * FROM PasswordRecovery WHERE Code = :Code", map[string]interface{}{"Code": code}); err != nil {
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
result.Err = model.NewAppError("SqlPasswordRecoveryStore.GetByCode", "store.sql_recover.get_by_code.app_error", nil, "", http.StatusBadRequest)
|
|
||||||
} else {
|
|
||||||
result.Err = model.NewAppError("SqlPasswordRecoveryStore.GetByCode", "store.sql_recover.get_by_code.app_error", nil, "", http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result.Data = &recovery
|
|
||||||
|
|
||||||
storeChannel <- result
|
|
||||||
close(storeChannel)
|
|
||||||
}()
|
|
||||||
|
|
||||||
return storeChannel
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See License.txt for license information.
|
|
||||||
|
|
||||||
package store
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/mattermost/platform/model"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSqlPasswordRecoveryGet(t *testing.T) {
|
|
||||||
Setup()
|
|
||||||
|
|
||||||
recovery := &model.PasswordRecovery{UserId: "12345678901234567890123456"}
|
|
||||||
Must(store.PasswordRecovery().SaveOrUpdate(recovery))
|
|
||||||
|
|
||||||
result := <-store.PasswordRecovery().Get(recovery.UserId)
|
|
||||||
rrecovery := result.Data.(*model.PasswordRecovery)
|
|
||||||
if rrecovery.Code != recovery.Code {
|
|
||||||
t.Fatal("codes didn't match")
|
|
||||||
}
|
|
||||||
|
|
||||||
result2 := <-store.PasswordRecovery().GetByCode(recovery.Code)
|
|
||||||
rrecovery2 := result2.Data.(*model.PasswordRecovery)
|
|
||||||
if rrecovery2.Code != recovery.Code {
|
|
||||||
t.Fatal("codes didn't match")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSqlPasswordRecoverySaveOrUpdate(t *testing.T) {
|
|
||||||
Setup()
|
|
||||||
|
|
||||||
recovery := &model.PasswordRecovery{UserId: "12345678901234567890123456"}
|
|
||||||
|
|
||||||
if err := (<-store.PasswordRecovery().SaveOrUpdate(recovery)).Err; err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// not duplicate, testing update
|
|
||||||
if err := (<-store.PasswordRecovery().SaveOrUpdate(recovery)).Err; err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSqlPasswordRecoveryDelete(t *testing.T) {
|
|
||||||
Setup()
|
|
||||||
|
|
||||||
recovery := &model.PasswordRecovery{UserId: "12345678901234567890123456"}
|
|
||||||
Must(store.PasswordRecovery().SaveOrUpdate(recovery))
|
|
||||||
|
|
||||||
if err := (<-store.PasswordRecovery().Delete(recovery.UserId)).Err; err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -62,6 +62,7 @@ const (
|
|||||||
EXIT_REMOVE_INDEX_POSTGRES = 121
|
EXIT_REMOVE_INDEX_POSTGRES = 121
|
||||||
EXIT_REMOVE_INDEX_MYSQL = 122
|
EXIT_REMOVE_INDEX_MYSQL = 122
|
||||||
EXIT_REMOVE_INDEX_MISSING = 123
|
EXIT_REMOVE_INDEX_MISSING = 123
|
||||||
|
EXIT_REMOVE_TABLE = 134
|
||||||
)
|
)
|
||||||
|
|
||||||
type SqlStore struct {
|
type SqlStore struct {
|
||||||
@@ -80,7 +81,7 @@ type SqlStore struct {
|
|||||||
command CommandStore
|
command CommandStore
|
||||||
preference PreferenceStore
|
preference PreferenceStore
|
||||||
license LicenseStore
|
license LicenseStore
|
||||||
recovery PasswordRecoveryStore
|
token TokenStore
|
||||||
emoji EmojiStore
|
emoji EmojiStore
|
||||||
status StatusStore
|
status StatusStore
|
||||||
fileInfo FileInfoStore
|
fileInfo FileInfoStore
|
||||||
@@ -131,7 +132,7 @@ func NewSqlStore() Store {
|
|||||||
sqlStore.command = NewSqlCommandStore(sqlStore)
|
sqlStore.command = NewSqlCommandStore(sqlStore)
|
||||||
sqlStore.preference = NewSqlPreferenceStore(sqlStore)
|
sqlStore.preference = NewSqlPreferenceStore(sqlStore)
|
||||||
sqlStore.license = NewSqlLicenseStore(sqlStore)
|
sqlStore.license = NewSqlLicenseStore(sqlStore)
|
||||||
sqlStore.recovery = NewSqlPasswordRecoveryStore(sqlStore)
|
sqlStore.token = NewSqlTokenStore(sqlStore)
|
||||||
sqlStore.emoji = NewSqlEmojiStore(sqlStore)
|
sqlStore.emoji = NewSqlEmojiStore(sqlStore)
|
||||||
sqlStore.status = NewSqlStatusStore(sqlStore)
|
sqlStore.status = NewSqlStatusStore(sqlStore)
|
||||||
sqlStore.fileInfo = NewSqlFileInfoStore(sqlStore)
|
sqlStore.fileInfo = NewSqlFileInfoStore(sqlStore)
|
||||||
@@ -159,7 +160,7 @@ func NewSqlStore() Store {
|
|||||||
sqlStore.command.(*SqlCommandStore).CreateIndexesIfNotExists()
|
sqlStore.command.(*SqlCommandStore).CreateIndexesIfNotExists()
|
||||||
sqlStore.preference.(*SqlPreferenceStore).CreateIndexesIfNotExists()
|
sqlStore.preference.(*SqlPreferenceStore).CreateIndexesIfNotExists()
|
||||||
sqlStore.license.(*SqlLicenseStore).CreateIndexesIfNotExists()
|
sqlStore.license.(*SqlLicenseStore).CreateIndexesIfNotExists()
|
||||||
sqlStore.recovery.(*SqlPasswordRecoveryStore).CreateIndexesIfNotExists()
|
sqlStore.token.(*SqlTokenStore).CreateIndexesIfNotExists()
|
||||||
sqlStore.emoji.(*SqlEmojiStore).CreateIndexesIfNotExists()
|
sqlStore.emoji.(*SqlEmojiStore).CreateIndexesIfNotExists()
|
||||||
sqlStore.status.(*SqlStatusStore).CreateIndexesIfNotExists()
|
sqlStore.status.(*SqlStatusStore).CreateIndexesIfNotExists()
|
||||||
sqlStore.fileInfo.(*SqlFileInfoStore).CreateIndexesIfNotExists()
|
sqlStore.fileInfo.(*SqlFileInfoStore).CreateIndexesIfNotExists()
|
||||||
@@ -388,7 +389,7 @@ func (ss *SqlStore) RemoveColumnIfExists(tableName string, columnName string) bo
|
|||||||
|
|
||||||
_, err := ss.GetMaster().Exec("ALTER TABLE " + tableName + " DROP COLUMN " + columnName)
|
_, err := ss.GetMaster().Exec("ALTER TABLE " + tableName + " DROP COLUMN " + columnName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l4g.Critical(utils.T("store.sql.drop_column.critical"), err)
|
l4g.Critical("Failed to drop column %v", err)
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
os.Exit(EXIT_REMOVE_COLUMN)
|
os.Exit(EXIT_REMOVE_COLUMN)
|
||||||
}
|
}
|
||||||
@@ -396,6 +397,21 @@ func (ss *SqlStore) RemoveColumnIfExists(tableName string, columnName string) bo
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ss *SqlStore) RemoveTableIfExists(tableName string) bool {
|
||||||
|
if !ss.DoesTableExist(tableName) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := ss.GetMaster().Exec("DROP TABLE " + tableName)
|
||||||
|
if err != nil {
|
||||||
|
l4g.Critical("Failed to drop table %v", err)
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
os.Exit(EXIT_REMOVE_TABLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (ss *SqlStore) RenameColumnIfExists(tableName string, oldColumnName string, newColumnName string, colType string) bool {
|
func (ss *SqlStore) RenameColumnIfExists(tableName string, oldColumnName string, newColumnName string, colType string) bool {
|
||||||
if !ss.DoesColumnExist(tableName, oldColumnName) {
|
if !ss.DoesColumnExist(tableName, oldColumnName) {
|
||||||
return false
|
return false
|
||||||
@@ -667,8 +683,8 @@ func (ss *SqlStore) License() LicenseStore {
|
|||||||
return ss.license
|
return ss.license
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *SqlStore) PasswordRecovery() PasswordRecoveryStore {
|
func (ss *SqlStore) Token() TokenStore {
|
||||||
return ss.recovery
|
return ss.token
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *SqlStore) Emoji() EmojiStore {
|
func (ss *SqlStore) Emoji() EmojiStore {
|
||||||
|
|||||||
108
store/sql_tokens_store.go
Обычный файл
108
store/sql_tokens_store.go
Обычный файл
@@ -0,0 +1,108 @@
|
|||||||
|
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
package store
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
l4g "github.com/alecthomas/log4go"
|
||||||
|
|
||||||
|
"github.com/mattermost/platform/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SqlTokenStore struct {
|
||||||
|
*SqlStore
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSqlTokenStore(sqlStore *SqlStore) TokenStore {
|
||||||
|
s := &SqlTokenStore{sqlStore}
|
||||||
|
|
||||||
|
for _, db := range sqlStore.GetAllConns() {
|
||||||
|
table := db.AddTableWithName(model.Token{}, "Tokens").SetKeys(false, "Token")
|
||||||
|
table.ColMap("Type").SetMaxSize(64)
|
||||||
|
table.ColMap("Extra").SetMaxSize(128)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SqlTokenStore) CreateIndexesIfNotExists() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SqlTokenStore) Save(token *model.Token) StoreChannel {
|
||||||
|
|
||||||
|
storeChannel := make(StoreChannel, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
result := StoreResult{}
|
||||||
|
|
||||||
|
if result.Err = token.IsValid(); result.Err != nil {
|
||||||
|
storeChannel <- result
|
||||||
|
close(storeChannel)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.GetMaster().Insert(token); err != nil {
|
||||||
|
result.Err = model.NewLocAppError("SqlTokenStore.Save", "store.sql_recover.save.app_error", nil, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
storeChannel <- result
|
||||||
|
close(storeChannel)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return storeChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SqlTokenStore) Delete(token string) StoreChannel {
|
||||||
|
|
||||||
|
storeChannel := make(StoreChannel, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
result := StoreResult{}
|
||||||
|
|
||||||
|
if _, err := s.GetMaster().Exec("DELETE FROM Tokens WHERE Token = :Token", map[string]interface{}{"Token": token}); err != nil {
|
||||||
|
result.Err = model.NewLocAppError("SqlTokenStore.Delete", "store.sql_recover.delete.app_error", nil, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
storeChannel <- result
|
||||||
|
close(storeChannel)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return storeChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SqlTokenStore) GetByToken(tokenString string) StoreChannel {
|
||||||
|
|
||||||
|
storeChannel := make(StoreChannel, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
result := StoreResult{}
|
||||||
|
|
||||||
|
token := model.Token{}
|
||||||
|
|
||||||
|
if err := s.GetReplica().SelectOne(&token, "SELECT * FROM Tokens WHERE Token = :Token", map[string]interface{}{"Token": tokenString}); err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
result.Err = model.NewAppError("SqlTokenStore.GetByToken", "store.sql_recover.get_by_code.app_error", nil, err.Error(), http.StatusBadRequest)
|
||||||
|
} else {
|
||||||
|
result.Err = model.NewAppError("SqlTokenStore.GetByToken", "store.sql_recover.get_by_code.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Data = &token
|
||||||
|
|
||||||
|
storeChannel <- result
|
||||||
|
close(storeChannel)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return storeChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SqlTokenStore) Cleanup() {
|
||||||
|
l4g.Debug("Cleaning up token store.")
|
||||||
|
deltime := model.GetMillis() - model.MAX_TOKEN_EXIPRY_TIME
|
||||||
|
if _, err := s.GetMaster().Exec("DELETE FROM Tokens WHERE CreateAt < :DelTime", map[string]interface{}{"DelTime": deltime}); err != nil {
|
||||||
|
l4g.Error("Unable to cleanup token store.")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -258,6 +258,7 @@ func UpgradeDatabaseToVersion39(sqlStore *SqlStore) {
|
|||||||
// TODO: Uncomment following condition when version 3.9.0 is released
|
// TODO: Uncomment following condition when version 3.9.0 is released
|
||||||
//if shouldPerformUpgrade(sqlStore, VERSION_3_8_0, VERSION_3_9_0) {
|
//if shouldPerformUpgrade(sqlStore, VERSION_3_8_0, VERSION_3_9_0) {
|
||||||
sqlStore.CreateColumnIfNotExists("OAuthAccessData", "Scope", "varchar(128)", "varchar(128)", model.DEFAULT_SCOPE)
|
sqlStore.CreateColumnIfNotExists("OAuthAccessData", "Scope", "varchar(128)", "varchar(128)", model.DEFAULT_SCOPE)
|
||||||
|
sqlStore.RemoveTableIfExists("PasswordRecovery")
|
||||||
|
|
||||||
// saveSchemaVersion(sqlStore, VERSION_3_9_0)
|
// saveSchemaVersion(sqlStore, VERSION_3_9_0)
|
||||||
//}
|
//}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ type Store interface {
|
|||||||
Command() CommandStore
|
Command() CommandStore
|
||||||
Preference() PreferenceStore
|
Preference() PreferenceStore
|
||||||
License() LicenseStore
|
License() LicenseStore
|
||||||
PasswordRecovery() PasswordRecoveryStore
|
Token() TokenStore
|
||||||
Emoji() EmojiStore
|
Emoji() EmojiStore
|
||||||
Status() StatusStore
|
Status() StatusStore
|
||||||
FileInfo() FileInfoStore
|
FileInfo() FileInfoStore
|
||||||
@@ -322,11 +322,11 @@ type LicenseStore interface {
|
|||||||
Get(id string) StoreChannel
|
Get(id string) StoreChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
type PasswordRecoveryStore interface {
|
type TokenStore interface {
|
||||||
SaveOrUpdate(recovery *model.PasswordRecovery) StoreChannel
|
Save(recovery *model.Token) StoreChannel
|
||||||
Delete(userId string) StoreChannel
|
Delete(token string) StoreChannel
|
||||||
Get(userId string) StoreChannel
|
GetByToken(token string) StoreChannel
|
||||||
GetByCode(code string) StoreChannel
|
Cleanup()
|
||||||
}
|
}
|
||||||
|
|
||||||
type EmojiStore interface {
|
type EmojiStore interface {
|
||||||
|
|||||||
16
utils/hash.go
Обычный файл
16
utils/hash.go
Обычный файл
@@ -0,0 +1,16 @@
|
|||||||
|
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HashSha256(text string) string {
|
||||||
|
hash := sha256.New()
|
||||||
|
hash.Write([]byte(text))
|
||||||
|
|
||||||
|
return fmt.Sprintf("%x", hash.Sum(nil))
|
||||||
|
}
|
||||||
@@ -655,10 +655,9 @@ export function updatePassword(userId, currentPassword, newPassword, success, er
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function verifyEmail(uid, hid, success, error) {
|
export function verifyEmail(token, success, error) {
|
||||||
Client.verifyEmail(
|
Client.verifyEmail(
|
||||||
uid,
|
token,
|
||||||
hid,
|
|
||||||
(data) => {
|
(data) => {
|
||||||
if (success) {
|
if (success) {
|
||||||
success(data);
|
success(data);
|
||||||
@@ -672,9 +671,9 @@ export function verifyEmail(uid, hid, success, error) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resetPassword(code, password, success, error) {
|
export function resetPassword(token, password, success, error) {
|
||||||
Client.resetPassword(
|
Client.resetPassword(
|
||||||
code,
|
token,
|
||||||
password,
|
password,
|
||||||
() => {
|
() => {
|
||||||
browserHistory.push('/login?extra=' + ActionTypes.PASSWORD_CHANGE);
|
browserHistory.push('/login?extra=' + ActionTypes.PASSWORD_CHANGE);
|
||||||
|
|||||||
@@ -1319,19 +1319,19 @@ export default class Client {
|
|||||||
this.trackEvent('api', 'api_channels_set_active', {channel_id: id});
|
this.trackEvent('api', 'api_channels_set_active', {channel_id: id});
|
||||||
}
|
}
|
||||||
|
|
||||||
verifyEmail(uid, hid, success, error) {
|
verifyEmail(token, success, error) {
|
||||||
request.
|
request.
|
||||||
post(`${this.getUsersRoute()}/verify_email`).
|
post(`${this.url}/api/v4/users/email/verify`).
|
||||||
set(this.defaultHeaders).
|
set(this.defaultHeaders).
|
||||||
type('application/json').
|
type('application/json').
|
||||||
accept('application/json').
|
accept('application/json').
|
||||||
send({uid, hid}).
|
send({token}).
|
||||||
end(this.handleResponse.bind(this, 'verifyEmail', success, error));
|
end(this.handleResponse.bind(this, 'verifyEmail', success, error));
|
||||||
}
|
}
|
||||||
|
|
||||||
resendVerification(email, success, error) {
|
resendVerification(email, success, error) {
|
||||||
request.
|
request.
|
||||||
post(`${this.getUsersRoute()}/resend_verification`).
|
post(`${this.url}/api/v4/users/email/verify/send`).
|
||||||
set(this.defaultHeaders).
|
set(this.defaultHeaders).
|
||||||
type('application/json').
|
type('application/json').
|
||||||
accept('application/json').
|
accept('application/json').
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ export default class DoVerifyEmail extends React.Component {
|
|||||||
}
|
}
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
verifyEmail(
|
verifyEmail(
|
||||||
this.props.location.query.uid,
|
this.props.location.query.token,
|
||||||
this.props.location.query.hid,
|
|
||||||
() => {
|
() => {
|
||||||
browserHistory.push('/login?extra=verified&email=' + encodeURIComponent(this.props.location.query.email));
|
browserHistory.push('/login?extra=verified&email=' + encodeURIComponent(this.props.location.query.email));
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class PasswordResetForm extends React.Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
resetPassword(
|
resetPassword(
|
||||||
this.props.location.query.code,
|
this.props.location.query.token,
|
||||||
password,
|
password,
|
||||||
() => {
|
() => {
|
||||||
this.setState({error: null});
|
this.setState({error: null});
|
||||||
|
|||||||
@@ -655,13 +655,12 @@ describe('Client.User', function() {
|
|||||||
TestHelper.initBasic(done, () => {
|
TestHelper.initBasic(done, () => {
|
||||||
TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
|
TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
|
||||||
TestHelper.basicClient().verifyEmail(
|
TestHelper.basicClient().verifyEmail(
|
||||||
'junk',
|
|
||||||
'junk',
|
'junk',
|
||||||
function() {
|
function() {
|
||||||
done.fail(new Error('should be invalid'));
|
done.fail(new Error('should be invalid'));
|
||||||
},
|
},
|
||||||
function(err) {
|
function(err) {
|
||||||
expect(err.id).toBe('api.context.invalid_param.app_error');
|
expect(err.id).toBe('api.context.invalid_body_param.app_error');
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user