diff options
Diffstat (limited to 'web/web.go')
-rw-r--r-- | web/web.go | 430 |
1 files changed, 430 insertions, 0 deletions
diff --git a/web/web.go b/web/web.go new file mode 100644 index 000000000..3210ede1e --- /dev/null +++ b/web/web.go @@ -0,0 +1,430 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +package web + +import ( + l4g "code.google.com/p/log4go" + "fmt" + "github.com/gorilla/mux" + "github.com/mattermost/platform/api" + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/utils" + "github.com/mssola/user_agent" + "gopkg.in/fsnotify.v1" + "html/template" + "net/http" + "strconv" + "strings" +) + +var Templates *template.Template + +type HtmlTemplatePage api.Page + +func NewHtmlTemplatePage(templateName string, title string) *HtmlTemplatePage { + + if len(title) > 0 { + title = utils.Cfg.ServiceSettings.SiteName + " - " + title + } + + props := make(map[string]string) + props["AnalyticsUrl"] = utils.Cfg.ServiceSettings.AnalyticsUrl + return &HtmlTemplatePage{TemplateName: templateName, Title: title, SiteName: utils.Cfg.ServiceSettings.SiteName, Props: props} +} + +func (me *HtmlTemplatePage) Render(c *api.Context, w http.ResponseWriter) { + if err := Templates.ExecuteTemplate(w, me.TemplateName, me); err != nil { + c.SetUnknownError(me.TemplateName, err.Error()) + } +} + +func InitWeb() { + l4g.Debug("Initializing web routes") + + staticDir := utils.FindDir("web/static") + l4g.Debug("Using static directory at %v", staticDir) + api.Srv.Router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", + http.FileServer(http.Dir(staticDir)))) + + api.Srv.Router.Handle("/", api.AppHandler(root)).Methods("GET") + api.Srv.Router.Handle("/login", api.AppHandler(login)).Methods("GET") + api.Srv.Router.Handle("/signup_team_confirm/", api.AppHandler(signupTeamConfirm)).Methods("GET") + api.Srv.Router.Handle("/signup_team_complete/", api.AppHandler(signupTeamComplete)).Methods("GET") + api.Srv.Router.Handle("/signup_user_complete/", api.AppHandler(signupUserComplete)).Methods("GET") + + api.Srv.Router.Handle("/logout", api.AppHandler(logout)).Methods("GET") + + api.Srv.Router.Handle("/verify", api.AppHandler(verifyEmail)).Methods("GET") + api.Srv.Router.Handle("/find_team", api.AppHandler(findTeam)).Methods("GET") + api.Srv.Router.Handle("/reset_password", api.AppHandler(resetPassword)).Methods("GET") + + csr := api.Srv.Router.PathPrefix("/channels").Subrouter() + csr.Handle("/{name:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}", api.UserRequired(getChannel)).Methods("GET") + + watchAndParseTemplates() +} + +func watchAndParseTemplates() { + + templatesDir := utils.FindDir("web/templates") + l4g.Debug("Parsing templates at %v", templatesDir) + var err error + if Templates, err = template.ParseGlob(templatesDir + "*.html"); err != nil { + l4g.Error("Failed to parse templates %v", err) + } + + watcher, err := fsnotify.NewWatcher() + if err != nil { + l4g.Error("Failed to create directory watcher %v", err) + } + + go func() { + for { + select { + case event := <-watcher.Events: + if event.Op&fsnotify.Write == fsnotify.Write { + l4g.Info("Re-parsing templates because of modified file %v", event.Name) + if Templates, err = template.ParseGlob(templatesDir + "*.html"); err != nil { + l4g.Error("Failed to parse templates %v", err) + } + } + case err := <-watcher.Errors: + l4g.Error("Failed in directory watcher %v", err) + } + } + }() + + err = watcher.Add(templatesDir) + if err != nil { + l4g.Error("Failed to add directory to watcher %v", err) + } +} + +var browsersNotSupported string = "MSIE/8;MSIE/9;Internet Explorer/8;Internet Explorer/9" + +func CheckBrowserCompatability(c *api.Context, r *http.Request) bool { + ua := user_agent.New(r.UserAgent()) + bname, bversion := ua.Browser() + + browsers := strings.Split(browsersNotSupported, ";") + for _, browser := range browsers { + version := strings.Split(browser, "/") + + if strings.HasPrefix(bname, version[0]) && strings.HasPrefix(bversion, version[1]) { + c.Err = model.NewAppError("CheckBrowserCompatability", "Your current browser is not supported, please upgrade to one of the following browsers: Google Chrome 21 or higher, Internet Explorer 10 or higher, FireFox 14 or higher", "") + return false + } + } + + return true + +} + +func root(c *api.Context, w http.ResponseWriter, r *http.Request) { + + if !CheckBrowserCompatability(c, r) { + return + } + + if len(c.Session.UserId) == 0 { + if api.IsTestDomain(r) || strings.Index(r.Host, "www") == 0 || strings.Index(r.Host, "beta") == 0 || strings.Index(r.Host, "ci") == 0 { + page := NewHtmlTemplatePage("signup_team", "Signup") + page.Render(c, w) + } else { + login(c, w, r) + } + } else { + page := NewHtmlTemplatePage("home", "Home") + page.Render(c, w) + } +} + +func login(c *api.Context, w http.ResponseWriter, r *http.Request) { + if !CheckBrowserCompatability(c, r) { + return + } + + teamName := "Beta" + teamDomain := "" + siteDomain := "." + utils.Cfg.ServiceSettings.Domain + + if utils.Cfg.ServiceSettings.Mode == utils.MODE_DEV { + teamDomain = "developer" + } else if utils.Cfg.ServiceSettings.Mode == utils.MODE_BETA { + teamDomain = "beta" + } else { + teamDomain, siteDomain = model.GetSubDomain(c.TeamUrl) + siteDomain = "." + siteDomain + ".com" + + if tResult := <-api.Srv.Store.Team().GetByDomain(teamDomain); tResult.Err != nil { + l4g.Error("Couldn't find team teamDomain=%v, siteDomain=%v, teamUrl=%v, err=%v", teamDomain, siteDomain, c.TeamUrl, tResult.Err.Message) + } else { + teamName = tResult.Data.(*model.Team).Name + } + } + + page := NewHtmlTemplatePage("login", "Login") + page.Props["TeamName"] = teamName + page.Props["TeamDomain"] = teamDomain + page.Props["SiteDomain"] = siteDomain + page.Render(c, w) +} + +func signupTeamConfirm(c *api.Context, w http.ResponseWriter, r *http.Request) { + email := r.FormValue("email") + + page := NewHtmlTemplatePage("signup_team_confirm", "Signup Email Sent") + page.Props["Email"] = email + page.Render(c, w) +} + +func signupTeamComplete(c *api.Context, w http.ResponseWriter, r *http.Request) { + data := r.FormValue("d") + hash := r.FormValue("h") + + if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.ServiceSettings.InviteSalt)) { + c.Err = model.NewAppError("signupTeamComplete", "The signup link does not appear to be valid", "") + return + } + + props := model.MapFromJson(strings.NewReader(data)) + + t, err := strconv.ParseInt(props["time"], 10, 64) + if err != nil || model.GetMillis()-t > 1000*60*60 { // one hour + c.Err = model.NewAppError("signupTeamComplete", "The signup link has expired", "") + return + } + + page := NewHtmlTemplatePage("signup_team_complete", "Complete Team Sign Up") + page.Props["Email"] = props["email"] + page.Props["Name"] = props["name"] + page.Props["Data"] = data + page.Props["Hash"] = hash + page.Render(c, w) +} + +func signupUserComplete(c *api.Context, w http.ResponseWriter, r *http.Request) { + + id := r.FormValue("id") + data := r.FormValue("d") + hash := r.FormValue("h") + var props map[string]string + + if len(id) > 0 { + props = make(map[string]string) + + if result := <-api.Srv.Store.Team().Get(id); result.Err != nil { + c.Err = result.Err + return + } else { + team := result.Data.(*model.Team) + if !(team.Type == model.TEAM_OPEN || (team.Type == model.TEAM_INVITE && len(team.AllowedDomains) > 0)) { + c.Err = model.NewAppError("signupUserComplete", "The team type doesn't allow open invites", "id="+id) + return + } + + props["email"] = "" + props["name"] = team.Name + props["domain"] = team.Domain + props["id"] = team.Id + data = model.MapToJson(props) + hash = "" + } + } else { + + if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.ServiceSettings.InviteSalt)) { + c.Err = model.NewAppError("signupTeamComplete", "The signup link does not appear to be valid", "") + return + } + + props = model.MapFromJson(strings.NewReader(data)) + + t, err := strconv.ParseInt(props["time"], 10, 64) + if err != nil || model.GetMillis()-t > 1000*60*60*48 { // 48 hour + c.Err = model.NewAppError("signupTeamComplete", "The signup link has expired", "") + return + } + } + + page := NewHtmlTemplatePage("signup_user_complete", "Complete User Sign Up") + page.Props["Email"] = props["email"] + page.Props["TeamName"] = props["name"] + page.Props["TeamDomain"] = props["domain"] + page.Props["TeamId"] = props["id"] + page.Props["Data"] = data + page.Props["Hash"] = hash + page.Render(c, w) +} + +func logout(c *api.Context, w http.ResponseWriter, r *http.Request) { + api.Logout(c, w, r) + http.Redirect(w, r, "/", http.StatusFound) +} + +func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) { + params := mux.Vars(r) + name := params["name"] + + var channelId string + if result := <-api.Srv.Store.Channel().CheckPermissionsToByName(c.Session.TeamId, name, c.Session.UserId); result.Err != nil { + c.Err = result.Err + return + } else { + channelId = result.Data.(string) + } + + if len(channelId) == 0 { + if strings.Index(name, "__") > 0 { + // It's a direct message channel that doesn't exist yet so let's create it + ids := strings.Split(name, "__") + otherUserId := "" + if ids[0] == c.Session.UserId { + otherUserId = ids[1] + } else { + otherUserId = ids[0] + } + + if sc, err := api.CreateDirectChannel(c, otherUserId, r.URL.Path); err != nil { + api.Handle404(w, r) + return + } else { + channelId = sc.Id + } + } else { + + // lets make sure the user is valid + if result := <-api.Srv.Store.User().Get(c.Session.UserId); result.Err != nil { + c.Err = result.Err + c.RemoveSessionCookie(w) + l4g.Error("Error in getting users profile for id=%v forcing logout", c.Session.UserId) + return + } + + api.Handle404(w, r) + return + } + } + + var team *model.Team + + if tResult := <-api.Srv.Store.Team().Get(c.Session.TeamId); tResult.Err != nil { + c.Err = tResult.Err + return + } else { + team = tResult.Data.(*model.Team) + } + + page := NewHtmlTemplatePage("channel", "") + page.Title = name + " - " + team.Name + " " + page.SiteName + page.Props["TeamName"] = team.Name + page.Props["TeamType"] = team.Type + page.Props["ChannelName"] = name + page.Props["ChannelId"] = channelId + page.Props["UserId"] = c.Session.UserId + page.Render(c, w) +} + +func verifyEmail(c *api.Context, w http.ResponseWriter, r *http.Request) { + resend := r.URL.Query().Get("resend") + domain := r.URL.Query().Get("domain") + email := r.URL.Query().Get("email") + hashedId := r.URL.Query().Get("hid") + userId := r.URL.Query().Get("uid") + + if resend == "true" { + + teamId := "" + if result := <-api.Srv.Store.Team().GetByDomain(domain); result.Err != nil { + c.Err = result.Err + return + } else { + teamId = result.Data.(*model.Team).Id + } + + if result := <-api.Srv.Store.User().GetByEmail(teamId, email); result.Err != nil { + c.Err = result.Err + return + } else { + user := result.Data.(*model.User) + api.FireAndForgetVerifyEmail(user.Id, strings.Split(user.FullName, " ")[0], user.Email, domain, c.TeamUrl) + http.Redirect(w, r, "/", http.StatusFound) + return + } + } + + var isVerified string + if len(userId) != 26 { + isVerified = "false" + } else if len(hashedId) == 0 { + isVerified = "false" + } else if model.ComparePassword(hashedId, userId) { + isVerified = "true" + if c.Err = (<-api.Srv.Store.User().VerifyEmail(userId)).Err; c.Err != nil { + return + } else { + c.LogAudit("") + } + } else { + isVerified = "false" + } + + page := NewHtmlTemplatePage("verify", "Email Verified") + page.Props["IsVerified"] = isVerified + page.Render(c, w) +} + +func findTeam(c *api.Context, w http.ResponseWriter, r *http.Request) { + page := NewHtmlTemplatePage("find_team", "Find Team") + page.Render(c, w) +} + +func resetPassword(c *api.Context, w http.ResponseWriter, r *http.Request) { + isResetLink := true + hash := r.URL.Query().Get("h") + data := r.URL.Query().Get("d") + + if len(hash) == 0 || len(data) == 0 { + isResetLink = false + } else { + if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.ServiceSettings.ResetSalt)) { + c.Err = model.NewAppError("resetPassword", "The reset link does not appear to be valid", "") + return + } + + props := model.MapFromJson(strings.NewReader(data)) + + t, err := strconv.ParseInt(props["time"], 10, 64) + if err != nil || model.GetMillis()-t > 1000*60*60 { // one hour + c.Err = model.NewAppError("resetPassword", "The signup link has expired", "") + return + } + } + + teamName := "Developer/Beta" + domain := "" + if utils.Cfg.ServiceSettings.Mode != utils.MODE_DEV { + domain, _ = model.GetSubDomain(c.TeamUrl) + + var team *model.Team + if tResult := <-api.Srv.Store.Team().GetByDomain(domain); tResult.Err != nil { + c.Err = tResult.Err + return + } else { + team = tResult.Data.(*model.Team) + } + + if team != nil { + teamName = team.Name + } + } + + page := NewHtmlTemplatePage("password_reset", "") + page.Title = "Reset Password - " + page.SiteName + page.Props["TeamName"] = teamName + page.Props["Hash"] = hash + page.Props["Data"] = data + page.Props["Domain"] = domain + page.Props["IsReset"] = strconv.FormatBool(isResetLink) + page.Render(c, w) +} |