summaryrefslogtreecommitdiffstats
path: root/api
diff options
context:
space:
mode:
authorChristopher Speller <crspeller@gmail.com>2016-02-08 07:26:10 -0500
committerChristopher Speller <crspeller@gmail.com>2016-03-14 00:38:25 -0400
commitd7cdcf082fab6c0cb7c2fe4bed821bd1a8000e69 (patch)
tree49a0de30cdc2ac461e72a242ae9a5593fcd6c8b9 /api
parent08f0800adef926e8b69ebea70e4995b89f5c3f3c (diff)
downloadchat-d7cdcf082fab6c0cb7c2fe4bed821bd1a8000e69.tar.gz
chat-d7cdcf082fab6c0cb7c2fe4bed821bd1a8000e69.tar.bz2
chat-d7cdcf082fab6c0cb7c2fe4bed821bd1a8000e69.zip
Convering client to react-router.
Diffstat (limited to 'api')
-rw-r--r--api/api.go43
-rw-r--r--api/context.go137
-rw-r--r--api/license.go20
-rw-r--r--api/license_test.go22
-rw-r--r--api/oauth.go343
-rw-r--r--api/post.go7
-rw-r--r--api/team.go114
-rw-r--r--api/team_test.go111
-rw-r--r--api/templates/email_change_body.html41
-rw-r--r--api/templates/email_change_subject.html1
-rw-r--r--api/templates/email_change_verify_body.html44
-rw-r--r--api/templates/email_change_verify_subject.html1
-rw-r--r--api/templates/email_footer.html13
-rw-r--r--api/templates/email_info.html7
-rw-r--r--api/templates/error.html37
-rw-r--r--api/templates/find_teams_body.html52
-rw-r--r--api/templates/find_teams_subject.html1
-rw-r--r--api/templates/invite_body.html46
-rw-r--r--api/templates/invite_subject.html1
-rw-r--r--api/templates/password_change_body.html43
-rw-r--r--api/templates/password_change_subject.html1
-rw-r--r--api/templates/post_body.html45
-rw-r--r--api/templates/post_subject.html1
-rw-r--r--api/templates/reset_body.html46
-rw-r--r--api/templates/reset_subject.html1
-rw-r--r--api/templates/signin_change_body.html43
-rw-r--r--api/templates/signin_change_subject.html1
-rw-r--r--api/templates/signup_team_body.html44
-rw-r--r--api/templates/signup_team_subject.html1
-rw-r--r--api/templates/verify_body.html44
-rw-r--r--api/templates/verify_subject.html1
-rw-r--r--api/templates/welcome_body.html51
-rw-r--r--api/templates/welcome_subject.html1
-rw-r--r--api/user.go146
-rw-r--r--api/user_test.go35
35 files changed, 612 insertions, 933 deletions
diff --git a/api/api.go b/api/api.go
index 4fecd3dd4..20f77e558 100644
--- a/api/api.go
+++ b/api/api.go
@@ -4,47 +4,15 @@
package api
import (
- "bytes"
- l4g "github.com/alecthomas/log4go"
+ "net/http"
+
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
- "html/template"
- "net/http"
_ "github.com/cloudfoundry/jibber_jabber"
_ "github.com/nicksnyder/go-i18n/i18n"
)
-var ServerTemplates *template.Template
-
-type ServerTemplatePage Page
-
-func NewServerTemplatePage(templateName, locale string) *ServerTemplatePage {
- return &ServerTemplatePage{
- TemplateName: templateName,
- Props: make(map[string]string),
- Extra: make(map[string]string),
- Html: make(map[string]template.HTML),
- ClientCfg: utils.ClientCfg,
- Locale: locale,
- }
-}
-
-func (me *ServerTemplatePage) Render() string {
- var text bytes.Buffer
-
- T := utils.GetUserTranslations(me.Locale)
- me.Props["Footer"] = T("api.templates.email_footer")
- me.Html["EmailInfo"] = template.HTML(T("api.templates.email_info",
- map[string]interface{}{"SupportEmail": me.ClientCfg["SupportEmail"], "SiteName": me.ClientCfg["SiteName"]}))
-
- if err := ServerTemplates.ExecuteTemplate(&text, me.TemplateName, me); err != nil {
- l4g.Error(utils.T("api.api.render.error"), me.TemplateName, err)
- }
-
- return text.String()
-}
-
func InitApi() {
r := Srv.Router.PathPrefix("/api/v1").Subrouter()
InitUser(r)
@@ -60,12 +28,7 @@ func InitApi() {
InitPreference(r)
InitLicense(r)
- templatesDir := utils.FindDir("api/templates")
- l4g.Debug(utils.T("api.api.init.parsing_templates.debug"), templatesDir)
- var err error
- if ServerTemplates, err = template.ParseGlob(templatesDir + "*.html"); err != nil {
- l4g.Error(utils.T("api.api.init.parsing_templates.error"), err)
- }
+ utils.InitHTML()
}
func HandleEtag(etag string, w http.ResponseWriter, r *http.Request) bool {
diff --git a/api/context.go b/api/context.go
index edcdcbfef..eed035daf 100644
--- a/api/context.go
+++ b/api/context.go
@@ -5,11 +5,9 @@ package api
import (
"fmt"
- "html/template"
"net"
"net/http"
"net/url"
- "strconv"
"strings"
l4g "github.com/alecthomas/log4go"
@@ -31,33 +29,16 @@ var allowedMethods []string = []string{
}
type Context struct {
- Session model.Session
- RequestId string
- IpAddress string
- Path string
- Err *model.AppError
- teamURLValid bool
- teamURL string
- siteURL string
- SessionTokenIndex int64
- T goi18n.TranslateFunc
- Locale string
-}
-
-type Page struct {
- TemplateName string
- Props map[string]string
- Extra map[string]string
- Html map[string]template.HTML
- ClientCfg map[string]string
- ClientLicense map[string]string
- User *model.User
- Team *model.Team
- Channel *model.Channel
- Preferences *model.Preferences
- PostID string
- SessionTokenIndex int64
- Locale string
+ Session model.Session
+ RequestId string
+ IpAddress string
+ Path string
+ Err *model.AppError
+ teamURLValid bool
+ teamURL string
+ siteURL string
+ T goi18n.TranslateFunc
+ Locale string
}
func ApiAppHandler(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
@@ -121,37 +102,8 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Attempt to parse the token from the cookie
if len(token) == 0 {
- tokens := GetMultiSessionCookieTokens(r)
- if len(tokens) > 0 {
- // If there is only 1 token in the cookie then just use it like normal
- if len(tokens) == 1 {
- token = tokens[0]
- } else {
- // If it is a multi-session token then find the correct session
- sessionTokenIndexStr := r.URL.Query().Get(model.SESSION_TOKEN_INDEX)
- sessionTokenIndex := int64(-1)
- if len(sessionTokenIndexStr) > 0 {
- if index, err := strconv.ParseInt(sessionTokenIndexStr, 10, 64); err == nil {
- sessionTokenIndex = index
- }
- } else {
- sessionTokenIndexStr := r.Header.Get(model.HEADER_MM_SESSION_TOKEN_INDEX)
- if len(sessionTokenIndexStr) > 0 {
- if index, err := strconv.ParseInt(sessionTokenIndexStr, 10, 64); err == nil {
- sessionTokenIndex = index
- }
- }
- }
-
- if sessionTokenIndex >= 0 && sessionTokenIndex < int64(len(tokens)) {
- token = tokens[sessionTokenIndex]
- c.SessionTokenIndex = sessionTokenIndex
- } else {
- c.SessionTokenIndex = -1
- }
- }
- } else {
- c.SessionTokenIndex = -1
+ if cookie, err := r.Cookie(model.SESSION_COOKIE_TOKEN); err == nil {
+ token = cookie.Value
}
}
@@ -185,8 +137,10 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if session == nil || session.IsExpired() {
c.RemoveSessionCookie(w, r)
- c.Err = model.NewLocAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token)
- c.Err.StatusCode = http.StatusUnauthorized
+ if h.requireUser || h.requireSystemAdmin {
+ c.Err = model.NewLocAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token)
+ c.Err.StatusCode = http.StatusUnauthorized
+ }
} else if !session.IsOAuth && isTokenFromQueryString {
c.Err = model.NewLocAppError("ServeHTTP", "api.context.token_provided.app_error", nil, "token="+token)
c.Err.StatusCode = http.StatusUnauthorized
@@ -390,22 +344,6 @@ func (c *Context) IsTeamAdmin() bool {
}
func (c *Context) RemoveSessionCookie(w http.ResponseWriter, r *http.Request) {
-
- // multiToken := ""
- // if oldMultiCookie, err := r.Cookie(model.SESSION_COOKIE_TOKEN); err == nil {
- // multiToken = oldMultiCookie.Value
- // }
-
- // multiCookie := &http.Cookie{
- // Name: model.SESSION_COOKIE_TOKEN,
- // Value: strings.TrimSpace(strings.Replace(multiToken, c.Session.Token, "", -1)),
- // Path: "/",
- // MaxAge: model.SESSION_TIME_WEB_IN_SECS,
- // HttpOnly: true,
- // }
-
- //http.SetCookie(w, multiCookie)
-
cookie := &http.Cookie{
Name: model.SESSION_COOKIE_TOKEN,
Value: "",
@@ -538,23 +476,25 @@ func IsPrivateIpAddress(ipAddress string) bool {
}
func RenderWebError(err *model.AppError, w http.ResponseWriter, r *http.Request) {
- props := make(map[string]string)
- props["Message"] = err.Message
- props["Details"] = err.DetailedError
+ T, locale := utils.GetTranslationsAndLocale(w, r)
+ page := utils.NewHTMLTemplate("error", locale)
+ page.Props["Message"] = err.Message
+ page.Props["Details"] = err.DetailedError
pathParts := strings.Split(r.URL.Path, "/")
if len(pathParts) > 1 {
- props["SiteURL"] = GetProtocol(r) + "://" + r.Host + "/" + pathParts[1]
+ page.Props["SiteURL"] = GetProtocol(r) + "://" + r.Host + "/" + pathParts[1]
} else {
- props["SiteURL"] = GetProtocol(r) + "://" + r.Host
+ page.Props["SiteURL"] = GetProtocol(r) + "://" + r.Host
}
- T, _ := utils.GetTranslationsAndLocale(w, r)
- props["Title"] = T("api.templates.error.title", map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"]})
- props["Link"] = T("api.templates.error.link")
+ page.Props["Title"] = T("api.templates.error.title", map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"]})
+ page.Props["Link"] = T("api.templates.error.link")
w.WriteHeader(err.StatusCode)
- ServerTemplates.ExecuteTemplate(w, "error.html", Page{Props: props, ClientCfg: utils.ClientCfg})
+ if rErr := page.RenderToWriter(w); rErr != nil {
+ l4g.Error("Failed to create error page: " + rErr.Error() + ", Original error: " + err.Error())
+ }
}
func Handle404(w http.ResponseWriter, r *http.Request) {
@@ -588,29 +528,6 @@ func GetSession(token string) *model.Session {
return session
}
-func GetMultiSessionCookieTokens(r *http.Request) []string {
- if multiCookie, err := r.Cookie(model.SESSION_COOKIE_TOKEN); err == nil {
- multiToken := multiCookie.Value
-
- if len(multiToken) > 0 {
- return strings.Split(multiToken, " ")
- }
- }
-
- return []string{}
-}
-
-func FindMultiSessionForTeamId(r *http.Request, teamId string) (int64, *model.Session) {
- for index, token := range GetMultiSessionCookieTokens(r) {
- s := GetSession(token)
- if s != nil && !s.IsExpired() && s.TeamId == teamId {
- return int64(index), s
- }
- }
-
- return -1, nil
-}
-
func AddSessionToCache(session *model.Session) {
sessionCache.AddWithExpiresInSecs(session.Token, session, int64(*utils.Cfg.ServiceSettings.SessionCacheInMinutes*60))
}
diff --git a/api/license.go b/api/license.go
index 23e7946c8..542b45e26 100644
--- a/api/license.go
+++ b/api/license.go
@@ -20,6 +20,7 @@ func InitLicense(r *mux.Router) {
sr := r.PathPrefix("/license").Subrouter()
sr.Handle("/add", ApiAdminSystemRequired(addLicense)).Methods("POST")
sr.Handle("/remove", ApiAdminSystemRequired(removeLicense)).Methods("POST")
+ sr.Handle("/client_config", ApiAppHandler(getClientLicenceConfig)).Methods("GET")
}
func addLicense(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -130,3 +131,22 @@ func removeLicense(c *Context, w http.ResponseWriter, r *http.Request) {
rdata["status"] = "ok"
w.Write([]byte(model.MapToJson(rdata)))
}
+
+func getClientLicenceConfig(c *Context, w http.ResponseWriter, r *http.Request) {
+ config := utils.ClientLicense
+
+ var etag string
+ if config["IsLicensed"] == "false" {
+ etag = model.Etag(config["IsLicensed"])
+ } else {
+ etag = model.Etag(config["IsLicensed"], config["IssuedAt"])
+ }
+
+ if HandleEtag(etag, w, r) {
+ return
+ }
+
+ w.Header().Set(model.HEADER_ETAG_SERVER, etag)
+
+ w.Write([]byte(model.MapToJson(config)))
+}
diff --git a/api/license_test.go b/api/license_test.go
new file mode 100644
index 000000000..b34aeb7a6
--- /dev/null
+++ b/api/license_test.go
@@ -0,0 +1,22 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package api
+
+import (
+ "testing"
+)
+
+func TestGetLicenceConfig(t *testing.T) {
+ Setup()
+
+ if result, err := Client.GetClientLicenceConfig(); err != nil {
+ t.Fatal(err)
+ } else {
+ cfg := result.Data.(map[string]string)
+
+ if _, ok := cfg["IsLicensed"]; !ok {
+ t.Fatal(cfg)
+ }
+ }
+}
diff --git a/api/oauth.go b/api/oauth.go
index 1ae3dbf78..9b7f3699d 100644
--- a/api/oauth.go
+++ b/api/oauth.go
@@ -5,12 +5,15 @@ package api
import (
"fmt"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+
l4g "github.com/alecthomas/log4go"
"github.com/gorilla/mux"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
- "net/http"
- "net/url"
)
func InitOAuth(r *mux.Router) {
@@ -20,6 +23,17 @@ func InitOAuth(r *mux.Router) {
sr.Handle("/register", ApiUserRequired(registerOAuthApp)).Methods("POST")
sr.Handle("/allow", ApiUserRequired(allowOAuth)).Methods("GET")
+ sr.Handle("/{service:[A-Za-z]+}/complete", AppHandlerIndependent(completeOAuth)).Methods("GET")
+ sr.Handle("/{service:[A-Za-z]+}/login", AppHandlerIndependent(loginWithOAuth)).Methods("GET")
+ sr.Handle("/{service:[A-Za-z]+}/signup", AppHandlerIndependent(signupWithOAuth)).Methods("GET")
+ sr.Handle("/authorize", ApiUserRequired(authorizeOAuth)).Methods("GET")
+ sr.Handle("/access_token", ApiAppHandler(getAccessToken)).Methods("POST")
+
+ // Also handle this a the old routes remove soon apiv2?
+ mr := Srv.Router
+ mr.Handle("/authorize", ApiUserRequired(authorizeOAuth)).Methods("GET")
+ mr.Handle("/access_token", ApiAppHandler(getAccessToken)).Methods("POST")
+ mr.Handle("/{service:[A-Za-z]+}/complete", AppHandlerIndependent(completeOAuth)).Methods("GET")
}
func registerOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -163,3 +177,328 @@ func GetAuthData(code string) *model.AuthData {
return result.Data.(*model.AuthData)
}
}
+
+func completeOAuth(c *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() + "/api/v1/oauth/" + service + "/complete"
+
+ if body, team, props, err := AuthorizeOAuthUser(service, code, state, uri); err != nil {
+ c.Err = err
+ return
+ } else {
+ action := props["action"]
+ switch action {
+ case model.OAUTH_ACTION_SIGNUP:
+ CreateOAuthUser(c, w, r, service, body, team)
+ if c.Err == nil {
+ http.Redirect(w, r, GetProtocol(r)+"://"+r.Host+"/"+team.Name, http.StatusTemporaryRedirect)
+ }
+ break
+ case model.OAUTH_ACTION_LOGIN:
+ LoginByOAuth(c, w, r, service, body, team)
+ if c.Err == nil {
+ http.Redirect(w, r, GetProtocol(r)+"://"+r.Host+"/"+team.Name, http.StatusTemporaryRedirect)
+ }
+ break
+ case model.OAUTH_ACTION_EMAIL_TO_SSO:
+ CompleteSwitchWithOAuth(c, w, r, service, body, team, props["email"])
+ if c.Err == nil {
+ http.Redirect(w, r, GetProtocol(r)+"://"+r.Host+"/"+team.Name+"/login?extra=signin_change", http.StatusTemporaryRedirect)
+ }
+ break
+ case model.OAUTH_ACTION_SSO_TO_EMAIL:
+ LoginByOAuth(c, w, r, service, body, team)
+ if c.Err == nil {
+ http.Redirect(w, r, GetProtocol(r)+"://"+r.Host+"/"+team.Name+"/"+"/claim?email="+url.QueryEscape(props["email"]), http.StatusTemporaryRedirect)
+ }
+ break
+ default:
+ LoginByOAuth(c, w, r, service, body, team)
+ if c.Err == nil {
+ http.Redirect(w, r, GetProtocol(r)+"://"+r.Host+"/"+team.Name, http.StatusTemporaryRedirect)
+ }
+ break
+ }
+ }
+}
+
+func authorizeOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
+ if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
+ c.Err = model.NewLocAppError("authorizeOAuth", "web.authorize_oauth.disabled.app_error", nil, "")
+ c.Err.StatusCode = http.StatusNotImplemented
+ return
+ }
+
+ responseType := r.URL.Query().Get("response_type")
+ clientId := r.URL.Query().Get("client_id")
+ redirect := r.URL.Query().Get("redirect_uri")
+ scope := r.URL.Query().Get("scope")
+ state := r.URL.Query().Get("state")
+
+ if len(responseType) == 0 || len(clientId) == 0 || len(redirect) == 0 {
+ c.Err = model.NewLocAppError("authorizeOAuth", "web.authorize_oauth.missing.app_error", nil, "")
+ return
+ }
+
+ var app *model.OAuthApp
+ if result := <-Srv.Store.OAuth().GetApp(clientId); result.Err != nil {
+ c.Err = result.Err
+ return
+ } else {
+ app = result.Data.(*model.OAuthApp)
+ }
+
+ var team *model.Team
+ if result := <-Srv.Store.Team().Get(c.Session.TeamId); result.Err != nil {
+ c.Err = result.Err
+ return
+ } else {
+ team = result.Data.(*model.Team)
+ }
+
+ page := utils.NewHTMLTemplate("authorize", c.Locale)
+ page.Props["Title"] = c.T("web.authorize_oauth.title")
+ page.Props["TeamName"] = team.Name
+ page.Props["AppName"] = app.Name
+ page.Props["ResponseType"] = responseType
+ page.Props["ClientId"] = clientId
+ page.Props["RedirectUri"] = redirect
+ page.Props["Scope"] = scope
+ page.Props["State"] = state
+ if err := page.RenderToWriter(w); err != nil {
+ c.SetUnknownError(page.TemplateName, err.Error())
+ }
+}
+
+func getAccessToken(c *Context, w http.ResponseWriter, r *http.Request) {
+ if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.disabled.app_error", nil, "")
+ c.Err.StatusCode = http.StatusNotImplemented
+ return
+ }
+
+ c.LogAudit("attempt")
+
+ r.ParseForm()
+
+ grantType := r.FormValue("grant_type")
+ if grantType != model.ACCESS_TOKEN_GRANT_TYPE {
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.bad_grant.app_error", nil, "")
+ return
+ }
+
+ clientId := r.FormValue("client_id")
+ if len(clientId) != 26 {
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.bad_client_id.app_error", nil, "")
+ return
+ }
+
+ secret := r.FormValue("client_secret")
+ if len(secret) == 0 {
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.bad_client_secret.app_error", nil, "")
+ return
+ }
+
+ code := r.FormValue("code")
+ if len(code) == 0 {
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.missing_code.app_error", nil, "")
+ return
+ }
+
+ redirectUri := r.FormValue("redirect_uri")
+
+ achan := Srv.Store.OAuth().GetApp(clientId)
+ tchan := Srv.Store.OAuth().GetAccessDataByAuthCode(code)
+
+ authData := GetAuthData(code)
+
+ if authData == nil {
+ c.LogAudit("fail - invalid auth code")
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.expired_code.app_error", nil, "")
+ return
+ }
+
+ uchan := Srv.Store.User().Get(authData.UserId)
+
+ if authData.IsExpired() {
+ c.LogAudit("fail - auth code expired")
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.expired_code.app_error", nil, "")
+ return
+ }
+
+ if authData.RedirectUri != redirectUri {
+ c.LogAudit("fail - redirect uri provided did not match previous redirect uri")
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.redirect_uri.app_error", nil, "")
+ return
+ }
+
+ if !model.ComparePassword(code, fmt.Sprintf("%v:%v:%v:%v", clientId, redirectUri, authData.CreateAt, authData.UserId)) {
+ c.LogAudit("fail - auth code is invalid")
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.expired_code.app_error", nil, "")
+ return
+ }
+
+ var app *model.OAuthApp
+ if result := <-achan; result.Err != nil {
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.credentials.app_error", nil, "")
+ return
+ } else {
+ app = result.Data.(*model.OAuthApp)
+ }
+
+ if !model.ComparePassword(app.ClientSecret, secret) {
+ c.LogAudit("fail - invalid client credentials")
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.credentials.app_error", nil, "")
+ return
+ }
+
+ callback := redirectUri
+ if len(callback) == 0 {
+ callback = app.CallbackUrls[0]
+ }
+
+ if result := <-tchan; result.Err != nil {
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.internal.app_error", nil, "")
+ return
+ } else if result.Data != nil {
+ c.LogAudit("fail - auth code has been used previously")
+ accessData := result.Data.(*model.AccessData)
+
+ // Revoke access token, related auth code, and session from DB as well as from cache
+ if err := RevokeAccessToken(accessData.Token); err != nil {
+ l4g.Error(utils.T("web.get_access_token.revoking.error") + err.Message)
+ }
+
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.exchanged.app_error", nil, "")
+ return
+ }
+
+ var user *model.User
+ if result := <-uchan; result.Err != nil {
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.internal_user.app_error", nil, "")
+ return
+ } else {
+ user = result.Data.(*model.User)
+ }
+
+ session := &model.Session{UserId: user.Id, TeamId: user.TeamId, Roles: user.Roles, IsOAuth: true}
+
+ if result := <-Srv.Store.Session().Save(session); result.Err != nil {
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.internal_session.app_error", nil, "")
+ return
+ } else {
+ session = result.Data.(*model.Session)
+ AddSessionToCache(session)
+ }
+
+ accessData := &model.AccessData{AuthCode: authData.Code, Token: session.Token, RedirectUri: callback}
+
+ if result := <-Srv.Store.OAuth().SaveAccessData(accessData); result.Err != nil {
+ l4g.Error(result.Err)
+ c.Err = model.NewLocAppError("getAccessToken", "web.get_access_token.internal_saving.app_error", nil, "")
+ return
+ }
+
+ accessRsp := &model.AccessResponse{AccessToken: session.Token, TokenType: model.ACCESS_TOKEN_TYPE, ExpiresIn: int32(*utils.Cfg.ServiceSettings.SessionLengthSSOInDays * 60 * 60 * 24)}
+
+ w.Header().Set("Content-Type", "application/json")
+ w.Header().Set("Cache-Control", "no-store")
+ w.Header().Set("Pragma", "no-cache")
+
+ c.LogAuditWithUserId(user.Id, "success")
+
+ w.Write([]byte(accessRsp.ToJson()))
+}
+
+func loginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
+ params := mux.Vars(r)
+ service := params["service"]
+ loginHint := r.URL.Query().Get("login_hint")
+ teamName := r.URL.Query().Get("team")
+
+ if len(teamName) == 0 {
+ c.Err = model.NewLocAppError("loginWithOAuth", "web.login_with_oauth.invalid_team.app_error", nil, "team_name="+teamName)
+ c.Err.StatusCode = http.StatusBadRequest
+ return
+ }
+
+ // Make sure team exists
+ if result := <-Srv.Store.Team().GetByName(teamName); result.Err != nil {
+ c.Err = result.Err
+ return
+ }
+
+ stateProps := map[string]string{}
+ stateProps["action"] = model.OAUTH_ACTION_LOGIN
+
+ if authUrl, err := GetAuthorizationCode(c, service, teamName, stateProps, loginHint); err != nil {
+ c.Err = err
+ return
+ } else {
+ http.Redirect(w, r, authUrl, http.StatusFound)
+ }
+}
+
+func signupWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
+ params := mux.Vars(r)
+ service := params["service"]
+ teamName := r.URL.Query().Get("team")
+
+ if !utils.Cfg.TeamSettings.EnableUserCreation {
+ c.Err = model.NewLocAppError("signupTeam", "web.singup_with_oauth.disabled.app_error", nil, "")
+ c.Err.StatusCode = http.StatusNotImplemented
+ return
+ }
+
+ if len(teamName) == 0 {
+ c.Err = model.NewLocAppError("signupWithOAuth", "web.singup_with_oauth.invalid_team.app_error", nil, "team_name="+teamName)
+ c.Err.StatusCode = http.StatusBadRequest
+ return
+ }
+
+ hash := r.URL.Query().Get("h")
+
+ var team *model.Team
+ if result := <-Srv.Store.Team().GetByName(teamName); result.Err != nil {
+ c.Err = result.Err
+ return
+ } else {
+ team = result.Data.(*model.Team)
+ }
+
+ if IsVerifyHashRequired(nil, team, hash) {
+ data := r.URL.Query().Get("d")
+ props := model.MapFromJson(strings.NewReader(data))
+
+ if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt)) {
+ c.Err = model.NewLocAppError("signupWithOAuth", "web.singup_with_oauth.invalid_link.app_error", nil, "")
+ return
+ }
+
+ t, err := strconv.ParseInt(props["time"], 10, 64)
+ if err != nil || model.GetMillis()-t > 1000*60*60*48 { // 48 hours
+ c.Err = model.NewLocAppError("signupWithOAuth", "web.singup_with_oauth.expired_link.app_error", nil, "")
+ return
+ }
+
+ if team.Id != props["id"] {
+ c.Err = model.NewLocAppError("signupWithOAuth", "web.singup_with_oauth.invalid_team.app_error", nil, data)
+ return
+ }
+ }
+
+ stateProps := map[string]string{}
+ stateProps["action"] = model.OAUTH_ACTION_SIGNUP
+
+ if authUrl, err := GetAuthorizationCode(c, service, teamName, stateProps, ""); err != nil {
+ c.Err = err
+ return
+ } else {
+ http.Redirect(w, r, authUrl, http.StatusFound)
+ }
+}
diff --git a/api/post.go b/api/post.go
index cd78b16f0..d0ec5826a 100644
--- a/api/post.go
+++ b/api/post.go
@@ -419,7 +419,7 @@ func handleWebhookEventsAndForget(c *Context, post *model.Post, team *model.Team
// copy the context and create a mock session for posting the message
mockSession := model.Session{UserId: hook.CreatorId, TeamId: hook.TeamId, IsOAuth: false}
- newContext := &Context{mockSession, model.NewId(), "", c.Path, nil, c.teamURLValid, c.teamURL, c.siteURL, 0, c.T, c.Locale}
+ newContext := &Context{mockSession, model.NewId(), "", c.Path, nil, c.teamURLValid, c.teamURL, c.siteURL, c.T, c.Locale}
if text, ok := respProps["text"]; ok {
if _, err := CreateWebhookPost(newContext, post.ChannelId, text, respProps["username"], respProps["icon_url"], post.Props, post.Type); err != nil {
@@ -604,12 +604,13 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *
year := fmt.Sprintf("%d", tm.Year())
zone, _ := tm.Zone()
- subjectPage := NewServerTemplatePage("post_subject", profileMap[id].Locale)
+ subjectPage := utils.NewHTMLTemplate("post_subject", profileMap[id].Locale)
subjectPage.Props["Subject"] = userLocale("api.templates.post_subject",
map[string]interface{}{"SubjectText": subjectText, "TeamDisplayName": team.DisplayName,
"Month": month[:3], "Day": day, "Year": year})
+ subjectPage.Props["SiteName"] = utils.Cfg.TeamSettings.SiteName
- bodyPage := NewServerTemplatePage("post_body", profileMap[id].Locale)
+ bodyPage := utils.NewHTMLTemplate("post_body", profileMap[id].Locale)
bodyPage.Props["SiteURL"] = c.GetSiteURL()
bodyPage.Props["PostMessage"] = model.ClearMentionTags(post.Message)
bodyPage.Props["TeamLink"] = teamURL + "/channels/" + channel.Name
diff --git a/api/team.go b/api/team.go
index 2f680dc76..255982522 100644
--- a/api/team.go
+++ b/api/team.go
@@ -29,13 +29,12 @@ func InitTeam(r *mux.Router) {
sr.Handle("/create_with_ldap", ApiAppHandler(createTeamWithLdap)).Methods("POST")
sr.Handle("/create_with_sso/{service:[A-Za-z]+}", ApiAppHandler(createTeamFromSSO)).Methods("POST")
sr.Handle("/signup", ApiAppHandler(signupTeam)).Methods("POST")
- sr.Handle("/all", ApiUserRequired(getAll)).Methods("GET")
+ sr.Handle("/all", ApiAppHandler(getAll)).Methods("GET")
sr.Handle("/find_team_by_name", ApiAppHandler(findTeamByName)).Methods("POST")
- sr.Handle("/find_teams", ApiAppHandler(findTeams)).Methods("POST")
- sr.Handle("/email_teams", ApiAppHandler(emailTeams)).Methods("POST")
sr.Handle("/invite_members", ApiUserRequired(inviteMembers)).Methods("POST")
sr.Handle("/update", ApiUserRequired(updateTeam)).Methods("POST")
sr.Handle("/me", ApiUserRequired(getMyTeam)).Methods("GET")
+ sr.Handle("/get_invite_info", ApiAppHandler(getInviteInfo)).Methods("POST")
// These should be moved to the global admain console
sr.Handle("/import_team", ApiUserRequired(importTeam)).Methods("POST")
sr.Handle("/export_team", ApiUserRequired(exportTeam)).Methods("GET")
@@ -60,11 +59,11 @@ func signupTeam(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- subjectPage := NewServerTemplatePage("signup_team_subject", c.Locale)
+ subjectPage := utils.NewHTMLTemplate("signup_team_subject", c.Locale)
subjectPage.Props["Subject"] = c.T("api.templates.signup_team_subject",
map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"]})
- bodyPage := NewServerTemplatePage("signup_team_body", c.Locale)
+ bodyPage := utils.NewHTMLTemplate("signup_team_body", c.Locale)
bodyPage.Props["SiteURL"] = c.GetSiteURL()
bodyPage.Props["Title"] = c.T("api.templates.signup_team_body.title")
bodyPage.Props["Button"] = c.T("api.templates.signup_team_body.button")
@@ -86,7 +85,7 @@ func signupTeam(c *Context, w http.ResponseWriter, r *http.Request) {
}
if !utils.Cfg.EmailSettings.RequireEmailVerification {
- m["follow_link"] = bodyPage.Props["Link"]
+ m["follow_link"] = fmt.Sprintf("/signup_team_complete/?d=%s&h=%s", url.QueryEscape(data), url.QueryEscape(hash))
}
w.Header().Set("Access-Control-Allow-Origin", " *")
@@ -147,7 +146,7 @@ func createTeamFromSSO(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- data := map[string]string{"follow_link": c.GetSiteURL() + "/" + rteam.Name + "/signup/" + service}
+ data := map[string]string{"follow_link": c.GetSiteURL() + "/api/v1/oauth/" + service + "/signup?team=" + rteam.Name}
w.Write([]byte(model.MapToJson(data)))
}
@@ -391,10 +390,6 @@ func isTeamCreationAllowed(c *Context, email string) bool {
}
func getAll(c *Context, w http.ResponseWriter, r *http.Request) {
- if !c.HasSystemAdminPermissions("getLogs") {
- return
- }
-
if result := <-Srv.Store.Team().GetAll(); result.Err != nil {
c.Err = result.Err
return
@@ -403,6 +398,9 @@ func getAll(c *Context, w http.ResponseWriter, r *http.Request) {
m := make(map[string]*model.Team)
for _, v := range teams {
m[v.Id] = v
+ if !c.IsSystemAdmin() {
+ m[v.Id].SanitizeForNotLoggedIn()
+ }
}
w.Write([]byte(model.TeamMapToJson(m)))
@@ -473,74 +471,6 @@ func FindTeamByName(c *Context, name string, all string) bool {
return false
}
-func findTeams(c *Context, w http.ResponseWriter, r *http.Request) {
-
- m := model.MapFromJson(r.Body)
-
- email := strings.ToLower(strings.TrimSpace(m["email"]))
-
- if email == "" {
- c.SetInvalidParam("findTeam", "email")
- return
- }
-
- if result := <-Srv.Store.Team().GetTeamsForEmail(email); result.Err != nil {
- c.Err = result.Err
- return
- } else {
- teams := result.Data.([]*model.Team)
- m := make(map[string]*model.Team)
- for _, v := range teams {
- v.Sanitize()
- m[v.Id] = v
- }
-
- w.Write([]byte(model.TeamMapToJson(m)))
- }
-}
-
-func emailTeams(c *Context, w http.ResponseWriter, r *http.Request) {
-
- m := model.MapFromJson(r.Body)
-
- email := strings.ToLower(strings.TrimSpace(m["email"]))
-
- if email == "" {
- c.SetInvalidParam("findTeam", "email")
- return
- }
-
- siteURL := c.GetSiteURL()
- subjectPage := NewServerTemplatePage("find_teams_subject", c.Locale)
- subjectPage.Props["Subject"] = c.T("api.templates.find_teams_subject",
- map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"]})
-
- bodyPage := NewServerTemplatePage("find_teams_body", c.Locale)
- bodyPage.Props["SiteURL"] = siteURL
- bodyPage.Props["Title"] = c.T("api.templates.find_teams_body.title")
- bodyPage.Props["Found"] = c.T("api.templates.find_teams_body.found")
- bodyPage.Props["NotFound"] = c.T("api.templates.find_teams_body.not_found")
-
- if result := <-Srv.Store.Team().GetTeamsForEmail(email); result.Err != nil {
- c.Err = result.Err
- } else {
- teams := result.Data.([]*model.Team)
-
- // the template expects Props to be a map with team names as the keys and the team url as the value
- props := make(map[string]string)
- for _, team := range teams {
- props[team.Name] = c.GetTeamURLFromTeam(team)
- }
- bodyPage.Extra = props
-
- if err := utils.SendMail(email, subjectPage.Render(), bodyPage.Render()); err != nil {
- l4g.Error(utils.T("api.team.email_teams.sending.error"), err)
- }
-
- w.Write([]byte(model.MapToJson(m)))
- }
-}
-
func inviteMembers(c *Context, w http.ResponseWriter, r *http.Request) {
invites := model.InvitesFromJson(r.Body)
if len(invites.Invites) == 0 {
@@ -600,11 +530,11 @@ func InviteMembers(c *Context, team *model.Team, user *model.User, invites []str
senderRole = c.T("api.team.invite_members.member")
}
- subjectPage := NewServerTemplatePage("invite_subject", c.Locale)
+ subjectPage := utils.NewHTMLTemplate("invite_subject", c.Locale)
subjectPage.Props["Subject"] = c.T("api.templates.invite_subject",
map[string]interface{}{"SenderName": sender, "TeamDisplayName": team.DisplayName, "SiteName": utils.ClientCfg["SiteName"]})
- bodyPage := NewServerTemplatePage("invite_body", c.Locale)
+ bodyPage := utils.NewHTMLTemplate("invite_body", c.Locale)
bodyPage.Props["SiteURL"] = c.GetSiteURL()
bodyPage.Props["Title"] = c.T("api.templates.invite_body.title")
bodyPage.Html["Info"] = template.HTML(c.T("api.templates.invite_body.info",
@@ -813,3 +743,25 @@ func exportTeam(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte(model.MapToJson(result)))
}
}
+
+func getInviteInfo(c *Context, w http.ResponseWriter, r *http.Request) {
+ m := model.MapFromJson(r.Body)
+ inviteId := m["invite_id"]
+
+ if result := <-Srv.Store.Team().GetByInviteId(inviteId); result.Err != nil {
+ c.Err = result.Err
+ return
+ } else {
+ team := result.Data.(*model.Team)
+ if !(team.Type == model.TEAM_OPEN) {
+ c.Err = model.NewLocAppError("getInviteInfo", "api.team.get_invite_info.not_open_team", nil, "id="+inviteId)
+ return
+ }
+
+ result := map[string]string{}
+ result["display_name"] = team.DisplayName
+ result["name"] = team.Name
+ result["id"] = team.Id
+ w.Write([]byte(model.MapToJson(result)))
+ }
+}
diff --git a/api/team_test.go b/api/team_test.go
index c942e2e1f..bbbc8385d 100644
--- a/api/team_test.go
+++ b/api/team_test.go
@@ -108,49 +108,36 @@ func TestCreateTeam(t *testing.T) {
}
}
-func TestFindTeamByEmail(t *testing.T) {
+func TestGetAllTeams(t *testing.T) {
Setup()
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
+ team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN, AllowTeamListing: true}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
- if r1, err := Client.FindTeams(user.Email); err != nil {
+ Client.LoginByEmail(team.Name, user.Email, "pwd")
+
+ enableIncomingHooks := *utils.Cfg.TeamSettings.EnableTeamListing
+ defer func() {
+ *utils.Cfg.TeamSettings.EnableTeamListing = enableIncomingHooks
+ }()
+ *utils.Cfg.TeamSettings.EnableTeamListing = true
+
+ if r1, err := Client.GetAllTeams(); err != nil {
t.Fatal(err)
} else {
teams := r1.Data.(map[string]*model.Team)
if teams[team.Id].Name != team.Name {
t.Fatal()
}
- if teams[team.Id].DisplayName != team.DisplayName {
- t.Fatal()
+ if teams[team.Id].Email != "" {
+ t.Fatal("Non admin users shoudn't get full listings")
}
}
- if _, err := Client.FindTeams("missing"); err != nil {
- t.Fatal(err)
- }
-}
-
-func TestGetAllTeams(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
-
- Client.LoginByEmail(team.Name, user.Email, "pwd")
-
- if _, err := Client.GetAllTeams(); err == nil {
- t.Fatal("you shouldn't have permissions")
- }
-
c := &Context{}
c.RequestId = model.NewId()
c.IpAddress = "cmd_line"
@@ -165,6 +152,9 @@ func TestGetAllTeams(t *testing.T) {
if teams[team.Id].Name != team.Name {
t.Fatal()
}
+ if teams[team.Id].Email != team.Email {
+ t.Fatal()
+ }
}
}
@@ -207,75 +197,6 @@ func TestTeamPermDelete(t *testing.T) {
Client.ClearOAuthToken()
}
-/*
-
-XXXXXX investigate and fix failing test
-
-func TestFindTeamByDomain(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
-
- if r1, err := Client.FindTeamByDomain(team.Name, false); err != nil {
- t.Fatal(err)
- } else {
- val := r1.Data.(bool)
- if !val {
- t.Fatal("should be a valid domain")
- }
- }
-
- if r1, err := Client.FindTeamByDomain(team.Name, true); err != nil {
- t.Fatal(err)
- } else {
- val := r1.Data.(bool)
- if !val {
- t.Fatal("should be a valid domain")
- }
- }
-
- if r1, err := Client.FindTeamByDomain("a"+model.NewId()+"a", false); err != nil {
- t.Fatal(err)
- } else {
- val := r1.Data.(bool)
- if val {
- t.Fatal("shouldn't be a valid domain")
- }
- }
-}
-
-*/
-
-func TestFindTeamByEmailSend(t *testing.T) {
- Setup()
-
- team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
- team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
-
- user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
- user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
- store.Must(Srv.Store.User().VerifyEmail(user.Id))
- Client.LoginByEmail(team.Name, user.Email, "pwd")
-
- if _, err := Client.FindTeamsSendEmail(user.Email); err != nil {
- t.Fatal(err)
- } else {
- }
-
- if _, err := Client.FindTeamsSendEmail("missing"); err != nil {
-
- // It should actually succeed at sending the email since it doesn't exist
- if !strings.Contains(err.DetailedError, "Failed to add to email address") {
- t.Fatal(err)
- }
- }
-}
-
func TestInviteMembers(t *testing.T) {
Setup()
diff --git a/api/templates/email_change_body.html b/api/templates/email_change_body.html
deleted file mode 100644
index 41b1bcd7d..000000000
--- a/api/templates/email_change_body.html
+++ /dev/null
@@ -1,41 +0,0 @@
-{{define "email_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;">{{.Props.Title}}</h2>
- <p>{{.Html.Info}}</p>
- </td>
- </tr>
- <tr>
- {{template "email_info" . }}
- </tr>
- </table>
- </td>
- </tr>
- <tr>
- {{template "email_footer" . }}
- </tr>
- </table>
- </td>
- </tr>
- </table>
- </td>
- </tr>
-</table>
-
-{{end}}
diff --git a/api/templates/email_change_subject.html b/api/templates/email_change_subject.html
deleted file mode 100644
index afabc2191..000000000
--- a/api/templates/email_change_subject.html
+++ /dev/null
@@ -1 +0,0 @@
-{{define "email_change_subject"}}[{{.ClientCfg.SiteName}}] {{.Props.Subject}}{{end}}
diff --git a/api/templates/email_change_verify_body.html b/api/templates/email_change_verify_body.html
deleted file mode 100644
index 0d0c0aaba..000000000
--- a/api/templates/email_change_verify_body.html
+++ /dev/null
@@ -1,44 +0,0 @@
-{{define "email_change_verify_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;">{{.Props.Title}}</h2>
- <p>{{.Props.Info}}</p>
- <p style="margin: 20px 0 15px">
- <a href="{{.Props.VerifyUrl}}" style="background: #2389D7; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 200px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">{{.Props.VerifyButton}}</a>
- </p>
- </td>
- </tr>
- <tr>
- {{template "email_info" . }}
- </tr>
- </table>
- </td>
- </tr>
- <tr>
- {{template "email_footer" . }}
- </tr>
- </table>
- </td>
- </tr>
- </table>
- </td>
- </tr>
-</table>
-
-{{end}}
diff --git a/api/templates/email_change_verify_subject.html b/api/templates/email_change_verify_subject.html
deleted file mode 100644
index 4fc4f4846..000000000
--- a/api/templates/email_change_verify_subject.html
+++ /dev/null
@@ -1 +0,0 @@
-{{define "email_change_verify_subject"}}[{{.ClientCfg.SiteName}}] {{.Props.Subject}}{{end}}
diff --git a/api/templates/email_footer.html b/api/templates/email_footer.html
deleted file mode 100644
index 6dc7fa483..000000000
--- a/api/templates/email_footer.html
+++ /dev/null
@@ -1,13 +0,0 @@
-{{define "email_footer"}}
-
-<td style="text-align: center;color: #AAA; font-size: 11px; padding-bottom: 10px;">
- <p style="margin: 25px 0;">
- <img width="65" src="{{.Props.SiteURL}}/static/images/circles.png" alt="">
- </p>
- <p style="padding: 0 50px;">
- (c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
- {{.Props.Footer}}
- </p>
-</td>
-
-{{end}}
diff --git a/api/templates/email_info.html b/api/templates/email_info.html
deleted file mode 100644
index 0a34f18a0..000000000
--- a/api/templates/email_info.html
+++ /dev/null
@@ -1,7 +0,0 @@
-{{define "email_info"}}
-
-<td style="color: #999; padding-top: 20px; line-height: 25px; font-size: 13px;">
- {{.Html.EmailInfo}}
-</td>
-
-{{end}}
diff --git a/api/templates/error.html b/api/templates/error.html
deleted file mode 100644
index 2f588aead..000000000
--- a/api/templates/error.html
+++ /dev/null
@@ -1,37 +0,0 @@
-<html>
-<head>
- <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
- <title><span class='fa fa-chevron-left'></span>Back - Error</title>
-
- <link rel="stylesheet" href="/static/css/bootstrap-3.3.5.min.css">
- <link rel="stylesheet" href="/static/css/jasny-bootstrap.min.css" rel="stylesheet">
-
- <script src="/static/js/react-with-addons-0.13.3.min.js"></script>
- <script src="/static/js/jquery-1.11.1.min.js"></script>
- <script src="/static/js/bootstrap-3.3.5.min.js"></script>
- <script src="/static/js/react-bootstrap-0.25.1.min.js"></script>
-
- <link id="favicon" rel="icon" href="/static/images/favicon/favicon-16x16.png" type="image/x-icon">
- <link rel="shortcut icon" href="/static/images/favicon/favicon-16x16.png" type="image/x-icon">
- <link href='/static/css/google-fonts.css' rel='stylesheet' type='text/css'>
- <link rel="stylesheet" href="/static/css/styles.css">
-
-
-</head>
-<body class="white error">
- <div class="container-fluid">
- <div class="error__container">
- <div class="error__icon"><i class="fa fa-exclamation-triangle"></i></div>
- <h2>{{.Props.Title}}</h2>
- <p>{{ .Props.Message }}</p>
- <a href="{{.Props.SiteURL}}">{{.Props.Link}}</a>
- </div>
- </div>
-</body>
-<script>
- var details = "{{ .Details }}";
- if (details.length > 0) {
- console.log("error details: " + details);
- }
-</script>
-</html>
diff --git a/api/templates/find_teams_body.html b/api/templates/find_teams_body.html
deleted file mode 100644
index 1324091aa..000000000
--- a/api/templates/find_teams_body.html
+++ /dev/null
@@ -1,52 +0,0 @@
-{{define "find_teams_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="{{.ClientCfg.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;">{{.Props.Title}}</h2>
- <p>{{ if .Extra }}
- {{.Props.Found}}<br>
- {{range $index, $element := .Extra}}
- <a href="{{ $element }}" style="text-decoration: none; color:#2389D7;">{{ $index }}</a><br>
- {{ end }}
- {{ else }}
- {{.Props.NotFound}}
- {{ end }}
- </p>
- </td>
- </tr>
- <tr>
- {{template "email_info" . }}
- </tr>
- </table>
- </td>
- </tr>
- <tr>
- {{template "email_footer" . }}
- </tr>
- </table>
- </td>
- </tr>
- </table>
- </td>
- </tr>
-</table>
-
-{{end}}
-
-
-
diff --git a/api/templates/find_teams_subject.html b/api/templates/find_teams_subject.html
deleted file mode 100644
index ebc339562..000000000
--- a/api/templates/find_teams_subject.html
+++ /dev/null
@@ -1 +0,0 @@
-{{define "find_teams_subject"}}{{.Props.Subject}}{{end}}
diff --git a/api/templates/invite_body.html b/api/templates/invite_body.html
deleted file mode 100644
index 2b6bde6d3..000000000
--- a/api/templates/invite_body.html
+++ /dev/null
@@ -1,46 +0,0 @@
-{{define "invite_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;">{{.Props.Title}}</h2>
- <p>{{.Html.Info}}</p>
- <p style="margin: 30px 0 15px">
- <a href="{{.Props.Link}}" style="background: #2389D7; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 200px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">{{.Props.Button}}</a>
- </p>
- <br/>
- <p>{{.Html.ExtraInfo}}</p>
- </td>
- </tr>
- <tr>
- {{template "email_info" . }}
- </tr>
- </table>
- </td>
- </tr>
- <tr>
- {{template "email_footer" . }}
- </tr>
- </table>
- </td>
- </tr>
- </table>
- </td>
- </tr>
-</table>
-
-{{end}}
diff --git a/api/templates/invite_subject.html b/api/templates/invite_subject.html
deleted file mode 100644
index 504915d50..000000000
--- a/api/templates/invite_subject.html
+++ /dev/null
@@ -1 +0,0 @@
-{{define "invite_subject"}}{{.Props.Subject}}{{end}}
diff --git a/api/templates/password_change_body.html b/api/templates/password_change_body.html
deleted file mode 100644
index 2c4ba10ca..000000000
--- a/api/templates/password_change_body.html
+++ /dev/null
@@ -1,43 +0,0 @@
-{{define "password_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;">{{.Props.Title}}</h2>
- <p>{{.Html.Info}}</p>
- </td>
- </tr>
- <tr>
- {{template "email_info" . }}
- </tr>
- </table>
- </td>
- </tr>
- <tr>
- {{template "email_footer" . }}
- </tr>
- </table>
- </td>
- </tr>
- </table>
- </td>
- </tr>
-</table>
-
-{{end}}
-
-
diff --git a/api/templates/password_change_subject.html b/api/templates/password_change_subject.html
deleted file mode 100644
index 897f1210d..000000000
--- a/api/templates/password_change_subject.html
+++ /dev/null
@@ -1 +0,0 @@
-{{define "password_change_subject"}}{{.Props.Subject}}{{end}}
diff --git a/api/templates/post_body.html b/api/templates/post_body.html
deleted file mode 100644
index 54f34d1dd..000000000
--- a/api/templates/post_body.html
+++ /dev/null
@@ -1,45 +0,0 @@
-{{define "post_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;">{{.Props.BodyText}}</h2>
- <p>{{.Html.Info}}<br><pre style="text-align:left;font-family: 'Lato', sans-serif; white-space: pre-wrap; white-space: -moz-pre-wrap; white-space: -pre-wrap; white-space: -o-pre-wrap; word-wrap: break-word;">{{.Props.PostMessage}}</pre></p>
- <p style="margin: 20px 0 15px">
- <a href="{{.Props.TeamLink}}" style="background: #2389D7; display: inline-block; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 170px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">{{.Props.Button}}</a>
- </p>
- </td>
- </tr>
- <tr>
- {{template "email_info" . }}
- </tr>
- </table>
- </td>
- </tr>
- <tr>
- {{template "email_footer" . }}
- </tr>
- </table>
- </td>
- </tr>
- </table>
- </td>
- </tr>
-</table>
-
-{{end}}
-
diff --git a/api/templates/post_subject.html b/api/templates/post_subject.html
deleted file mode 100644
index 60daaa432..000000000
--- a/api/templates/post_subject.html
+++ /dev/null
@@ -1 +0,0 @@
-{{define "post_subject"}}[{{.ClientCfg.SiteName}}] {{.Props.Subject}}{{end}}
diff --git a/api/templates/reset_body.html b/api/templates/reset_body.html
deleted file mode 100644
index 69cd44957..000000000
--- a/api/templates/reset_body.html
+++ /dev/null
@@ -1,46 +0,0 @@
-{{define "reset_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;">{{.Props.Title}}</h2>
- <p>{{.Html.Info}}</p>
- <p style="margin: 20px 0 15px">
- <a href="{{.Props.ResetUrl}}" style="background: #2389D7; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 200px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">{{.Props.Button}}</a>
- </p>
- </td>
- </tr>
- <tr>
- {{template "email_info" . }}
- </tr>
- </table>
- </td>
- </tr>
- <tr>
- {{template "email_footer" . }}
- </tr>
- </table>
- </td>
- </tr>
- </table>
- </td>
- </tr>
-</table>
-
-{{end}}
-
-
diff --git a/api/templates/reset_subject.html b/api/templates/reset_subject.html
deleted file mode 100644
index a2852d332..000000000
--- a/api/templates/reset_subject.html
+++ /dev/null
@@ -1 +0,0 @@
-{{define "reset_subject"}}{{.Props.Subject}}{{end}}
diff --git a/api/templates/signin_change_body.html b/api/templates/signin_change_body.html
deleted file mode 100644
index af8577f0f..000000000
--- a/api/templates/signin_change_body.html
+++ /dev/null
@@ -1,43 +0,0 @@
-{{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;">{{.Props.Title}}</h2>
- <p>{{.Html.Info}}</p>
- </td>
- </tr>
- <tr>
- {{template "email_info" . }}
- </tr>
- </table>
- </td>
- </tr>
- <tr>
- {{template "email_footer" . }}
- </tr>
- </table>
- </td>
- </tr>
- </table>
- </td>
- </tr>
-</table>
-
-{{end}}
-
-
diff --git a/api/templates/signin_change_subject.html b/api/templates/signin_change_subject.html
deleted file mode 100644
index 606dc4df3..000000000
--- a/api/templates/signin_change_subject.html
+++ /dev/null
@@ -1 +0,0 @@
-{{define "signin_change_subject"}}{{.Props.Subject}}{{end}}
diff --git a/api/templates/signup_team_body.html b/api/templates/signup_team_body.html
deleted file mode 100644
index 683a9891e..000000000
--- a/api/templates/signup_team_body.html
+++ /dev/null
@@ -1,44 +0,0 @@
-{{define "signup_team_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;">{{.Props.Title}}</h2>
- <p style="margin: 20px 0 25px">
- <a href="{{.Props.Link}}" style="background: #2389D7; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 200px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">{{.Props.Button}}</a>
- </p>
- {{.Html.Info}}<br></p>
- </td>
- </tr>
- <tr>
- {{template "email_info" . }}
- </tr>
- </table>
- </td>
- </tr>
- <tr>
- {{template "email_footer" . }}
- </tr>
- </table>
- </td>
- </tr>
- </table>
- </td>
- </tr>
-</table>
-
-{{end}}
diff --git a/api/templates/signup_team_subject.html b/api/templates/signup_team_subject.html
deleted file mode 100644
index 413a5c8c1..000000000
--- a/api/templates/signup_team_subject.html
+++ /dev/null
@@ -1 +0,0 @@
-{{define "signup_team_subject"}}{{.Props.Subject}}{{end}} \ No newline at end of file
diff --git a/api/templates/verify_body.html b/api/templates/verify_body.html
deleted file mode 100644
index 2b0d25f94..000000000
--- a/api/templates/verify_body.html
+++ /dev/null
@@ -1,44 +0,0 @@
-{{define "verify_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;">{{.Props.Title}}</h2>
- <p>{{.Props.Info}}</p>
- <p style="margin: 20px 0 15px">
- <a href="{{.Props.VerifyUrl}}" style="background: #2389D7; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 200px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">{{.Props.Button}}</a>
- </p>
- </td>
- </tr>
- <tr>
- {{template "email_info" . }}
- </tr>
- </table>
- </td>
- </tr>
- <tr>
- {{template "email_footer" . }}
- </tr>
- </table>
- </td>
- </tr>
- </table>
- </td>
- </tr>
-</table>
-
-{{end}}
diff --git a/api/templates/verify_subject.html b/api/templates/verify_subject.html
deleted file mode 100644
index ad7fc2aaa..000000000
--- a/api/templates/verify_subject.html
+++ /dev/null
@@ -1 +0,0 @@
-{{define "verify_subject"}}{{.Props.Subject}}{{end}}
diff --git a/api/templates/welcome_body.html b/api/templates/welcome_body.html
deleted file mode 100644
index b5ca9beb3..000000000
--- a/api/templates/welcome_body.html
+++ /dev/null
@@ -1,51 +0,0 @@
-{{define "welcome_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">
- {{if .Props.VerifyUrl }}
- <tr>
- <td style="border-bottom: 1px solid #ddd; padding: 0 0 20px;">
- <h2 style="font-weight: normal; margin-top: 10px;">{{.Props.Title}}</h2>
- <p>{{.Props.Info}}</p>
- <p style="margin: 20px 0 15px">
- <a href="{{.Props.VerifyUrl}}" style="background: #2389D7; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 200px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">{{.Props.Button}}</a>
- </p>
- </td>
- </tr>
- {{end}}
- <tr>
- <td style="border-bottom: 1px solid #ddd; padding: 0 0 20px;">
- <h2 style="font-weight: normal; margin-top: 25px; line-height: 1.5;">{{.Props.Info2}}</h2>
- <a href="{{.Props.TeamURL}}">{{.Props.TeamURL}}</a>
- <p>{{.Props.Info3}}</p>
- </td>
- </tr>
- </table>
- </td>
- </tr>
- <tr>
- {{template "email_footer" . }}
- </tr>
- </table>
- </td>
- </tr>
- </table>
- </td>
- </tr>
-</table>
-
-{{end}}
-
diff --git a/api/templates/welcome_subject.html b/api/templates/welcome_subject.html
deleted file mode 100644
index 95189b900..000000000
--- a/api/templates/welcome_subject.html
+++ /dev/null
@@ -1 +0,0 @@
-{{define "welcome_subject"}}{{.Props.Subject}}{{end}}
diff --git a/api/user.go b/api/user.go
index b7e6220d8..0841c38aa 100644
--- a/api/user.go
+++ b/api/user.go
@@ -53,10 +53,13 @@ func InitUser(r *mux.Router) {
sr.Handle("/attach_device", ApiUserRequired(attachDeviceId)).Methods("POST")
sr.Handle("/switch_to_sso", ApiAppHandler(switchToSSO)).Methods("POST")
sr.Handle("/switch_to_email", ApiUserRequired(switchToEmail)).Methods("POST")
+ sr.Handle("/verify_email", ApiAppHandler(verifyEmail)).Methods("POST")
+ sr.Handle("/resend_verification", ApiAppHandler(resendVerification)).Methods("POST")
sr.Handle("/newimage", ApiUserRequired(uploadProfileImage)).Methods("POST")
sr.Handle("/me", ApiAppHandler(getMe)).Methods("GET")
+ sr.Handle("/me_logged_in", ApiAppHandler(getMeLoggedIn)).Methods("GET")
sr.Handle("/status", ApiUserRequiredActivity(getStatuses, false)).Methods("POST")
sr.Handle("/profiles", ApiUserRequired(getProfiles)).Methods("GET")
sr.Handle("/profiles/{id:[A-Za-z0-9]+}", ApiUserRequired(getProfiles)).Methods("GET")
@@ -315,10 +318,10 @@ func CreateOAuthUser(c *Context, w http.ResponseWriter, r *http.Request, service
func sendWelcomeEmailAndForget(c *Context, userId, email, teamName, teamDisplayName, siteURL, teamURL string, verified bool) {
go func() {
- subjectPage := NewServerTemplatePage("welcome_subject", c.Locale)
+ subjectPage := utils.NewHTMLTemplate("welcome_subject", c.Locale)
subjectPage.Props["Subject"] = c.T("api.templates.welcome_subject", map[string]interface{}{"TeamDisplayName": teamDisplayName})
- bodyPage := NewServerTemplatePage("welcome_body", c.Locale)
+ bodyPage := utils.NewHTMLTemplate("welcome_body", c.Locale)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = c.T("api.templates.welcome_body.title", map[string]interface{}{"TeamDisplayName": teamDisplayName})
bodyPage.Props["Info"] = c.T("api.templates.welcome_body.info")
@@ -328,7 +331,7 @@ func sendWelcomeEmailAndForget(c *Context, userId, email, teamName, teamDisplayN
bodyPage.Props["TeamURL"] = teamURL
if !verified {
- link := fmt.Sprintf("%s/verify_email?uid=%s&hid=%s&teamname=%s&email=%s", siteURL, userId, model.HashPassword(userId), teamName, email)
+ link := fmt.Sprintf("%s/do_verify_email?uid=%s&hid=%s&teamname=%s&email=%s", siteURL, userId, model.HashPassword(userId), teamName, email)
bodyPage.Props["VerifyUrl"] = link
}
@@ -380,13 +383,13 @@ func addDirectChannelsAndForget(user *model.User) {
func SendVerifyEmailAndForget(c *Context, userId, userEmail, teamName, teamDisplayName, siteURL, teamURL string) {
go func() {
- link := fmt.Sprintf("%s/verify_email?uid=%s&hid=%s&teamname=%s&email=%s", siteURL, userId, model.HashPassword(userId), teamName, userEmail)
+ link := fmt.Sprintf("%s/do_verify_email?uid=%s&hid=%s&teamname=%s&email=%s", siteURL, userId, model.HashPassword(userId), teamName, userEmail)
- subjectPage := NewServerTemplatePage("verify_subject", c.Locale)
+ subjectPage := utils.NewHTMLTemplate("verify_subject", c.Locale)
subjectPage.Props["Subject"] = c.T("api.templates.verify_subject",
map[string]interface{}{"TeamDisplayName": teamDisplayName, "SiteName": utils.ClientCfg["SiteName"]})
- bodyPage := NewServerTemplatePage("verify_body", c.Locale)
+ bodyPage := utils.NewHTMLTemplate("verify_body", c.Locale)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = c.T("api.templates.verify_body.title", map[string]interface{}{"TeamDisplayName": teamDisplayName})
bodyPage.Props["Info"] = c.T("api.templates.verify_body.info")
@@ -621,31 +624,17 @@ func Login(c *Context, w http.ResponseWriter, r *http.Request, user *model.User,
w.Header().Set(model.HEADER_TOKEN, session.Token)
- tokens := GetMultiSessionCookieTokens(r)
- multiToken := ""
- seen := make(map[string]string)
- seen[session.TeamId] = session.TeamId
- for _, token := range tokens {
- s := GetSession(token)
- if s != nil && !s.IsExpired() && seen[s.TeamId] == "" {
- multiToken += " " + token
- seen[s.TeamId] = s.TeamId
- }
- }
-
- multiToken = strings.TrimSpace(multiToken + " " + session.Token)
expiresAt := time.Unix(model.GetMillis()/1000+int64(maxAge), 0)
-
- multiSessionCookie := &http.Cookie{
+ sessionCookie := &http.Cookie{
Name: model.SESSION_COOKIE_TOKEN,
- Value: multiToken,
+ Value: session.Token,
Path: "/",
MaxAge: maxAge,
Expires: expiresAt,
HttpOnly: true,
}
- http.SetCookie(w, multiSessionCookie)
+ http.SetCookie(w, sessionCookie)
c.Session = *session
c.LogAuditWithUserId(user.Id, "success")
@@ -902,6 +891,26 @@ func getMe(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
+func getMeLoggedIn(c *Context, w http.ResponseWriter, r *http.Request) {
+ data := make(map[string]string)
+ data["logged_in"] = "false"
+ data["team_name"] = ""
+
+ if len(c.Session.UserId) != 0 {
+ teamChan := Srv.Store.Team().Get(c.Session.TeamId)
+ var team *model.Team
+ if tr := <-teamChan; tr.Err != nil {
+ c.Err = tr.Err
+ return
+ } else {
+ team = tr.Data.(*model.Team)
+ }
+ data["logged_in"] = "true"
+ data["team_name"] = team.Name
+ }
+ w.Write([]byte(model.MapToJson(data)))
+}
+
func getUser(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
id := params["id"]
@@ -1622,12 +1631,12 @@ func sendPasswordReset(c *Context, w http.ResponseWriter, r *http.Request) {
data := model.MapToJson(newProps)
hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.PasswordResetSalt))
- link := fmt.Sprintf("%s/reset_password?d=%s&h=%s", c.GetTeamURLFromTeam(team), url.QueryEscape(data), url.QueryEscape(hash))
+ link := fmt.Sprintf("%s/reset_password_complete?d=%s&h=%s", c.GetTeamURLFromTeam(team), url.QueryEscape(data), url.QueryEscape(hash))
- subjectPage := NewServerTemplatePage("reset_subject", c.Locale)
+ subjectPage := utils.NewHTMLTemplate("reset_subject", c.Locale)
subjectPage.Props["Subject"] = c.T("api.templates.reset_subject")
- bodyPage := NewServerTemplatePage("reset_body", c.Locale)
+ bodyPage := utils.NewHTMLTemplate("reset_body", c.Locale)
bodyPage.Props["SiteURL"] = c.GetSiteURL()
bodyPage.Props["Title"] = c.T("api.templates.reset_body.title")
bodyPage.Html["Info"] = template.HTML(c.T("api.templates.reset_body.info"))
@@ -1743,11 +1752,11 @@ func resetPassword(c *Context, w http.ResponseWriter, r *http.Request) {
func sendPasswordChangeEmailAndForget(c *Context, email, teamDisplayName, teamURL, siteURL, method string) {
go func() {
- subjectPage := NewServerTemplatePage("password_change_subject", c.Locale)
+ subjectPage := utils.NewHTMLTemplate("password_change_subject", c.Locale)
subjectPage.Props["Subject"] = c.T("api.templates.password_change_subject",
map[string]interface{}{"TeamDisplayName": teamDisplayName, "SiteName": utils.ClientCfg["SiteName"]})
- bodyPage := NewServerTemplatePage("password_change_body", c.Locale)
+ bodyPage := utils.NewHTMLTemplate("password_change_body", c.Locale)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = c.T("api.templates.password_change_body.title")
bodyPage.Html["Info"] = template.HTML(c.T("api.templates.password_change_body.info",
@@ -1763,11 +1772,12 @@ func sendPasswordChangeEmailAndForget(c *Context, email, teamDisplayName, teamUR
func sendEmailChangeEmailAndForget(c *Context, oldEmail, newEmail, teamDisplayName, teamURL, siteURL string) {
go func() {
- subjectPage := NewServerTemplatePage("email_change_subject", c.Locale)
+ subjectPage := utils.NewHTMLTemplate("email_change_subject", c.Locale)
subjectPage.Props["Subject"] = c.T("api.templates.email_change_subject",
map[string]interface{}{"TeamDisplayName": teamDisplayName})
+ subjectPage.Props["SiteName"] = utils.Cfg.TeamSettings.SiteName
- bodyPage := NewServerTemplatePage("email_change_body", c.Locale)
+ bodyPage := utils.NewHTMLTemplate("email_change_body", c.Locale)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = c.T("api.templates.email_change_body.title")
bodyPage.Html["Info"] = template.HTML(c.T("api.templates.email_change_body.info",
@@ -1785,11 +1795,12 @@ func SendEmailChangeVerifyEmailAndForget(c *Context, userId, newUserEmail, teamN
link := fmt.Sprintf("%s/verify_email?uid=%s&hid=%s&teamname=%s&email=%s", siteURL, userId, model.HashPassword(userId), teamName, newUserEmail)
- subjectPage := NewServerTemplatePage("email_change_verify_subject", c.Locale)
+ subjectPage := utils.NewHTMLTemplate("email_change_verify_subject", c.Locale)
subjectPage.Props["Subject"] = c.T("api.templates.email_change_verify_subject",
map[string]interface{}{"TeamDisplayName": teamDisplayName})
+ subjectPage.Props["SiteName"] = utils.Cfg.TeamSettings.SiteName
- bodyPage := NewServerTemplatePage("email_change_verify_body", c.Locale)
+ bodyPage := utils.NewHTMLTemplate("email_change_verify_body", c.Locale)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = c.T("api.templates.email_change_verify_body.title")
bodyPage.Props["Info"] = c.T("api.templates.email_change_verify_body.info",
@@ -1918,7 +1929,7 @@ func GetAuthorizationCode(c *Context, service, teamName string, props map[string
props["team"] = teamName
state := b64.StdEncoding.EncodeToString([]byte(model.MapToJson(props)))
- redirectUri := c.GetSiteURL() + "/signup/" + service + "/complete" // Remove /signup after a few releases (~1.8)
+ redirectUri := c.GetSiteURL() + "/api/v1/oauth/" + service + "/complete"
authUrl := endpoint + "?response_type=code&client_id=" + clientId + "&redirect_uri=" + url.QueryEscape(redirectUri) + "&state=" + url.QueryEscape(state)
@@ -2216,11 +2227,11 @@ func switchToEmail(c *Context, w http.ResponseWriter, r *http.Request) {
func sendSignInChangeEmailAndForget(c *Context, email, teamDisplayName, teamURL, siteURL, method string) {
go func() {
- subjectPage := NewServerTemplatePage("signin_change_subject", c.Locale)
+ subjectPage := utils.NewHTMLTemplate("signin_change_subject", c.Locale)
subjectPage.Props["Subject"] = c.T("api.templates.singin_change_email.subject",
map[string]interface{}{"TeamDisplayName": teamDisplayName, "SiteName": utils.ClientCfg["SiteName"]})
- bodyPage := NewServerTemplatePage("signin_change_body", c.Locale)
+ bodyPage := utils.NewHTMLTemplate("signin_change_body", c.Locale)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = c.T("api.templates.signin_change_email.body.title")
bodyPage.Html["Info"] = template.HTML(c.T("api.templates.singin_change_email.body.info",
@@ -2232,3 +2243,68 @@ func sendSignInChangeEmailAndForget(c *Context, email, teamDisplayName, teamURL,
}()
}
+
+func verifyEmail(c *Context, w http.ResponseWriter, r *http.Request) {
+ props := model.MapFromJson(r.Body)
+
+ userId := props["uid"]
+ if len(userId) != 26 {
+ c.SetInvalidParam("verifyEmail", "uid")
+ return
+ }
+
+ hashedId := props["hid"]
+ if len(hashedId) == 0 {
+ c.SetInvalidParam("verifyEmail", "hid")
+ return
+ }
+
+ if model.ComparePassword(hashedId, userId) {
+ if c.Err = (<-Srv.Store.User().VerifyEmail(userId)).Err; c.Err != nil {
+ return
+ } else {
+ c.LogAudit("Email Verified")
+ return
+ }
+ }
+
+ c.Err = model.NewLocAppError("verifyEmail", "api.user.verify_email.bad_link.app_error", nil, "")
+ c.Err.StatusCode = http.StatusForbidden
+}
+
+func resendVerification(c *Context, w http.ResponseWriter, r *http.Request) {
+ props := model.MapFromJson(r.Body)
+
+ teamName := props["team_name"]
+ if len(teamName) == 0 {
+ c.SetInvalidParam("resendVerification", "team_name")
+ return
+ }
+
+ email := props["email"]
+ if len(email) == 0 {
+ c.SetInvalidParam("resendVerification", "email")
+ return
+ }
+
+ var team *model.Team
+ if result := <-Srv.Store.Team().GetByName(teamName); result.Err != nil {
+ c.Err = result.Err
+ return
+ } else {
+ team = result.Data.(*model.Team)
+ }
+
+ if result := <-Srv.Store.User().GetByEmail(team.Id, email); result.Err != nil {
+ c.Err = result.Err
+ return
+ } else {
+ user := result.Data.(*model.User)
+
+ if user.LastActivityAt > 0 {
+ SendEmailChangeVerifyEmailAndForget(c, user.Id, user.Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team))
+ } else {
+ SendVerifyEmailAndForget(c, user.Id, user.Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team))
+ }
+ }
+}
diff --git a/api/user_test.go b/api/user_test.go
index 1a1cf9634..27f00829f 100644
--- a/api/user_test.go
+++ b/api/user_test.go
@@ -1263,3 +1263,38 @@ func TestSwitchToEmail(t *testing.T) {
t.Fatal("should have failed - wrong user")
}
}
+
+func TestMeLoggedIn(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()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
+ ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User)
+ store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
+
+ Client.AuthToken = "invalid"
+
+ if result, err := Client.GetMeLoggedIn(); err != nil {
+ t.Fatal(err)
+ } else {
+ meLoggedIn := result.Data.(map[string]string)
+
+ if val, ok := meLoggedIn["logged_in"]; !ok || val != "false" {
+ t.Fatal("Got: " + val)
+ }
+ }
+
+ Client.LoginByEmail(team.Name, user.Email, user.Password)
+
+ if result, err := Client.GetMeLoggedIn(); err != nil {
+ t.Fatal(err)
+ } else {
+ meLoggedIn := result.Data.(map[string]string)
+
+ if val, ok := meLoggedIn["logged_in"]; !ok || val != "true" {
+ t.Fatal("Got: " + val)
+ }
+ }
+}