Merge pull request #1745 from mattermost/plt-1118
PLT-1118 Add ability to switch between SSO and email account
Этот коммит содержится в:
43
api/templates/signin_change_body.html
Обычный файл
43
api/templates/signin_change_body.html
Обычный файл
@@ -0,0 +1,43 @@
|
|||||||
|
{{define "signin_change_body"}}
|
||||||
|
|
||||||
|
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="margin-top: 20px; line-height: 1.7; color: #555;">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 660px; font-family: Helvetica, Arial, sans-serif; font-size: 14px; background: #FFF;">
|
||||||
|
<tr>
|
||||||
|
<td style="border: 1px solid #ddd;">
|
||||||
|
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 20px 20px 10px; text-align:left;">
|
||||||
|
<img src="{{.Props.SiteURL}}/static/images/logo-email.png" width="130px" style="opacity: 0.5" alt="">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" style="padding: 20px 50px 0; text-align: center; margin: 0 auto">
|
||||||
|
<tr>
|
||||||
|
<td style="border-bottom: 1px solid #ddd; padding: 0 0 20px;">
|
||||||
|
<h2 style="font-weight: normal; margin-top: 10px;">You updated your sign-in method</h2>
|
||||||
|
<p>You updated your sign-in method for {{.Props.TeamDisplayName}} on {{ .Props.TeamURL }} to {{.Props.Method}}.<br>If this change wasn't initiated by you, please contact your system administrator.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
{{template "email_info" . }}
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
{{template "email_footer" . }}
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
|
||||||
1
api/templates/signin_change_subject.html
Обычный файл
1
api/templates/signin_change_subject.html
Обычный файл
@@ -0,0 +1 @@
|
|||||||
|
{{define "signin_change_subject"}}You updated your sign-in method for {{.Props.TeamDisplayName}} on {{ .ClientCfg.SiteName }}{{end}}
|
||||||
340
api/user.go
340
api/user.go
@@ -47,6 +47,8 @@ func InitUser(r *mux.Router) {
|
|||||||
sr.Handle("/logout", ApiUserRequired(logout)).Methods("POST")
|
sr.Handle("/logout", ApiUserRequired(logout)).Methods("POST")
|
||||||
sr.Handle("/login_ldap", ApiAppHandler(loginLdap)).Methods("POST")
|
sr.Handle("/login_ldap", ApiAppHandler(loginLdap)).Methods("POST")
|
||||||
sr.Handle("/revoke_session", ApiUserRequired(revokeSession)).Methods("POST")
|
sr.Handle("/revoke_session", ApiUserRequired(revokeSession)).Methods("POST")
|
||||||
|
sr.Handle("/switch_to_sso", ApiAppHandler(switchToSSO)).Methods("POST")
|
||||||
|
sr.Handle("/switch_to_email", ApiUserRequired(switchToEmail)).Methods("POST")
|
||||||
|
|
||||||
sr.Handle("/newimage", ApiUserRequired(uploadProfileImage)).Methods("POST")
|
sr.Handle("/newimage", ApiUserRequired(uploadProfileImage)).Methods("POST")
|
||||||
|
|
||||||
@@ -225,6 +227,70 @@ func CreateUser(team *model.Team, user *model.User) (*model.User, *model.AppErro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CreateOAuthUser(c *Context, w http.ResponseWriter, r *http.Request, service string, userData io.ReadCloser, team *model.Team) *model.User {
|
||||||
|
var user *model.User
|
||||||
|
provider := einterfaces.GetOauthProvider(service)
|
||||||
|
if provider == nil {
|
||||||
|
c.Err = model.NewAppError("CreateOAuthUser", service+" oauth not avlailable on this server", "")
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
user = provider.GetUserFromJson(userData)
|
||||||
|
}
|
||||||
|
|
||||||
|
if user == nil {
|
||||||
|
c.Err = model.NewAppError("CreateOAuthUser", "Could not create user out of "+service+" user object", "")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
suchan := Srv.Store.User().GetByAuth(team.Id, user.AuthData, service)
|
||||||
|
euchan := Srv.Store.User().GetByEmail(team.Id, user.Email)
|
||||||
|
|
||||||
|
if team.Email == "" {
|
||||||
|
team.Email = user.Email
|
||||||
|
if result := <-Srv.Store.Team().Update(team); result.Err != nil {
|
||||||
|
c.Err = result.Err
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
found := true
|
||||||
|
count := 0
|
||||||
|
for found {
|
||||||
|
if found = IsUsernameTaken(user.Username, team.Id); c.Err != nil {
|
||||||
|
return nil
|
||||||
|
} else if found {
|
||||||
|
user.Username = user.Username + strconv.Itoa(count)
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := <-suchan; result.Err == nil {
|
||||||
|
c.Err = model.NewAppError("signupCompleteOAuth", "This "+service+" account has already been used to sign up for team "+team.DisplayName, "email="+user.Email)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := <-euchan; result.Err == nil {
|
||||||
|
c.Err = model.NewAppError("signupCompleteOAuth", "Team "+team.DisplayName+" already has a user with the email address attached to your "+service+" account", "email="+user.Email)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
user.TeamId = team.Id
|
||||||
|
user.EmailVerified = true
|
||||||
|
|
||||||
|
ruser, err := CreateUser(team, user)
|
||||||
|
if err != nil {
|
||||||
|
c.Err = err
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
Login(c, w, r, ruser, "")
|
||||||
|
if c.Err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ruser
|
||||||
|
}
|
||||||
|
|
||||||
func sendWelcomeEmailAndForget(userId, email, teamName, teamDisplayName, siteURL, teamURL string, verified bool) {
|
func sendWelcomeEmailAndForget(userId, email, teamName, teamDisplayName, siteURL, teamURL string, verified bool) {
|
||||||
go func() {
|
go func() {
|
||||||
|
|
||||||
@@ -335,6 +401,11 @@ func LoginByEmail(c *Context, w http.ResponseWriter, r *http.Request, email, nam
|
|||||||
} else {
|
} else {
|
||||||
user := result.Data.(*model.User)
|
user := result.Data.(*model.User)
|
||||||
|
|
||||||
|
if len(user.AuthData) != 0 {
|
||||||
|
c.Err = model.NewAppError("LoginByEmail", "Please sign in using "+user.AuthService, "")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if checkUserLoginAttempts(c, user) && checkUserPassword(c, user, password) {
|
if checkUserLoginAttempts(c, user) && checkUserPassword(c, user, password) {
|
||||||
Login(c, w, r, user, deviceId)
|
Login(c, w, r, user, deviceId)
|
||||||
return user
|
return user
|
||||||
@@ -344,10 +415,36 @@ func LoginByEmail(c *Context, w http.ResponseWriter, r *http.Request, email, nam
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func LoginByOAuth(c *Context, w http.ResponseWriter, r *http.Request, service string, userData io.ReadCloser, team *model.Team) *model.User {
|
||||||
|
authData := ""
|
||||||
|
provider := einterfaces.GetOauthProvider(service)
|
||||||
|
if provider == nil {
|
||||||
|
c.Err = model.NewAppError("LoginByOAuth", service+" oauth not avlailable on this server", "")
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
authData = provider.GetAuthDataFromJson(userData)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(authData) == 0 {
|
||||||
|
c.Err = model.NewAppError("LoginByOAuth", "Could not parse auth data out of "+service+" user object", "")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var user *model.User
|
||||||
|
if result := <-Srv.Store.User().GetByAuth(team.Id, authData, service); result.Err != nil {
|
||||||
|
c.Err = result.Err
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
user = result.Data.(*model.User)
|
||||||
|
Login(c, w, r, user, "")
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func checkUserLoginAttempts(c *Context, user *model.User) bool {
|
func checkUserLoginAttempts(c *Context, user *model.User) bool {
|
||||||
if user.FailedAttempts >= utils.Cfg.ServiceSettings.MaximumLoginAttempts {
|
if user.FailedAttempts >= utils.Cfg.ServiceSettings.MaximumLoginAttempts {
|
||||||
c.LogAuditWithUserId(user.Id, "fail")
|
c.LogAuditWithUserId(user.Id, "fail")
|
||||||
c.Err = model.NewAppError("checkUserPassword", "Your account is locked because of too many failed password attempts. Please reset your password.", "user_id="+user.Id)
|
c.Err = model.NewAppError("checkUserLoginAttempts", "Your account is locked because of too many failed password attempts. Please reset your password.", "user_id="+user.Id)
|
||||||
c.Err.StatusCode = http.StatusForbidden
|
c.Err.StatusCode = http.StatusForbidden
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -1660,21 +1757,22 @@ func getStatuses(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAuthorizationCode(c *Context, w http.ResponseWriter, r *http.Request, teamName, service, redirectUri, loginHint string) {
|
func GetAuthorizationCode(c *Context, service, teamName string, props map[string]string, loginHint string) (string, *model.AppError) {
|
||||||
|
|
||||||
sso := utils.Cfg.GetSSOService(service)
|
sso := utils.Cfg.GetSSOService(service)
|
||||||
if sso != nil && !sso.Enable {
|
if sso != nil && !sso.Enable {
|
||||||
c.Err = model.NewAppError("GetAuthorizationCode", "Unsupported OAuth service provider", "service="+service)
|
return "", model.NewAppError("GetAuthorizationCode", "Unsupported OAuth service provider", "service="+service)
|
||||||
c.Err.StatusCode = http.StatusBadRequest
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clientId := sso.Id
|
clientId := sso.Id
|
||||||
endpoint := sso.AuthEndpoint
|
endpoint := sso.AuthEndpoint
|
||||||
scope := sso.Scope
|
scope := sso.Scope
|
||||||
|
|
||||||
stateProps := map[string]string{"team": teamName, "hash": model.HashPassword(clientId)}
|
props["hash"] = model.HashPassword(clientId)
|
||||||
state := b64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps)))
|
props["team"] = teamName
|
||||||
|
state := b64.StdEncoding.EncodeToString([]byte(model.MapToJson(props)))
|
||||||
|
|
||||||
|
redirectUri := c.GetSiteURL() + "/" + service + "/complete"
|
||||||
|
|
||||||
authUrl := endpoint + "?response_type=code&client_id=" + clientId + "&redirect_uri=" + url.QueryEscape(redirectUri) + "&state=" + url.QueryEscape(state)
|
authUrl := endpoint + "?response_type=code&client_id=" + clientId + "&redirect_uri=" + url.QueryEscape(redirectUri) + "&state=" + url.QueryEscape(state)
|
||||||
|
|
||||||
@@ -1686,18 +1784,18 @@ func GetAuthorizationCode(c *Context, w http.ResponseWriter, r *http.Request, te
|
|||||||
authUrl += "&login_hint=" + utils.UrlEncode(loginHint)
|
authUrl += "&login_hint=" + utils.UrlEncode(loginHint)
|
||||||
}
|
}
|
||||||
|
|
||||||
http.Redirect(w, r, authUrl, http.StatusFound)
|
return authUrl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func AuthorizeOAuthUser(service, code, state, redirectUri string) (io.ReadCloser, *model.Team, *model.AppError) {
|
func AuthorizeOAuthUser(service, code, state, redirectUri string) (io.ReadCloser, *model.Team, map[string]string, *model.AppError) {
|
||||||
sso := utils.Cfg.GetSSOService(service)
|
sso := utils.Cfg.GetSSOService(service)
|
||||||
if sso == nil || !sso.Enable {
|
if sso == nil || !sso.Enable {
|
||||||
return nil, nil, model.NewAppError("AuthorizeOAuthUser", "Unsupported OAuth service provider", "service="+service)
|
return nil, nil, nil, model.NewAppError("AuthorizeOAuthUser", "Unsupported OAuth service provider", "service="+service)
|
||||||
}
|
}
|
||||||
|
|
||||||
stateStr := ""
|
stateStr := ""
|
||||||
if b, err := b64.StdEncoding.DecodeString(state); err != nil {
|
if b, err := b64.StdEncoding.DecodeString(state); err != nil {
|
||||||
return nil, nil, model.NewAppError("AuthorizeOAuthUser", "Invalid state", err.Error())
|
return nil, nil, nil, model.NewAppError("AuthorizeOAuthUser", "Invalid state", err.Error())
|
||||||
} else {
|
} else {
|
||||||
stateStr = string(b)
|
stateStr = string(b)
|
||||||
}
|
}
|
||||||
@@ -1705,12 +1803,13 @@ 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 !model.ComparePassword(stateProps["hash"], sso.Id) {
|
||||||
return nil, nil, model.NewAppError("AuthorizeOAuthUser", "Invalid state", "")
|
return nil, nil, nil, model.NewAppError("AuthorizeOAuthUser", "Invalid state", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
teamName := stateProps["team"]
|
ok := true
|
||||||
if len(teamName) == 0 {
|
teamName := ""
|
||||||
return nil, nil, model.NewAppError("AuthorizeOAuthUser", "Invalid state; missing team name", "")
|
if teamName, ok = stateProps["team"]; !ok {
|
||||||
|
return nil, nil, nil, model.NewAppError("AuthorizeOAuthUser", "Invalid state; missing team name", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
tchan := Srv.Store.Team().GetByName(teamName)
|
tchan := Srv.Store.Team().GetByName(teamName)
|
||||||
@@ -1730,20 +1829,20 @@ func AuthorizeOAuthUser(service, code, state, redirectUri string) (io.ReadCloser
|
|||||||
|
|
||||||
var ar *model.AccessResponse
|
var ar *model.AccessResponse
|
||||||
if resp, err := client.Do(req); err != nil {
|
if resp, err := client.Do(req); err != nil {
|
||||||
return nil, nil, model.NewAppError("AuthorizeOAuthUser", "Token request failed", err.Error())
|
return nil, nil, nil, model.NewAppError("AuthorizeOAuthUser", "Token request failed", err.Error())
|
||||||
} else {
|
} else {
|
||||||
ar = model.AccessResponseFromJson(resp.Body)
|
ar = model.AccessResponseFromJson(resp.Body)
|
||||||
if ar == nil {
|
if ar == nil {
|
||||||
return nil, nil, model.NewAppError("AuthorizeOAuthUser", "Bad response from token request", "")
|
return nil, nil, nil, model.NewAppError("AuthorizeOAuthUser", "Bad response from token request", "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.ToLower(ar.TokenType) != model.ACCESS_TOKEN_TYPE {
|
if strings.ToLower(ar.TokenType) != model.ACCESS_TOKEN_TYPE {
|
||||||
return nil, nil, model.NewAppError("AuthorizeOAuthUser", "Bad token type", "token_type="+ar.TokenType)
|
return nil, nil, nil, model.NewAppError("AuthorizeOAuthUser", "Bad token type", "token_type="+ar.TokenType)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ar.AccessToken) == 0 {
|
if len(ar.AccessToken) == 0 {
|
||||||
return nil, nil, model.NewAppError("AuthorizeOAuthUser", "Missing access token", "")
|
return nil, nil, nil, model.NewAppError("AuthorizeOAuthUser", "Missing access token", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
p = url.Values{}
|
p = url.Values{}
|
||||||
@@ -1755,12 +1854,12 @@ func AuthorizeOAuthUser(service, code, state, redirectUri string) (io.ReadCloser
|
|||||||
req.Header.Set("Authorization", "Bearer "+ar.AccessToken)
|
req.Header.Set("Authorization", "Bearer "+ar.AccessToken)
|
||||||
|
|
||||||
if resp, err := client.Do(req); err != nil {
|
if resp, err := client.Do(req); err != nil {
|
||||||
return nil, nil, model.NewAppError("AuthorizeOAuthUser", "Token request to "+service+" failed", err.Error())
|
return nil, nil, nil, model.NewAppError("AuthorizeOAuthUser", "Token request to "+service+" failed", err.Error())
|
||||||
} else {
|
} else {
|
||||||
if result := <-tchan; result.Err != nil {
|
if result := <-tchan; result.Err != nil {
|
||||||
return nil, nil, result.Err
|
return nil, nil, nil, result.Err
|
||||||
} else {
|
} else {
|
||||||
return resp.Body, result.Data.(*model.Team), nil
|
return resp.Body, result.Data.(*model.Team), stateProps, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1780,3 +1879,200 @@ func IsUsernameTaken(name string, teamId string) bool {
|
|||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func switchToSSO(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
props := model.MapFromJson(r.Body)
|
||||||
|
|
||||||
|
password := props["password"]
|
||||||
|
if len(password) == 0 {
|
||||||
|
c.SetInvalidParam("switchToSSO", "password")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
teamName := props["team_name"]
|
||||||
|
if len(teamName) == 0 {
|
||||||
|
c.SetInvalidParam("switchToSSO", "team_name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
service := props["service"]
|
||||||
|
if len(service) == 0 {
|
||||||
|
c.SetInvalidParam("switchToSSO", "service")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
email := props["email"]
|
||||||
|
if len(email) == 0 {
|
||||||
|
c.SetInvalidParam("switchToSSO", "email")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.LogAudit("attempt")
|
||||||
|
|
||||||
|
var team *model.Team
|
||||||
|
if result := <-Srv.Store.Team().GetByName(teamName); result.Err != nil {
|
||||||
|
c.LogAudit("fail - couldn't get team")
|
||||||
|
c.Err = result.Err
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
team = result.Data.(*model.Team)
|
||||||
|
}
|
||||||
|
|
||||||
|
var user *model.User
|
||||||
|
if result := <-Srv.Store.User().GetByEmail(team.Id, email); result.Err != nil {
|
||||||
|
c.LogAudit("fail - couldn't get user")
|
||||||
|
c.Err = result.Err
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
user = result.Data.(*model.User)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !checkUserLoginAttempts(c, user) || !checkUserPassword(c, user, password) {
|
||||||
|
c.LogAuditWithUserId(user.Id, "fail - invalid password")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stateProps := map[string]string{}
|
||||||
|
stateProps["action"] = model.OAUTH_ACTION_EMAIL_TO_SSO
|
||||||
|
stateProps["email"] = email
|
||||||
|
|
||||||
|
m := map[string]string{}
|
||||||
|
if authUrl, err := GetAuthorizationCode(c, service, teamName, stateProps, ""); err != nil {
|
||||||
|
c.LogAuditWithUserId(user.Id, "fail - oauth issue")
|
||||||
|
c.Err = err
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
m["follow_link"] = authUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
c.LogAuditWithUserId(user.Id, "success")
|
||||||
|
w.Write([]byte(model.MapToJson(m)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func CompleteSwitchWithOAuth(c *Context, w http.ResponseWriter, r *http.Request, service string, userData io.ReadCloser, team *model.Team, email string) {
|
||||||
|
authData := ""
|
||||||
|
provider := einterfaces.GetOauthProvider(service)
|
||||||
|
if provider == nil {
|
||||||
|
c.Err = model.NewAppError("CompleteClaimWithOAuth", service+" oauth not avlailable on this server", "")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
authData = provider.GetAuthDataFromJson(userData)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(authData) == 0 {
|
||||||
|
c.Err = model.NewAppError("CompleteClaimWithOAuth", "Could not parse auth data out of "+service+" user object", "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(email) == 0 {
|
||||||
|
c.Err = model.NewAppError("CompleteClaimWithOAuth", "Blank email", "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var user *model.User
|
||||||
|
if result := <-Srv.Store.User().GetByEmail(team.Id, email); result.Err != nil {
|
||||||
|
c.Err = result.Err
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
user = result.Data.(*model.User)
|
||||||
|
}
|
||||||
|
|
||||||
|
RevokeAllSession(c, user.Id)
|
||||||
|
if c.Err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := <-Srv.Store.User().UpdateAuthData(user.Id, service, authData); result.Err != nil {
|
||||||
|
c.Err = result.Err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sendSignInChangeEmailAndForget(user.Email, team.DisplayName, c.GetSiteURL()+"/"+team.Name, c.GetSiteURL(), strings.Title(service)+" SSO")
|
||||||
|
}
|
||||||
|
|
||||||
|
func switchToEmail(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
props := model.MapFromJson(r.Body)
|
||||||
|
|
||||||
|
password := props["password"]
|
||||||
|
if len(password) == 0 {
|
||||||
|
c.SetInvalidParam("switchToEmail", "password")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
teamName := props["team_name"]
|
||||||
|
if len(teamName) == 0 {
|
||||||
|
c.SetInvalidParam("switchToEmail", "team_name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
email := props["email"]
|
||||||
|
if len(email) == 0 {
|
||||||
|
c.SetInvalidParam("switchToEmail", "email")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.LogAudit("attempt")
|
||||||
|
|
||||||
|
var team *model.Team
|
||||||
|
if result := <-Srv.Store.Team().GetByName(teamName); result.Err != nil {
|
||||||
|
c.LogAudit("fail - couldn't get team")
|
||||||
|
c.Err = result.Err
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
team = result.Data.(*model.Team)
|
||||||
|
}
|
||||||
|
|
||||||
|
var user *model.User
|
||||||
|
if result := <-Srv.Store.User().GetByEmail(team.Id, email); result.Err != nil {
|
||||||
|
c.LogAudit("fail - couldn't get user")
|
||||||
|
c.Err = result.Err
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
user = result.Data.(*model.User)
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Id != c.Session.UserId {
|
||||||
|
c.LogAudit("fail - user ids didn't match")
|
||||||
|
c.Err = model.NewAppError("switchToEmail", "Update password failed because context user_id did not match provided user's id", "")
|
||||||
|
c.Err.StatusCode = http.StatusForbidden
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := <-Srv.Store.User().UpdatePassword(c.Session.UserId, model.HashPassword(password)); result.Err != nil {
|
||||||
|
c.LogAudit("fail - database issue")
|
||||||
|
c.Err = result.Err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sendSignInChangeEmailAndForget(user.Email, team.DisplayName, c.GetSiteURL()+"/"+team.Name, c.GetSiteURL(), "email and password")
|
||||||
|
|
||||||
|
RevokeAllSession(c, c.Session.UserId)
|
||||||
|
if c.Err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m := map[string]string{}
|
||||||
|
m["follow_link"] = c.GetTeamURL() + "/login?extra=signin_change"
|
||||||
|
|
||||||
|
c.LogAudit("success")
|
||||||
|
w.Write([]byte(model.MapToJson(m)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendSignInChangeEmailAndForget(email, teamDisplayName, teamURL, siteURL, method string) {
|
||||||
|
go func() {
|
||||||
|
|
||||||
|
subjectPage := NewServerTemplatePage("signin_change_subject")
|
||||||
|
subjectPage.Props["SiteURL"] = siteURL
|
||||||
|
subjectPage.Props["TeamDisplayName"] = teamDisplayName
|
||||||
|
bodyPage := NewServerTemplatePage("signin_change_body")
|
||||||
|
bodyPage.Props["SiteURL"] = siteURL
|
||||||
|
bodyPage.Props["TeamDisplayName"] = teamDisplayName
|
||||||
|
bodyPage.Props["TeamURL"] = teamURL
|
||||||
|
bodyPage.Props["Method"] = method
|
||||||
|
|
||||||
|
if err := utils.SendMail(email, subjectPage.Render(), bodyPage.Render()); err != nil {
|
||||||
|
l4g.Error("Failed to send update password email successfully err=%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|||||||
103
api/user_test.go
103
api/user_test.go
@@ -1085,3 +1085,106 @@ func TestStatuses(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSwitchToSSO(t *testing.T) {
|
||||||
|
Setup()
|
||||||
|
|
||||||
|
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
|
||||||
|
rteam, _ := Client.CreateTeam(&team)
|
||||||
|
|
||||||
|
user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
|
||||||
|
ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User)
|
||||||
|
store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
|
||||||
|
|
||||||
|
m := map[string]string{}
|
||||||
|
if _, err := Client.SwitchToSSO(m); err == nil {
|
||||||
|
t.Fatal("should have failed - empty data")
|
||||||
|
}
|
||||||
|
|
||||||
|
m["password"] = "pwd"
|
||||||
|
_, err := Client.SwitchToSSO(m)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have failed - missing team_name, service, email")
|
||||||
|
}
|
||||||
|
|
||||||
|
m["team_name"] = team.Name
|
||||||
|
if _, err := Client.SwitchToSSO(m); err == nil {
|
||||||
|
t.Fatal("should have failed - missing service, email")
|
||||||
|
}
|
||||||
|
|
||||||
|
m["service"] = "someservice"
|
||||||
|
if _, err := Client.SwitchToSSO(m); err == nil {
|
||||||
|
t.Fatal("should have failed - missing email")
|
||||||
|
}
|
||||||
|
|
||||||
|
m["team_name"] = "junk"
|
||||||
|
if _, err := Client.SwitchToSSO(m); err == nil {
|
||||||
|
t.Fatal("should have failed - bad team name")
|
||||||
|
}
|
||||||
|
|
||||||
|
m["team_name"] = team.Name
|
||||||
|
m["email"] = "junk"
|
||||||
|
if _, err := Client.SwitchToSSO(m); err == nil {
|
||||||
|
t.Fatal("should have failed - bad email")
|
||||||
|
}
|
||||||
|
|
||||||
|
m["email"] = ruser.Email
|
||||||
|
m["password"] = "junk"
|
||||||
|
if _, err := Client.SwitchToSSO(m); err == nil {
|
||||||
|
t.Fatal("should have failed - bad password")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSwitchToEmail(t *testing.T) {
|
||||||
|
Setup()
|
||||||
|
|
||||||
|
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
|
||||||
|
rteam, _ := Client.CreateTeam(&team)
|
||||||
|
|
||||||
|
user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
|
||||||
|
ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User)
|
||||||
|
store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
|
||||||
|
|
||||||
|
user2 := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
|
||||||
|
ruser2 := Client.Must(Client.CreateUser(&user2, "")).Data.(*model.User)
|
||||||
|
store.Must(Srv.Store.User().VerifyEmail(ruser2.Id))
|
||||||
|
|
||||||
|
m := map[string]string{}
|
||||||
|
if _, err := Client.SwitchToSSO(m); err == nil {
|
||||||
|
t.Fatal("should have failed - not logged in")
|
||||||
|
}
|
||||||
|
|
||||||
|
Client.LoginByEmail(team.Name, user.Email, user.Password)
|
||||||
|
|
||||||
|
if _, err := Client.SwitchToSSO(m); err == nil {
|
||||||
|
t.Fatal("should have failed - empty data")
|
||||||
|
}
|
||||||
|
|
||||||
|
m["password"] = "pwd"
|
||||||
|
_, err := Client.SwitchToSSO(m)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have failed - missing team_name, service, email")
|
||||||
|
}
|
||||||
|
|
||||||
|
m["team_name"] = team.Name
|
||||||
|
if _, err := Client.SwitchToSSO(m); err == nil {
|
||||||
|
t.Fatal("should have failed - missing email")
|
||||||
|
}
|
||||||
|
|
||||||
|
m["email"] = ruser.Email
|
||||||
|
m["team_name"] = "junk"
|
||||||
|
if _, err := Client.SwitchToSSO(m); err == nil {
|
||||||
|
t.Fatal("should have failed - bad team name")
|
||||||
|
}
|
||||||
|
|
||||||
|
m["team_name"] = team.Name
|
||||||
|
m["email"] = "junk"
|
||||||
|
if _, err := Client.SwitchToSSO(m); err == nil {
|
||||||
|
t.Fatal("should have failed - bad email")
|
||||||
|
}
|
||||||
|
|
||||||
|
m["email"] = ruser2.Email
|
||||||
|
if _, err := Client.SwitchToSSO(m); err == nil {
|
||||||
|
t.Fatal("should have failed - wrong user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -100,4 +100,4 @@
|
|||||||
"TokenEndpoint": "",
|
"TokenEndpoint": "",
|
||||||
"UserApiEndpoint": ""
|
"UserApiEndpoint": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -349,6 +349,24 @@ func (c *Client) GetSessions(id string) (*Result, *AppError) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) SwitchToSSO(m map[string]string) (*Result, *AppError) {
|
||||||
|
if r, err := c.DoApiPost("/users/switch_to_sso", MapToJson(m)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||||
|
r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SwitchToEmail(m map[string]string) (*Result, *AppError) {
|
||||||
|
if r, err := c.DoApiPost("/users/switch_to_email", MapToJson(m)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||||
|
r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) Command(channelId string, command string, suggest bool) (*Result, *AppError) {
|
func (c *Client) Command(channelId string, command string, suggest bool) (*Result, *AppError) {
|
||||||
m := make(map[string]string)
|
m := make(map[string]string)
|
||||||
m["command"] = command
|
m["command"] = command
|
||||||
|
|||||||
@@ -10,6 +10,13 @@ import (
|
|||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
OAUTH_ACTION_SIGNUP = "signup"
|
||||||
|
OAUTH_ACTION_LOGIN = "login"
|
||||||
|
OAUTH_ACTION_EMAIL_TO_SSO = "email_to_sso"
|
||||||
|
OAUTH_ACTION_SSO_TO_EMAIL = "sso_to_email"
|
||||||
|
)
|
||||||
|
|
||||||
type OAuthApp struct {
|
type OAuthApp struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
CreatorId string `json:"creator_id"`
|
CreatorId string `json:"creator_id"`
|
||||||
|
|||||||
@@ -266,7 +266,7 @@ func (us SqlUserStore) UpdatePassword(userId, hashedPassword string) StoreChanne
|
|||||||
|
|
||||||
updateAt := model.GetMillis()
|
updateAt := model.GetMillis()
|
||||||
|
|
||||||
if _, err := us.GetMaster().Exec("UPDATE Users SET Password = :Password, LastPasswordUpdate = :LastPasswordUpdate, UpdateAt = :UpdateAt, FailedAttempts = 0 WHERE Id = :UserId AND AuthData = ''", map[string]interface{}{"Password": hashedPassword, "LastPasswordUpdate": updateAt, "UpdateAt": updateAt, "UserId": userId}); err != nil {
|
if _, err := us.GetMaster().Exec("UPDATE Users SET Password = :Password, LastPasswordUpdate = :LastPasswordUpdate, UpdateAt = :UpdateAt, AuthData = '', AuthService = '', FailedAttempts = 0 WHERE Id = :UserId", map[string]interface{}{"Password": hashedPassword, "LastPasswordUpdate": updateAt, "UpdateAt": updateAt, "UserId": userId}); err != nil {
|
||||||
result.Err = model.NewAppError("SqlUserStore.UpdatePassword", "We couldn't update the user password", "id="+userId+", "+err.Error())
|
result.Err = model.NewAppError("SqlUserStore.UpdatePassword", "We couldn't update the user password", "id="+userId+", "+err.Error())
|
||||||
} else {
|
} else {
|
||||||
result.Data = userId
|
result.Data = userId
|
||||||
@@ -298,6 +298,28 @@ func (us SqlUserStore) UpdateFailedPasswordAttempts(userId string, attempts int)
|
|||||||
return storeChannel
|
return storeChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (us SqlUserStore) UpdateAuthData(userId, service, authData string) StoreChannel {
|
||||||
|
|
||||||
|
storeChannel := make(StoreChannel)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
result := StoreResult{}
|
||||||
|
|
||||||
|
updateAt := model.GetMillis()
|
||||||
|
|
||||||
|
if _, err := us.GetMaster().Exec("UPDATE Users SET Password = '', LastPasswordUpdate = :LastPasswordUpdate, UpdateAt = :UpdateAt, FailedAttempts = 0, AuthService = :AuthService, AuthData = :AuthData WHERE Id = :UserId", map[string]interface{}{"LastPasswordUpdate": updateAt, "UpdateAt": updateAt, "UserId": userId, "AuthService": service, "AuthData": authData}); err != nil {
|
||||||
|
result.Err = model.NewAppError("SqlUserStore.UpdateAuthData", "We couldn't update the auth data", "id="+userId+", "+err.Error())
|
||||||
|
} else {
|
||||||
|
result.Data = userId
|
||||||
|
}
|
||||||
|
|
||||||
|
storeChannel <- result
|
||||||
|
close(storeChannel)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return storeChannel
|
||||||
|
}
|
||||||
|
|
||||||
func (us SqlUserStore) Get(id string) StoreChannel {
|
func (us SqlUserStore) Get(id string) StoreChannel {
|
||||||
|
|
||||||
storeChannel := make(StoreChannel)
|
storeChannel := make(StoreChannel)
|
||||||
|
|||||||
@@ -390,3 +390,34 @@ func TestUserStoreDelete(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUserStoreUpdateAuthData(t *testing.T) {
|
||||||
|
Setup()
|
||||||
|
|
||||||
|
u1 := model.User{}
|
||||||
|
u1.TeamId = model.NewId()
|
||||||
|
u1.Email = model.NewId()
|
||||||
|
Must(store.User().Save(&u1))
|
||||||
|
|
||||||
|
service := "someservice"
|
||||||
|
authData := "1"
|
||||||
|
|
||||||
|
if err := (<-store.User().UpdateAuthData(u1.Id, service, authData)).Err; err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r1 := <-store.User().GetByEmail(u1.TeamId, u1.Email); r1.Err != nil {
|
||||||
|
t.Fatal(r1.Err)
|
||||||
|
} else {
|
||||||
|
user := r1.Data.(*model.User)
|
||||||
|
if user.AuthService != service {
|
||||||
|
t.Fatal("AuthService was not updated correctly")
|
||||||
|
}
|
||||||
|
if user.AuthData != authData {
|
||||||
|
t.Fatal("AuthData was not updated correctly")
|
||||||
|
}
|
||||||
|
if user.Password != "" {
|
||||||
|
t.Fatal("Password was not cleared properly")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ type UserStore interface {
|
|||||||
UpdateLastActivityAt(userId string, time int64) StoreChannel
|
UpdateLastActivityAt(userId string, time int64) StoreChannel
|
||||||
UpdateUserAndSessionActivity(userId string, sessionId string, time int64) StoreChannel
|
UpdateUserAndSessionActivity(userId string, sessionId string, time int64) StoreChannel
|
||||||
UpdatePassword(userId, newPassword string) StoreChannel
|
UpdatePassword(userId, newPassword string) StoreChannel
|
||||||
|
UpdateAuthData(userId, service, authData string) StoreChannel
|
||||||
Get(id string) StoreChannel
|
Get(id string) StoreChannel
|
||||||
GetProfiles(teamId string) StoreChannel
|
GetProfiles(teamId string) StoreChannel
|
||||||
GetByEmail(teamId string, email string) StoreChannel
|
GetByEmail(teamId string, email string) StoreChannel
|
||||||
|
|||||||
53
web/react/components/claim/claim_account.jsx
Обычный файл
53
web/react/components/claim/claim_account.jsx
Обычный файл
@@ -0,0 +1,53 @@
|
|||||||
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import EmailToSSO from './email_to_sso.jsx';
|
||||||
|
import SSOToEmail from './sso_to_email.jsx';
|
||||||
|
|
||||||
|
export default class ClaimAccount extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {};
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
let content;
|
||||||
|
if (this.props.email === '') {
|
||||||
|
content = <p>{'No email specified.'}</p>;
|
||||||
|
} else if (this.props.currentType === '' && this.props.newType !== '') {
|
||||||
|
content = (
|
||||||
|
<EmailToSSO
|
||||||
|
email={this.props.email}
|
||||||
|
type={this.props.newType}
|
||||||
|
teamName={this.props.teamName}
|
||||||
|
teamDisplayName={this.props.teamDisplayName}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
content = (
|
||||||
|
<SSOToEmail
|
||||||
|
email={this.props.email}
|
||||||
|
currentType={this.props.currentType}
|
||||||
|
teamName={this.props.teamName}
|
||||||
|
teamDisplayName={this.props.teamDisplayName}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ClaimAccount.defaultProps = {
|
||||||
|
};
|
||||||
|
ClaimAccount.propTypes = {
|
||||||
|
currentType: React.PropTypes.string.isRequired,
|
||||||
|
newType: React.PropTypes.string.isRequired,
|
||||||
|
email: React.PropTypes.string.isRequired,
|
||||||
|
teamName: React.PropTypes.string.isRequired,
|
||||||
|
teamDisplayName: React.PropTypes.string.isRequired
|
||||||
|
};
|
||||||
97
web/react/components/claim/email_to_sso.jsx
Обычный файл
97
web/react/components/claim/email_to_sso.jsx
Обычный файл
@@ -0,0 +1,97 @@
|
|||||||
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import * as Utils from '../../utils/utils.jsx';
|
||||||
|
import * as Client from '../../utils/client.jsx';
|
||||||
|
|
||||||
|
export default class EmailToSSO extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.submit = this.submit.bind(this);
|
||||||
|
|
||||||
|
this.state = {};
|
||||||
|
}
|
||||||
|
submit(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var state = {};
|
||||||
|
|
||||||
|
var password = ReactDOM.findDOMNode(this.refs.password).value.trim();
|
||||||
|
if (!password) {
|
||||||
|
state.error = 'Please enter your password.';
|
||||||
|
this.setState(state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.error = null;
|
||||||
|
this.setState(state);
|
||||||
|
|
||||||
|
var postData = {};
|
||||||
|
postData.password = password;
|
||||||
|
postData.email = this.props.email;
|
||||||
|
postData.team_name = this.props.teamName;
|
||||||
|
postData.service = this.props.type;
|
||||||
|
|
||||||
|
Client.switchToSSO(postData,
|
||||||
|
(data) => {
|
||||||
|
if (data.follow_link) {
|
||||||
|
window.location.href = data.follow_link;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
this.setState({error});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
var error = null;
|
||||||
|
if (this.state.error) {
|
||||||
|
error = <div className='form-group has-error'><label className='control-label'>{this.state.error}</label></div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
var formClass = 'form-group';
|
||||||
|
if (error) {
|
||||||
|
formClass += ' has-error';
|
||||||
|
}
|
||||||
|
|
||||||
|
const uiType = Utils.toTitleCase(this.props.type) + ' SSO';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='col-sm-12'>
|
||||||
|
<div className='signup-team__container'>
|
||||||
|
<h3>{'Switch Email/Password Account to ' + uiType}</h3>
|
||||||
|
<form onSubmit={this.submit}>
|
||||||
|
<p>{'Upon claiming your account, you will only be able to login with ' + Utils.toTitleCase(this.props.type) + ' SSO.'}</p>
|
||||||
|
<p>{'Enter the password for your ' + this.props.teamDisplayName + ' ' + global.window.mm_config.SiteName + ' account.'}</p>
|
||||||
|
<div className={formClass}>
|
||||||
|
<input
|
||||||
|
type='password'
|
||||||
|
className='form-control'
|
||||||
|
name='password'
|
||||||
|
ref='password'
|
||||||
|
placeholder='Password'
|
||||||
|
spellCheck='false'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{error}
|
||||||
|
<button
|
||||||
|
type='submit'
|
||||||
|
className='btn btn-primary'
|
||||||
|
>
|
||||||
|
{'Switch account to ' + uiType}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EmailToSSO.defaultProps = {
|
||||||
|
};
|
||||||
|
EmailToSSO.propTypes = {
|
||||||
|
type: React.PropTypes.string.isRequired,
|
||||||
|
email: React.PropTypes.string.isRequired,
|
||||||
|
teamName: React.PropTypes.string.isRequired,
|
||||||
|
teamDisplayName: React.PropTypes.string.isRequired
|
||||||
|
};
|
||||||
113
web/react/components/claim/sso_to_email.jsx
Обычный файл
113
web/react/components/claim/sso_to_email.jsx
Обычный файл
@@ -0,0 +1,113 @@
|
|||||||
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import * as Utils from '../../utils/utils.jsx';
|
||||||
|
import * as Client from '../../utils/client.jsx';
|
||||||
|
|
||||||
|
export default class SSOToEmail extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.submit = this.submit.bind(this);
|
||||||
|
|
||||||
|
this.state = {};
|
||||||
|
}
|
||||||
|
submit(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const state = {};
|
||||||
|
|
||||||
|
const password = ReactDOM.findDOMNode(this.refs.password).value.trim();
|
||||||
|
if (!password) {
|
||||||
|
state.error = 'Please enter a password.';
|
||||||
|
this.setState(state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmPassword = ReactDOM.findDOMNode(this.refs.passwordconfirm).value.trim();
|
||||||
|
if (!confirmPassword || password !== confirmPassword) {
|
||||||
|
state.error = 'Passwords do not match.';
|
||||||
|
this.setState(state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.error = null;
|
||||||
|
this.setState(state);
|
||||||
|
|
||||||
|
var postData = {};
|
||||||
|
postData.password = password;
|
||||||
|
postData.email = this.props.email;
|
||||||
|
postData.team_name = this.props.teamName;
|
||||||
|
|
||||||
|
Client.switchToEmail(postData,
|
||||||
|
(data) => {
|
||||||
|
if (data.follow_link) {
|
||||||
|
window.location.href = data.follow_link;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
this.setState({error});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
var error = null;
|
||||||
|
if (this.state.error) {
|
||||||
|
error = <div className='form-group has-error'><label className='control-label'>{this.state.error}</label></div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
var formClass = 'form-group';
|
||||||
|
if (error) {
|
||||||
|
formClass += ' has-error';
|
||||||
|
}
|
||||||
|
|
||||||
|
const uiType = Utils.toTitleCase(this.props.currentType) + ' SSO';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='col-sm-12'>
|
||||||
|
<div className='signup-team__container'>
|
||||||
|
<h3>{'Switch ' + uiType + ' Account to Email'}</h3>
|
||||||
|
<form onSubmit={this.submit}>
|
||||||
|
<p>{'Upon changing your account type, you will only be able to login with your email and password.'}</p>
|
||||||
|
<p>{'Enter a new password for your ' + this.props.teamDisplayName + ' ' + global.window.mm_config.SiteName + ' account.'}</p>
|
||||||
|
<div className={formClass}>
|
||||||
|
<input
|
||||||
|
type='password'
|
||||||
|
className='form-control'
|
||||||
|
name='password'
|
||||||
|
ref='password'
|
||||||
|
placeholder='New Password'
|
||||||
|
spellCheck='false'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={formClass}>
|
||||||
|
<input
|
||||||
|
type='password'
|
||||||
|
className='form-control'
|
||||||
|
name='passwordconfirm'
|
||||||
|
ref='passwordconfirm'
|
||||||
|
placeholder='Confirm Password'
|
||||||
|
spellCheck='false'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{error}
|
||||||
|
<button
|
||||||
|
type='submit'
|
||||||
|
className='btn btn-primary'
|
||||||
|
>
|
||||||
|
{'Switch ' + uiType + ' account to email and password'}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SSOToEmail.defaultProps = {
|
||||||
|
};
|
||||||
|
SSOToEmail.propTypes = {
|
||||||
|
currentType: React.PropTypes.string.isRequired,
|
||||||
|
email: React.PropTypes.string.isRequired,
|
||||||
|
teamName: React.PropTypes.string.isRequired,
|
||||||
|
teamDisplayName: React.PropTypes.string.isRequired
|
||||||
|
};
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import * as Utils from '../utils/utils.jsx';
|
|
||||||
import LoginEmail from './login_email.jsx';
|
import LoginEmail from './login_email.jsx';
|
||||||
import LoginLdap from './login_ldap.jsx';
|
import LoginLdap from './login_ldap.jsx';
|
||||||
|
|
||||||
|
import * as Utils from '../utils/utils.jsx';
|
||||||
|
import Constants from '../utils/constants.jsx';
|
||||||
|
|
||||||
export default class Login extends React.Component {
|
export default class Login extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -40,15 +42,24 @@ export default class Login extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const verifiedParam = Utils.getUrlParameter('verified');
|
const extraParam = Utils.getUrlParameter('extra');
|
||||||
let verifiedBox = '';
|
let extraBox = '';
|
||||||
if (verifiedParam) {
|
if (extraParam) {
|
||||||
verifiedBox = (
|
let msg;
|
||||||
<div className='alert alert-success'>
|
if (extraParam === Constants.SIGNIN_CHANGE) {
|
||||||
<i className='fa fa-check' />
|
msg = ' Sign-in method changed successfully';
|
||||||
{' Email Verified'}
|
} else if (extraParam === Constants.SIGNIN_VERIFIED) {
|
||||||
</div>
|
msg = ' Email Verified';
|
||||||
);
|
}
|
||||||
|
|
||||||
|
if (msg != null) {
|
||||||
|
extraBox = (
|
||||||
|
<div className='alert alert-success'>
|
||||||
|
<i className='fa fa-check' />
|
||||||
|
{msg}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let emailSignup;
|
let emailSignup;
|
||||||
@@ -124,7 +135,7 @@ export default class Login extends React.Component {
|
|||||||
<h5 className='margin--less'>{'Sign in to:'}</h5>
|
<h5 className='margin--less'>{'Sign in to:'}</h5>
|
||||||
<h2 className='signup-team__name'>{teamDisplayName}</h2>
|
<h2 className='signup-team__name'>{teamDisplayName}</h2>
|
||||||
<h2 className='signup-team__subdomain'>{'on '}{global.window.mm_config.SiteName}</h2>
|
<h2 className='signup-team__subdomain'>{'on '}{global.window.mm_config.SiteName}</h2>
|
||||||
{verifiedBox}
|
{extraBox}
|
||||||
{loginMessage}
|
{loginMessage}
|
||||||
{emailSignup}
|
{emailSignup}
|
||||||
{ldapLogin}
|
{ldapLogin}
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import SettingItemMax from '../setting_item_max.jsx';
|
|||||||
import AccessHistoryModal from '../access_history_modal.jsx';
|
import AccessHistoryModal from '../access_history_modal.jsx';
|
||||||
import ActivityLogModal from '../activity_log_modal.jsx';
|
import ActivityLogModal from '../activity_log_modal.jsx';
|
||||||
import ToggleModalButton from '../toggle_modal_button.jsx';
|
import ToggleModalButton from '../toggle_modal_button.jsx';
|
||||||
|
|
||||||
|
import TeamStore from '../../stores/team_store.jsx';
|
||||||
|
|
||||||
import * as Client from '../../utils/client.jsx';
|
import * as Client from '../../utils/client.jsx';
|
||||||
import * as AsyncClient from '../../utils/async_client.jsx';
|
import * as AsyncClient from '../../utils/async_client.jsx';
|
||||||
import Constants from '../../utils/constants.jsx';
|
import Constants from '../../utils/constants.jsx';
|
||||||
@@ -18,9 +21,19 @@ export default class SecurityTab extends React.Component {
|
|||||||
this.updateCurrentPassword = this.updateCurrentPassword.bind(this);
|
this.updateCurrentPassword = this.updateCurrentPassword.bind(this);
|
||||||
this.updateNewPassword = this.updateNewPassword.bind(this);
|
this.updateNewPassword = this.updateNewPassword.bind(this);
|
||||||
this.updateConfirmPassword = this.updateConfirmPassword.bind(this);
|
this.updateConfirmPassword = this.updateConfirmPassword.bind(this);
|
||||||
this.setupInitialState = this.setupInitialState.bind(this);
|
this.getDefaultState = this.getDefaultState.bind(this);
|
||||||
|
this.createPasswordSection = this.createPasswordSection.bind(this);
|
||||||
|
this.createSignInSection = this.createSignInSection.bind(this);
|
||||||
|
|
||||||
this.state = this.setupInitialState();
|
this.state = this.getDefaultState();
|
||||||
|
}
|
||||||
|
getDefaultState() {
|
||||||
|
return {
|
||||||
|
currentPassword: '',
|
||||||
|
newPassword: '',
|
||||||
|
confirmPassword: '',
|
||||||
|
authService: this.props.user.auth_service
|
||||||
|
};
|
||||||
}
|
}
|
||||||
submitPassword(e) {
|
submitPassword(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -51,13 +64,13 @@ export default class SecurityTab extends React.Component {
|
|||||||
data.new_password = newPassword;
|
data.new_password = newPassword;
|
||||||
|
|
||||||
Client.updatePassword(data,
|
Client.updatePassword(data,
|
||||||
function success() {
|
() => {
|
||||||
this.props.updateSection('');
|
this.props.updateSection('');
|
||||||
AsyncClient.getMe();
|
AsyncClient.getMe();
|
||||||
this.setState(this.setupInitialState());
|
this.setState(this.getDefaultState());
|
||||||
}.bind(this),
|
},
|
||||||
function fail(err) {
|
(err) => {
|
||||||
var state = this.setupInitialState();
|
var state = this.getDefaultState();
|
||||||
if (err.message) {
|
if (err.message) {
|
||||||
state.serverError = err.message;
|
state.serverError = err.message;
|
||||||
} else {
|
} else {
|
||||||
@@ -65,7 +78,7 @@ export default class SecurityTab extends React.Component {
|
|||||||
}
|
}
|
||||||
state.passwordError = '';
|
state.passwordError = '';
|
||||||
this.setState(state);
|
this.setState(state);
|
||||||
}.bind(this)
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
updateCurrentPassword(e) {
|
updateCurrentPassword(e) {
|
||||||
@@ -77,86 +90,60 @@ export default class SecurityTab extends React.Component {
|
|||||||
updateConfirmPassword(e) {
|
updateConfirmPassword(e) {
|
||||||
this.setState({confirmPassword: e.target.value});
|
this.setState({confirmPassword: e.target.value});
|
||||||
}
|
}
|
||||||
setupInitialState() {
|
createPasswordSection() {
|
||||||
return {currentPassword: '', newPassword: '', confirmPassword: ''};
|
let updateSectionStatus;
|
||||||
}
|
|
||||||
render() {
|
|
||||||
var serverError;
|
|
||||||
if (this.state.serverError) {
|
|
||||||
serverError = this.state.serverError;
|
|
||||||
}
|
|
||||||
var passwordError;
|
|
||||||
if (this.state.passwordError) {
|
|
||||||
passwordError = this.state.passwordError;
|
|
||||||
}
|
|
||||||
|
|
||||||
var updateSectionStatus;
|
if (this.props.activeSection === 'password' && this.props.user.auth_service === '') {
|
||||||
var passwordSection;
|
const inputs = [];
|
||||||
if (this.props.activeSection === 'password') {
|
|
||||||
var inputs = [];
|
|
||||||
var submit = null;
|
|
||||||
|
|
||||||
if (this.props.user.auth_service === '') {
|
inputs.push(
|
||||||
inputs.push(
|
<div
|
||||||
<div
|
key='currentPasswordUpdateForm'
|
||||||
key='currentPasswordUpdateForm'
|
className='form-group'
|
||||||
className='form-group'
|
>
|
||||||
>
|
<label className='col-sm-5 control-label'>{'Current Password'}</label>
|
||||||
<label className='col-sm-5 control-label'>Current Password</label>
|
<div className='col-sm-7'>
|
||||||
<div className='col-sm-7'>
|
<input
|
||||||
<input
|
className='form-control'
|
||||||
className='form-control'
|
type='password'
|
||||||
type='password'
|
onChange={this.updateCurrentPassword}
|
||||||
onChange={this.updateCurrentPassword}
|
value={this.state.currentPassword}
|
||||||
value={this.state.currentPassword}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
inputs.push(
|
);
|
||||||
<div
|
inputs.push(
|
||||||
key='newPasswordUpdateForm'
|
<div
|
||||||
className='form-group'
|
key='newPasswordUpdateForm'
|
||||||
>
|
className='form-group'
|
||||||
<label className='col-sm-5 control-label'>New Password</label>
|
>
|
||||||
<div className='col-sm-7'>
|
<label className='col-sm-5 control-label'>{'New Password'}</label>
|
||||||
<input
|
<div className='col-sm-7'>
|
||||||
className='form-control'
|
<input
|
||||||
type='password'
|
className='form-control'
|
||||||
onChange={this.updateNewPassword}
|
type='password'
|
||||||
value={this.state.newPassword}
|
onChange={this.updateNewPassword}
|
||||||
/>
|
value={this.state.newPassword}
|
||||||
</div>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
inputs.push(
|
);
|
||||||
<div
|
inputs.push(
|
||||||
key='retypeNewPasswordUpdateForm'
|
<div
|
||||||
className='form-group'
|
key='retypeNewPasswordUpdateForm'
|
||||||
>
|
className='form-group'
|
||||||
<label className='col-sm-5 control-label'>Retype New Password</label>
|
>
|
||||||
<div className='col-sm-7'>
|
<label className='col-sm-5 control-label'>{'Retype New Password'}</label>
|
||||||
<input
|
<div className='col-sm-7'>
|
||||||
className='form-control'
|
<input
|
||||||
type='password'
|
className='form-control'
|
||||||
onChange={this.updateConfirmPassword}
|
type='password'
|
||||||
value={this.state.confirmPassword}
|
onChange={this.updateConfirmPassword}
|
||||||
/>
|
value={this.state.confirmPassword}
|
||||||
</div>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
|
);
|
||||||
submit = this.submitPassword;
|
|
||||||
} else {
|
|
||||||
inputs.push(
|
|
||||||
<div
|
|
||||||
key='oauthPasswordInfo'
|
|
||||||
className='form-group'
|
|
||||||
>
|
|
||||||
<label className='col-sm-12'>Log in occurs through GitLab. Please see your GitLab account settings page to update your password.</label>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateSectionStatus = function resetSection(e) {
|
updateSectionStatus = function resetSection(e) {
|
||||||
this.props.updateSection('');
|
this.props.updateSection('');
|
||||||
@@ -164,51 +151,157 @@ export default class SecurityTab extends React.Component {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
|
|
||||||
passwordSection = (
|
return (
|
||||||
<SettingItemMax
|
<SettingItemMax
|
||||||
title='Password'
|
title='Password'
|
||||||
inputs={inputs}
|
inputs={inputs}
|
||||||
submit={submit}
|
submit={this.submitPassword}
|
||||||
server_error={serverError}
|
server_error={this.state.serverError}
|
||||||
client_error={passwordError}
|
client_error={this.state.passwordError}
|
||||||
updateSection={updateSectionStatus}
|
updateSection={updateSectionStatus}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
}
|
||||||
var describe;
|
|
||||||
if (this.props.user.auth_service === '') {
|
|
||||||
var d = new Date(this.props.user.last_password_update);
|
|
||||||
var hour = '12';
|
|
||||||
if (d.getHours() % 12) {
|
|
||||||
hour = String(d.getHours() % 12);
|
|
||||||
}
|
|
||||||
var min = String(d.getMinutes());
|
|
||||||
if (d.getMinutes() < 10) {
|
|
||||||
min = '0' + d.getMinutes();
|
|
||||||
}
|
|
||||||
var timeOfDay = ' am';
|
|
||||||
if (d.getHours() >= 12) {
|
|
||||||
timeOfDay = ' pm';
|
|
||||||
}
|
|
||||||
|
|
||||||
describe = 'Last updated ' + Constants.MONTHS[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear() + ' at ' + hour + ':' + min + timeOfDay;
|
var describe;
|
||||||
} else {
|
var d = new Date(this.props.user.last_password_update);
|
||||||
describe = 'Log in done through GitLab';
|
var hour = '12';
|
||||||
|
if (d.getHours() % 12) {
|
||||||
|
hour = String(d.getHours() % 12);
|
||||||
|
}
|
||||||
|
var min = String(d.getMinutes());
|
||||||
|
if (d.getMinutes() < 10) {
|
||||||
|
min = '0' + d.getMinutes();
|
||||||
|
}
|
||||||
|
var timeOfDay = ' am';
|
||||||
|
if (d.getHours() >= 12) {
|
||||||
|
timeOfDay = ' pm';
|
||||||
|
}
|
||||||
|
|
||||||
|
describe = 'Last updated ' + Constants.MONTHS[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear() + ' at ' + hour + ':' + min + timeOfDay;
|
||||||
|
|
||||||
|
updateSectionStatus = function updateSection() {
|
||||||
|
this.props.updateSection('password');
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingItemMin
|
||||||
|
title='Password'
|
||||||
|
describe={describe}
|
||||||
|
updateSection={updateSectionStatus}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
createSignInSection() {
|
||||||
|
let updateSectionStatus;
|
||||||
|
const user = this.props.user;
|
||||||
|
|
||||||
|
if (this.props.activeSection === 'signin') {
|
||||||
|
const inputs = [];
|
||||||
|
const teamName = TeamStore.getCurrent().name;
|
||||||
|
|
||||||
|
let emailOption;
|
||||||
|
if (global.window.mm_config.EnableSignUpWithEmail === 'true' && user.auth_service !== '') {
|
||||||
|
emailOption = (
|
||||||
|
<div>
|
||||||
|
<a
|
||||||
|
className='btn btn-primary'
|
||||||
|
href={'/' + teamName + '/claim?email=' + user.email}
|
||||||
|
>
|
||||||
|
{'Switch to using email and password'}
|
||||||
|
</a>
|
||||||
|
<br/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSectionStatus = function updateSection() {
|
let gitlabOption;
|
||||||
this.props.updateSection('password');
|
if (global.window.mm_config.EnableSignUpWithGitLab === 'true' && user.auth_service === '') {
|
||||||
|
gitlabOption = (
|
||||||
|
<div>
|
||||||
|
<a
|
||||||
|
className='btn btn-primary'
|
||||||
|
href={'/' + teamName + '/claim?email=' + user.email + '&new_type=' + Constants.GITLAB_SERVICE}
|
||||||
|
>
|
||||||
|
{'Switch to using GitLab SSO'}
|
||||||
|
</a>
|
||||||
|
<br/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let googleOption;
|
||||||
|
if (global.window.mm_config.EnableSignUpWithGoogle === 'true' && user.auth_service === '') {
|
||||||
|
googleOption = (
|
||||||
|
<div>
|
||||||
|
<a
|
||||||
|
className='btn btn-primary'
|
||||||
|
href={'/' + teamName + '/claim?email=' + user.email + '&new_type=' + Constants.GOOGLE_SERVICE}
|
||||||
|
>
|
||||||
|
{'Switch to using Google SSO'}
|
||||||
|
</a>
|
||||||
|
<br/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
inputs.push(
|
||||||
|
<div key='userSignInOption'>
|
||||||
|
{emailOption}
|
||||||
|
{gitlabOption}
|
||||||
|
<br/>
|
||||||
|
{googleOption}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
updateSectionStatus = function updateSection(e) {
|
||||||
|
this.props.updateSection('');
|
||||||
|
this.setState({serverError: null});
|
||||||
|
e.preventDefault();
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
|
|
||||||
passwordSection = (
|
const extraInfo = <span>{'You may only have one sign-in method at a time. Switching sign-in method will send an email notifying you if the change was successful.'}</span>;
|
||||||
<SettingItemMin
|
|
||||||
title='Password'
|
return (
|
||||||
describe={describe}
|
<SettingItemMax
|
||||||
|
title='Sign-in Method'
|
||||||
|
extraInfo={extraInfo}
|
||||||
|
inputs={inputs}
|
||||||
|
server_error={this.state.serverError}
|
||||||
updateSection={updateSectionStatus}
|
updateSection={updateSectionStatus}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateSectionStatus = function updateSection() {
|
||||||
|
this.props.updateSection('signin');
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
let describe = 'Email and Password';
|
||||||
|
if (this.props.user.auth_service === Constants.GITLAB_SERVICE) {
|
||||||
|
describe = 'GitLab SSO';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingItemMin
|
||||||
|
title='Sign-in Method'
|
||||||
|
describe={describe}
|
||||||
|
updateSection={updateSectionStatus}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
const passwordSection = this.createPasswordSection();
|
||||||
|
let signInSection;
|
||||||
|
|
||||||
|
let numMethods = 0;
|
||||||
|
numMethods = global.window.mm_config.EnableSignUpWithGitLab === 'true' ? numMethods + 1 : numMethods;
|
||||||
|
numMethods = global.window.mm_config.EnableSignUpWithGoogle === 'true' ? numMethods + 1 : numMethods;
|
||||||
|
|
||||||
|
if (global.window.mm_config.EnableSignUpWithEmail && numMethods > 0) {
|
||||||
|
signInSection = this.createSignInSection();
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className='modal-header'>
|
<div className='modal-header'>
|
||||||
@@ -233,9 +326,11 @@ export default class SecurityTab extends React.Component {
|
|||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div className='user-settings'>
|
<div className='user-settings'>
|
||||||
<h3 className='tab-header'>Security Settings</h3>
|
<h3 className='tab-header'>{'Security Settings'}</h3>
|
||||||
<div className='divider-dark first'/>
|
<div className='divider-dark first'/>
|
||||||
{passwordSection}
|
{passwordSection}
|
||||||
|
<div className='divider-light'/>
|
||||||
|
{signInSection}
|
||||||
<div className='divider-dark'/>
|
<div className='divider-dark'/>
|
||||||
<br></br>
|
<br></br>
|
||||||
<ToggleModalButton
|
<ToggleModalButton
|
||||||
|
|||||||
19
web/react/pages/claim_account.jsx
Обычный файл
19
web/react/pages/claim_account.jsx
Обычный файл
@@ -0,0 +1,19 @@
|
|||||||
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import ClaimAccount from '../components/claim/claim_account.jsx';
|
||||||
|
|
||||||
|
function setupClaimAccountPage(props) {
|
||||||
|
ReactDOM.render(
|
||||||
|
<ClaimAccount
|
||||||
|
email={props.Email}
|
||||||
|
currentType={props.CurrentType}
|
||||||
|
newType={props.NewType}
|
||||||
|
teamName={props.TeamName}
|
||||||
|
teamDisplayName={props.TeamDisplayName}
|
||||||
|
/>,
|
||||||
|
document.getElementById('claim')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
global.window.setup_claim_account_page = setupClaimAccountPage;
|
||||||
@@ -228,6 +228,40 @@ export function resetPassword(data, success, error) {
|
|||||||
track('api', 'api_users_reset_password');
|
track('api', 'api_users_reset_password');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function switchToSSO(data, success, error) {
|
||||||
|
$.ajax({
|
||||||
|
url: '/api/v1/users/switch_to_sso',
|
||||||
|
dataType: 'json',
|
||||||
|
contentType: 'application/json',
|
||||||
|
type: 'POST',
|
||||||
|
data: JSON.stringify(data),
|
||||||
|
success,
|
||||||
|
error: function onError(xhr, status, err) {
|
||||||
|
var e = handleError('switchToSSO', xhr, status, err);
|
||||||
|
error(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
track('api', 'api_users_switch_to_sso');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function switchToEmail(data, success, error) {
|
||||||
|
$.ajax({
|
||||||
|
url: '/api/v1/users/switch_to_email',
|
||||||
|
dataType: 'json',
|
||||||
|
contentType: 'application/json',
|
||||||
|
type: 'POST',
|
||||||
|
data: JSON.stringify(data),
|
||||||
|
success,
|
||||||
|
error: function onError(xhr, status, err) {
|
||||||
|
var e = handleError('switchToEmail', xhr, status, err);
|
||||||
|
error(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
track('api', 'api_users_switch_to_email');
|
||||||
|
}
|
||||||
|
|
||||||
export function logout() {
|
export function logout() {
|
||||||
track('api', 'api_users_logout');
|
track('api', 'api_users_logout');
|
||||||
var currentTeamUrl = TeamStore.getCurrentTeamUrl();
|
var currentTeamUrl = TeamStore.getCurrentTeamUrl();
|
||||||
|
|||||||
@@ -117,6 +117,8 @@ export default {
|
|||||||
GITLAB_SERVICE: 'gitlab',
|
GITLAB_SERVICE: 'gitlab',
|
||||||
GOOGLE_SERVICE: 'google',
|
GOOGLE_SERVICE: 'google',
|
||||||
EMAIL_SERVICE: 'email',
|
EMAIL_SERVICE: 'email',
|
||||||
|
SIGNIN_CHANGE: 'signin_change',
|
||||||
|
SIGNIN_VERIFIED: 'verified',
|
||||||
POST_CHUNK_SIZE: 60,
|
POST_CHUNK_SIZE: 60,
|
||||||
MAX_POST_CHUNKS: 3,
|
MAX_POST_CHUNKS: 3,
|
||||||
POST_FOCUS_CONTEXT_RADIUS: 10,
|
POST_FOCUS_CONTEXT_RADIUS: 10,
|
||||||
|
|||||||
16
web/templates/claim_account.html
Обычный файл
16
web/templates/claim_account.html
Обычный файл
@@ -0,0 +1,16 @@
|
|||||||
|
{{define "claim_account"}}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
{{template "head" . }}
|
||||||
|
<body class="white">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="inner__wrap">
|
||||||
|
<div class="row content" id="claim"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
window.setup_claim_account_page({{ .Props }});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
{{end}}
|
||||||
221
web/web.go
221
web/web.go
@@ -8,7 +8,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/mattermost/platform/api"
|
"github.com/mattermost/platform/api"
|
||||||
"github.com/mattermost/platform/einterfaces"
|
|
||||||
"github.com/mattermost/platform/model"
|
"github.com/mattermost/platform/model"
|
||||||
"github.com/mattermost/platform/store"
|
"github.com/mattermost/platform/store"
|
||||||
"github.com/mattermost/platform/utils"
|
"github.com/mattermost/platform/utils"
|
||||||
@@ -71,8 +70,7 @@ func InitWeb() {
|
|||||||
mainrouter.Handle("/verify_email", api.AppHandlerIndependent(verifyEmail)).Methods("GET")
|
mainrouter.Handle("/verify_email", api.AppHandlerIndependent(verifyEmail)).Methods("GET")
|
||||||
mainrouter.Handle("/find_team", api.AppHandlerIndependent(findTeam)).Methods("GET")
|
mainrouter.Handle("/find_team", api.AppHandlerIndependent(findTeam)).Methods("GET")
|
||||||
mainrouter.Handle("/signup_team", api.AppHandlerIndependent(signup)).Methods("GET")
|
mainrouter.Handle("/signup_team", api.AppHandlerIndependent(signup)).Methods("GET")
|
||||||
mainrouter.Handle("/login/{service:[A-Za-z]+}/complete", api.AppHandlerIndependent(loginCompleteOAuth)).Methods("GET")
|
mainrouter.Handle("/{service:[A-Za-z]+}/complete", api.AppHandlerIndependent(completeOAuth)).Methods("GET")
|
||||||
mainrouter.Handle("/signup/{service:[A-Za-z]+}/complete", api.AppHandlerIndependent(signupCompleteOAuth)).Methods("GET")
|
|
||||||
|
|
||||||
mainrouter.Handle("/admin_console", api.UserRequired(adminConsole)).Methods("GET")
|
mainrouter.Handle("/admin_console", api.UserRequired(adminConsole)).Methods("GET")
|
||||||
mainrouter.Handle("/admin_console/", api.UserRequired(adminConsole)).Methods("GET")
|
mainrouter.Handle("/admin_console/", api.UserRequired(adminConsole)).Methods("GET")
|
||||||
@@ -92,6 +90,7 @@ func InitWeb() {
|
|||||||
mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/login", api.AppHandler(login)).Methods("GET")
|
mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/login", api.AppHandler(login)).Methods("GET")
|
||||||
mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/logout", api.AppHandler(logout)).Methods("GET")
|
mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/logout", api.AppHandler(logout)).Methods("GET")
|
||||||
mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/reset_password", api.AppHandler(resetPassword)).Methods("GET")
|
mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/reset_password", api.AppHandler(resetPassword)).Methods("GET")
|
||||||
|
mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/claim", api.AppHandler(claimAccount)).Methods("GET")
|
||||||
mainrouter.Handle("/{team}/pl/{postid}", api.AppHandler(postPermalink)).Methods("GET") // Bug in gorilla.mux prevents us from using regex here.
|
mainrouter.Handle("/{team}/pl/{postid}", api.AppHandler(postPermalink)).Methods("GET") // Bug in gorilla.mux prevents us from using regex here.
|
||||||
mainrouter.Handle("/{team}/login/{service}", api.AppHandler(loginWithOAuth)).Methods("GET") // Bug in gorilla.mux prevents us from using regex here.
|
mainrouter.Handle("/{team}/login/{service}", api.AppHandler(loginWithOAuth)).Methods("GET") // Bug in gorilla.mux prevents us from using regex here.
|
||||||
mainrouter.Handle("/{team}/channels/{channelname}", api.AppHandler(getChannel)).Methods("GET") // Bug in gorilla.mux prevents us from using regex here.
|
mainrouter.Handle("/{team}/channels/{channelname}", api.AppHandler(getChannel)).Methods("GET") // Bug in gorilla.mux prevents us from using regex here.
|
||||||
@@ -565,7 +564,7 @@ func verifyEmail(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
c.LogAudit("Email Verified")
|
c.LogAudit("Email Verified")
|
||||||
http.Redirect(w, r, api.GetProtocol(r)+"://"+r.Host+"/"+name+"/login?verified=true&email="+url.QueryEscape(email), http.StatusTemporaryRedirect)
|
http.Redirect(w, r, api.GetProtocol(r)+"://"+r.Host+"/"+name+"/login?extra=verified&email="+url.QueryEscape(email), http.StatusTemporaryRedirect)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -687,89 +686,63 @@ func signupWithOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
redirectUri := c.GetSiteURL() + "/signup/" + service + "/complete"
|
stateProps := map[string]string{}
|
||||||
|
stateProps["action"] = model.OAUTH_ACTION_SIGNUP
|
||||||
|
|
||||||
api.GetAuthorizationCode(c, w, r, teamName, service, redirectUri, "")
|
if authUrl, err := api.GetAuthorizationCode(c, service, teamName, stateProps, ""); err != nil {
|
||||||
|
c.Err = err
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
http.Redirect(w, r, authUrl, http.StatusFound)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func signupCompleteOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
func completeOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
||||||
params := mux.Vars(r)
|
params := mux.Vars(r)
|
||||||
service := params["service"]
|
service := params["service"]
|
||||||
|
|
||||||
code := r.URL.Query().Get("code")
|
code := r.URL.Query().Get("code")
|
||||||
state := r.URL.Query().Get("state")
|
state := r.URL.Query().Get("state")
|
||||||
|
|
||||||
uri := c.GetSiteURL() + "/signup/" + service + "/complete"
|
uri := c.GetSiteURL() + "/" + service + "/complete"
|
||||||
|
|
||||||
if body, team, err := api.AuthorizeOAuthUser(service, code, state, uri); err != nil {
|
if body, team, props, err := api.AuthorizeOAuthUser(service, code, state, uri); err != nil {
|
||||||
c.Err = err
|
c.Err = err
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
var user *model.User
|
action := props["action"]
|
||||||
provider := einterfaces.GetOauthProvider(service)
|
switch action {
|
||||||
if provider == nil {
|
case model.OAUTH_ACTION_SIGNUP:
|
||||||
c.Err = model.NewAppError("signupCompleteOAuth", service+" oauth not avlailable on this server", "")
|
api.CreateOAuthUser(c, w, r, service, body, team)
|
||||||
return
|
if c.Err == nil {
|
||||||
} else {
|
root(c, w, r)
|
||||||
user = provider.GetUserFromJson(body)
|
|
||||||
}
|
|
||||||
|
|
||||||
if user == nil {
|
|
||||||
c.Err = model.NewAppError("signupCompleteOAuth", "Could not create user out of "+service+" user object", "")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
suchan := api.Srv.Store.User().GetByAuth(team.Id, user.AuthData, service)
|
|
||||||
euchan := api.Srv.Store.User().GetByEmail(team.Id, user.Email)
|
|
||||||
|
|
||||||
if team.Email == "" {
|
|
||||||
team.Email = user.Email
|
|
||||||
if result := <-api.Srv.Store.Team().Update(team); result.Err != nil {
|
|
||||||
c.Err = result.Err
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
} else {
|
break
|
||||||
found := true
|
case model.OAUTH_ACTION_LOGIN:
|
||||||
count := 0
|
api.LoginByOAuth(c, w, r, service, body, team)
|
||||||
for found {
|
if c.Err == nil {
|
||||||
if found = api.IsUsernameTaken(user.Username, team.Id); c.Err != nil {
|
root(c, w, r)
|
||||||
return
|
|
||||||
} else if found {
|
|
||||||
user.Username = user.Username + strconv.Itoa(count)
|
|
||||||
count += 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
break
|
||||||
|
case model.OAUTH_ACTION_EMAIL_TO_SSO:
|
||||||
|
api.CompleteSwitchWithOAuth(c, w, r, service, body, team, props["email"])
|
||||||
|
if c.Err == nil {
|
||||||
|
http.Redirect(w, r, api.GetProtocol(r)+"://"+r.Host+"/"+team.Name+"/login?extra=signin_change", http.StatusTemporaryRedirect)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case model.OAUTH_ACTION_SSO_TO_EMAIL:
|
||||||
|
api.LoginByOAuth(c, w, r, service, body, team)
|
||||||
|
if c.Err == nil {
|
||||||
|
http.Redirect(w, r, api.GetProtocol(r)+"://"+r.Host+"/"+team.Name+"/"+"/claim?email="+url.QueryEscape(props["email"]), http.StatusTemporaryRedirect)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
api.LoginByOAuth(c, w, r, service, body, team)
|
||||||
|
if c.Err == nil {
|
||||||
|
root(c, w, r)
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if result := <-suchan; result.Err == nil {
|
|
||||||
c.Err = model.NewAppError("signupCompleteOAuth", "This "+service+" account has already been used to sign up for team "+team.DisplayName, "email="+user.Email)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if result := <-euchan; result.Err == nil {
|
|
||||||
c.Err = model.NewAppError("signupCompleteOAuth", "Team "+team.DisplayName+" already has a user with the email address attached to your "+service+" account", "email="+user.Email)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
user.TeamId = team.Id
|
|
||||||
user.EmailVerified = true
|
|
||||||
|
|
||||||
ruser, err := api.CreateUser(team, user)
|
|
||||||
if err != nil {
|
|
||||||
c.Err = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
api.Login(c, w, r, ruser, "")
|
|
||||||
|
|
||||||
if c.Err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
page := NewHtmlTemplatePage("home", "Home")
|
|
||||||
page.Team = team
|
|
||||||
page.User = ruser
|
|
||||||
page.Render(c, w)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -791,57 +764,14 @@ func loginWithOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
redirectUri := c.GetSiteURL() + "/login/" + service + "/complete"
|
stateProps := map[string]string{}
|
||||||
|
stateProps["action"] = model.OAUTH_ACTION_LOGIN
|
||||||
|
|
||||||
api.GetAuthorizationCode(c, w, r, teamName, service, redirectUri, loginHint)
|
if authUrl, err := api.GetAuthorizationCode(c, service, teamName, stateProps, loginHint); err != nil {
|
||||||
}
|
|
||||||
|
|
||||||
func loginCompleteOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
|
||||||
params := mux.Vars(r)
|
|
||||||
service := params["service"]
|
|
||||||
|
|
||||||
code := r.URL.Query().Get("code")
|
|
||||||
state := r.URL.Query().Get("state")
|
|
||||||
|
|
||||||
uri := c.GetSiteURL() + "/login/" + service + "/complete"
|
|
||||||
|
|
||||||
if body, team, err := api.AuthorizeOAuthUser(service, code, state, uri); err != nil {
|
|
||||||
c.Err = err
|
c.Err = err
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
authData := ""
|
http.Redirect(w, r, authUrl, http.StatusFound)
|
||||||
provider := einterfaces.GetOauthProvider(service)
|
|
||||||
if provider == nil {
|
|
||||||
c.Err = model.NewAppError("signupCompleteOAuth", service+" oauth not avlailable on this server", "")
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
authData = provider.GetAuthDataFromJson(body)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(authData) == 0 {
|
|
||||||
c.Err = model.NewAppError("loginCompleteOAuth", "Could not parse auth data out of "+service+" user object", "")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var user *model.User
|
|
||||||
if result := <-api.Srv.Store.User().GetByAuth(team.Id, authData, service); result.Err != nil {
|
|
||||||
c.Err = result.Err
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
user = result.Data.(*model.User)
|
|
||||||
api.Login(c, w, r, user, "")
|
|
||||||
|
|
||||||
if c.Err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
page := NewHtmlTemplatePage("home", "Home")
|
|
||||||
page.Team = team
|
|
||||||
page.User = user
|
|
||||||
page.Render(c, w)
|
|
||||||
|
|
||||||
root(c, w, r)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1172,3 +1102,58 @@ func incomingWebhook(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Header().Set("Content-Type", "text/plain")
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
w.Write([]byte("ok"))
|
w.Write([]byte("ok"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func claimAccount(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !CheckBrowserCompatability(c, r) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
params := mux.Vars(r)
|
||||||
|
teamName := params["team"]
|
||||||
|
email := r.URL.Query().Get("email")
|
||||||
|
newType := r.URL.Query().Get("new_type")
|
||||||
|
|
||||||
|
var team *model.Team
|
||||||
|
if tResult := <-api.Srv.Store.Team().GetByName(teamName); tResult.Err != nil {
|
||||||
|
l4g.Error("Couldn't find team name=%v, err=%v", teamName, tResult.Err.Message)
|
||||||
|
http.Redirect(w, r, api.GetProtocol(r)+"://"+r.Host, http.StatusTemporaryRedirect)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
team = tResult.Data.(*model.Team)
|
||||||
|
}
|
||||||
|
|
||||||
|
authType := ""
|
||||||
|
if len(email) != 0 {
|
||||||
|
if uResult := <-api.Srv.Store.User().GetByEmail(team.Id, email); uResult.Err != nil {
|
||||||
|
l4g.Error("Couldn't find user teamid=%v, email=%v, err=%v", team.Id, email, uResult.Err.Message)
|
||||||
|
http.Redirect(w, r, api.GetProtocol(r)+"://"+r.Host, http.StatusTemporaryRedirect)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
user := uResult.Data.(*model.User)
|
||||||
|
authType = user.AuthService
|
||||||
|
|
||||||
|
// if user is not logged in to their SSO account, ask them to log in
|
||||||
|
if len(authType) != 0 && user.Id != c.Session.UserId {
|
||||||
|
stateProps := map[string]string{}
|
||||||
|
stateProps["action"] = model.OAUTH_ACTION_SSO_TO_EMAIL
|
||||||
|
stateProps["email"] = email
|
||||||
|
|
||||||
|
if authUrl, err := api.GetAuthorizationCode(c, authType, team.Name, stateProps, ""); err != nil {
|
||||||
|
c.Err = err
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
http.Redirect(w, r, authUrl, http.StatusFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
page := NewHtmlTemplatePage("claim_account", "Claim Account")
|
||||||
|
page.Props["Email"] = email
|
||||||
|
page.Props["CurrentType"] = authType
|
||||||
|
page.Props["NewType"] = newType
|
||||||
|
page.Props["TeamDisplayName"] = team.DisplayName
|
||||||
|
page.Props["TeamName"] = team.Name
|
||||||
|
|
||||||
|
page.Render(c, w)
|
||||||
|
}
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user