summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoramWilander <jwawilander@gmail.com>2015-08-28 08:37:55 -0400
committerJoramWilander <jwawilander@gmail.com>2015-08-28 08:37:55 -0400
commitf5fec3a157e6c9146a0c4e28dd5f70e6c066affd (patch)
tree176afd630a3afbe0ac3389695be6b4ce1c45d069
parentdb7e8c12889485234fb2d1ba6556106e5fc7548b (diff)
downloadchat-f5fec3a157e6c9146a0c4e28dd5f70e6c066affd.tar.gz
chat-f5fec3a157e6c9146a0c4e28dd5f70e6c066affd.tar.bz2
chat-f5fec3a157e6c9146a0c4e28dd5f70e6c066affd.zip
Added the ability to create a team with SSO services and added the ability to turn off email sign up.
-rw-r--r--api/team.go74
-rw-r--r--api/user.go22
-rw-r--r--config/config.json3
-rw-r--r--docker/dev/config_docker.json13
-rw-r--r--docker/local/config_docker.json13
-rw-r--r--model/gitlab.go2
-rw-r--r--model/google.go6
-rw-r--r--model/team.go59
-rw-r--r--model/team_test.go59
-rw-r--r--model/user.go60
-rw-r--r--model/user_test.go42
-rw-r--r--model/utils.go25
-rw-r--r--model/utils_test.go44
-rw-r--r--utils/config.go19
-rw-r--r--web/react/components/login.jsx80
-rw-r--r--web/react/components/signup_team.jsx106
-rw-r--r--web/react/components/signup_user_complete.jsx51
-rw-r--r--web/react/components/signup_user_oauth.jsx87
-rw-r--r--web/react/components/team_signup_choose_auth.jsx88
-rw-r--r--web/react/components/team_signup_with_email.jsx82
-rw-r--r--web/react/components/team_signup_with_sso.jsx137
-rw-r--r--web/react/pages/signup_team.jsx6
-rw-r--r--web/react/pages/signup_user_oauth.jsx11
-rw-r--r--web/react/utils/client.jsx15
-rw-r--r--web/react/utils/constants.jsx1
-rw-r--r--web/sass-files/sass/partials/_responsive.scss8
-rw-r--r--web/sass-files/sass/partials/_signup.scss42
-rw-r--r--web/templates/signup_team.html2
-rw-r--r--web/templates/signup_user_oauth.html26
-rw-r--r--web/web.go45
30 files changed, 883 insertions, 345 deletions
diff --git a/api/team.go b/api/team.go
index eaa0d2695..d123b7dfa 100644
--- a/api/team.go
+++ b/api/team.go
@@ -23,6 +23,7 @@ func InitTeam(r *mux.Router) {
sr := r.PathPrefix("/teams").Subrouter()
sr.Handle("/create", ApiAppHandler(createTeam)).Methods("POST")
sr.Handle("/create_from_signup", ApiAppHandler(createTeamFromSignup)).Methods("POST")
+ sr.Handle("/create_with_sso/{service:[A-Za-z]+}", ApiAppHandler(createTeamFromSSO)).Methods("POST")
sr.Handle("/signup", ApiAppHandler(signupTeam)).Methods("POST")
sr.Handle("/find_team_by_name", ApiAppHandler(findTeamByName)).Methods("POST")
sr.Handle("/find_teams", ApiAppHandler(findTeams)).Methods("POST")
@@ -35,6 +36,11 @@ func InitTeam(r *mux.Router) {
}
func signupTeam(c *Context, w http.ResponseWriter, r *http.Request) {
+ if !utils.Cfg.ServiceSettings.AllowEmailSignUp {
+ c.Err = model.NewAppError("signupTeam", "Team sign-up with email is disabled.", "")
+ c.Err.StatusCode = http.StatusNotImplemented
+ return
+ }
m := model.MapFromJson(r.Body)
email := strings.ToLower(strings.TrimSpace(m["email"]))
@@ -74,7 +80,70 @@ func signupTeam(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte(model.MapToJson(m)))
}
+func createTeamFromSSO(c *Context, w http.ResponseWriter, r *http.Request) {
+ params := mux.Vars(r)
+ service := params["service"]
+
+ if !utils.IsServiceAllowed(service) {
+ c.SetInvalidParam("createTeamFromSSO", "service")
+ return
+ }
+
+ team := model.TeamFromJson(r.Body)
+
+ if team == nil {
+ c.SetInvalidParam("createTeamFromSSO", "team")
+ return
+ }
+
+ team.PreSave()
+
+ team.Name = model.CleanTeamName(team.Name)
+
+ if err := team.IsValid(); err != nil {
+ c.Err = err
+ return
+ }
+
+ team.Id = ""
+
+ found := true
+ count := 0
+ for found {
+ if found = FindTeamByName(c, team.Name, "true"); c.Err != nil {
+ return
+ } else if found {
+ team.Name = team.Name + strconv.Itoa(count)
+ count += 1
+ }
+ }
+
+ team.AllowValet = utils.Cfg.TeamSettings.AllowValetDefault
+
+ if result := <-Srv.Store.Team().Save(team); result.Err != nil {
+ c.Err = result.Err
+ return
+ } else {
+ rteam := result.Data.(*model.Team)
+
+ if _, err := CreateDefaultChannels(c, rteam.Id); err != nil {
+ c.Err = nil
+ return
+ }
+
+ data := map[string]string{"follow_link": c.GetSiteURL() + "/" + rteam.Name + "/signup/" + service}
+ w.Write([]byte(model.MapToJson(data)))
+
+ }
+
+}
+
func createTeamFromSignup(c *Context, w http.ResponseWriter, r *http.Request) {
+ if !utils.Cfg.ServiceSettings.AllowEmailSignUp {
+ c.Err = model.NewAppError("createTeamFromSignup", "Team sign-up with email is disabled.", "")
+ c.Err.StatusCode = http.StatusNotImplemented
+ return
+ }
teamSignup := model.TeamSignupFromJson(r.Body)
@@ -170,6 +239,11 @@ func createTeamFromSignup(c *Context, w http.ResponseWriter, r *http.Request) {
}
func createTeam(c *Context, w http.ResponseWriter, r *http.Request) {
+ if !utils.Cfg.ServiceSettings.AllowEmailSignUp {
+ c.Err = model.NewAppError("createTeam", "Team sign-up with email is disabled.", "")
+ c.Err.StatusCode = http.StatusNotImplemented
+ return
+ }
team := model.TeamFromJson(r.Body)
diff --git a/api/user.go b/api/user.go
index 05ccd03e8..3796dde2a 100644
--- a/api/user.go
+++ b/api/user.go
@@ -58,6 +58,11 @@ func InitUser(r *mux.Router) {
}
func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
+ if !utils.Cfg.ServiceSettings.AllowEmailSignUp {
+ c.Err = model.NewAppError("signupTeam", "User sign-up with email is disabled.", "")
+ c.Err.StatusCode = http.StatusNotImplemented
+ return
+ }
user := model.UserFromJson(r.Body)
@@ -181,7 +186,7 @@ func CreateUser(c *Context, team *model.Team, user *model.User) *model.User {
if result := <-Srv.Store.User().Save(user); result.Err != nil {
c.Err = result.Err
- l4g.Error("Filae err=%v", result.Err)
+ l4g.Error("Couldn't save the user err=%v", result.Err)
return nil
} else {
ruser := result.Data.(*model.User)
@@ -1426,3 +1431,18 @@ func AuthorizeOAuthUser(service, code, state, redirectUri string) (io.ReadCloser
}
}
+
+func IsUsernameTaken(name string, teamId string) bool {
+
+ if !model.IsValidUsername(name) {
+ return false
+ }
+
+ if result := <-Srv.Store.User().GetByUsername(teamId, name); result.Err != nil {
+ return false
+ } else {
+ return true
+ }
+
+ return false
+}
diff --git a/config/config.json b/config/config.json
index 6c915e290..e0f3232ed 100644
--- a/config/config.json
+++ b/config/config.json
@@ -22,7 +22,8 @@
"AnalyticsUrl": "",
"UseLocalStorage": true,
"StorageDirectory": "./data/",
- "AllowedLoginAttempts": 10
+ "AllowedLoginAttempts": 10,
+ "AllowEmailSignUp": true
},
"SSOSettings": {
"gitlab": {
diff --git a/docker/dev/config_docker.json b/docker/dev/config_docker.json
index 0fa51cfd4..f566cea61 100644
--- a/docker/dev/config_docker.json
+++ b/docker/dev/config_docker.json
@@ -22,16 +22,27 @@
"AnalyticsUrl": "",
"UseLocalStorage": true,
"StorageDirectory": "/mattermost/data/",
- "AllowedLoginAttempts": 10
+ "AllowedLoginAttempts": 10,
+ "AllowEmailSignUp": true
},
"SSOSettings": {
"gitlab": {
"Allow": false,
"Secret" : "",
"Id": "",
+ "Scope": "",
"AuthEndpoint": "",
"TokenEndpoint": "",
"UserApiEndpoint": ""
+ },
+ "google": {
+ "Allow": false,
+ "Secret": "",
+ "Id": "",
+ "Scope": "email profile",
+ "AuthEndpoint": "https://accounts.google.com/o/oauth2/auth",
+ "TokenEndpoint": "https://www.googleapis.com/oauth2/v3/token",
+ "UserApiEndpoint": "https://www.googleapis.com/plus/v1/people/me"
}
},
"SqlSettings": {
diff --git a/docker/local/config_docker.json b/docker/local/config_docker.json
index 0fa51cfd4..f566cea61 100644
--- a/docker/local/config_docker.json
+++ b/docker/local/config_docker.json
@@ -22,16 +22,27 @@
"AnalyticsUrl": "",
"UseLocalStorage": true,
"StorageDirectory": "/mattermost/data/",
- "AllowedLoginAttempts": 10
+ "AllowedLoginAttempts": 10,
+ "AllowEmailSignUp": true
},
"SSOSettings": {
"gitlab": {
"Allow": false,
"Secret" : "",
"Id": "",
+ "Scope": "",
"AuthEndpoint": "",
"TokenEndpoint": "",
"UserApiEndpoint": ""
+ },
+ "google": {
+ "Allow": false,
+ "Secret": "",
+ "Id": "",
+ "Scope": "email profile",
+ "AuthEndpoint": "https://accounts.google.com/o/oauth2/auth",
+ "TokenEndpoint": "https://www.googleapis.com/oauth2/v3/token",
+ "UserApiEndpoint": "https://www.googleapis.com/plus/v1/people/me"
}
},
"SqlSettings": {
diff --git a/model/gitlab.go b/model/gitlab.go
index 9adcac189..d281b6ea0 100644
--- a/model/gitlab.go
+++ b/model/gitlab.go
@@ -23,7 +23,7 @@ type GitLabUser struct {
func UserFromGitLabUser(glu *GitLabUser) *User {
user := &User{}
- user.Username = glu.Username
+ user.Username = CleanUsername(glu.Username)
splitName := strings.Split(glu.Name, " ")
if len(splitName) == 2 {
user.FirstName = splitName[0]
diff --git a/model/google.go b/model/google.go
index 2a1eb3caa..bc65d0817 100644
--- a/model/google.go
+++ b/model/google.go
@@ -23,11 +23,6 @@ type GoogleUser struct {
func UserFromGoogleUser(gu *GoogleUser) *User {
user := &User{}
- if len(gu.Nickname) > 0 {
- user.Username = gu.Nickname
- } else {
- user.Username = strings.ToLower(strings.Replace(gu.DisplayName, " ", "", -1))
- }
user.FirstName = gu.Names["givenName"]
user.LastName = gu.Names["familyName"]
user.Nickname = gu.Nickname
@@ -35,6 +30,7 @@ func UserFromGoogleUser(gu *GoogleUser) *User {
for _, e := range gu.Emails {
if e["type"] == "account" {
user.Email = e["value"]
+ user.Username = CleanUsername(strings.Split(user.Email, "@")[0])
}
}
diff --git a/model/team.go b/model/team.go
index e7005625b..95e2757c8 100644
--- a/model/team.go
+++ b/model/team.go
@@ -5,7 +5,10 @@ package model
import (
"encoding/json"
+ "fmt"
"io"
+ "regexp"
+ "strings"
)
const (
@@ -93,7 +96,7 @@ func (o *Team) IsValid() *AppError {
return NewAppError("Team.IsValid", "Invalid email", "id="+o.Id)
}
- if !IsValidEmail(o.Email) {
+ if len(o.Email) > 0 && !IsValidEmail(o.Email) {
return NewAppError("Team.IsValid", "Invalid email", "id="+o.Id)
}
@@ -140,3 +143,57 @@ func (o *Team) PreSave() {
func (o *Team) PreUpdate() {
o.UpdateAt = GetMillis()
}
+
+func IsReservedTeamName(s string) bool {
+ s = strings.ToLower(s)
+
+ for _, value := range reservedName {
+ if strings.Index(s, value) == 0 {
+ return true
+ }
+ }
+
+ return false
+}
+
+func IsValidTeamName(s string) bool {
+
+ if !IsValidAlphaNum(s) {
+ return false
+ }
+
+ if len(s) <= 3 {
+ return false
+ }
+
+ return true
+}
+
+var validTeamNameCharacter = regexp.MustCompile(`^[a-z0-9-]$`)
+
+func CleanTeamName(s string) string {
+ s = strings.ToLower(strings.Replace(s, " ", "-", -1))
+
+ for _, value := range reservedName {
+ if strings.Index(s, value) == 0 {
+ s = strings.Replace(s, value, "", -1)
+ }
+ }
+
+ s = strings.TrimSpace(s)
+
+ for _, c := range s {
+ char := fmt.Sprintf("%c", c)
+ if !validTeamNameCharacter.MatchString(char) {
+ s = strings.Replace(s, char, "", -1)
+ }
+ }
+
+ s = strings.Trim(s, "-")
+
+ if !IsValidTeamName(s) {
+ s = NewId()
+ }
+
+ return s
+}
diff --git a/model/team_test.go b/model/team_test.go
index 071b1a2e9..0dec07559 100644
--- a/model/team_test.go
+++ b/model/team_test.go
@@ -74,3 +74,62 @@ func TestTeamPreUpdate(t *testing.T) {
o := Team{DisplayName: "test"}
o.PreUpdate()
}
+
+var domains = []struct {
+ value string
+ expected bool
+}{
+ {"spin-punch", true},
+ {"-spin-punch", false},
+ {"spin-punch-", false},
+ {"spin_punch", false},
+ {"a", false},
+ {"aa", false},
+ {"aaa", false},
+ {"aaa-999b", true},
+ {"b00b", true},
+ {"b))b", false},
+ {"test", true},
+}
+
+func TestValidTeamName(t *testing.T) {
+ for _, v := range domains {
+ if IsValidTeamName(v.value) != v.expected {
+ t.Errorf("expect %v as %v", v.value, v.expected)
+ }
+ }
+}
+
+var tReservedDomains = []struct {
+ value string
+ expected bool
+}{
+ {"test-hello", true},
+ {"test", true},
+ {"admin", true},
+ {"Admin-punch", true},
+ {"spin-punch-admin", false},
+}
+
+func TestReservedTeamName(t *testing.T) {
+ for _, v := range tReservedDomains {
+ if IsReservedTeamName(v.value) != v.expected {
+ t.Errorf("expect %v as %v", v.value, v.expected)
+ }
+ }
+}
+
+func TestCleanTeamName(t *testing.T) {
+ if CleanTeamName("Jimbo's Team") != "jimbos-team" {
+ t.Fatal("didn't clean name properly")
+ }
+ if len(CleanTeamName("Test")) != 26 {
+ t.Fatal("didn't clean name properly")
+ }
+ if CleanTeamName("Team Really cool") != "really-cool" {
+ t.Fatal("didn't clean name properly")
+ }
+ if CleanTeamName("super-duper-guys") != "super-duper-guys" {
+ t.Fatal("didn't clean name properly")
+ }
+}
diff --git a/model/user.go b/model/user.go
index ebefa4762..7c53593d2 100644
--- a/model/user.go
+++ b/model/user.go
@@ -6,6 +6,7 @@ package model
import (
"code.google.com/p/go.crypto/bcrypt"
"encoding/json"
+ "fmt"
"io"
"regexp"
"strings"
@@ -72,13 +73,7 @@ func (u *User) IsValid() *AppError {
return NewAppError("User.IsValid", "Invalid team id", "")
}
- if len(u.Username) == 0 || len(u.Username) > 64 {
- return NewAppError("User.IsValid", "Invalid username", "user_id="+u.Id)
- }
-
- validChars, _ := regexp.Compile("^[a-z0-9\\.\\-\\_]+$")
-
- if !validChars.MatchString(u.Username) {
+ if !IsValidUsername(u.Username) {
return NewAppError("User.IsValid", "Invalid username", "user_id="+u.Id)
}
@@ -332,17 +327,58 @@ func ComparePassword(hash string, password string) bool {
func IsUsernameValid(username string) bool {
- var restrictedUsernames = []string{
- BOT_USERNAME,
- "all",
- "channel",
+ return true
+}
+
+var validUsernameChars = regexp.MustCompile(`^[a-z0-9\.\-_]+$`)
+
+var restrictedUsernames = []string{
+ BOT_USERNAME,
+ "all",
+ "channel",
+}
+
+func IsValidUsername(s string) bool {
+ if len(s) == 0 || len(s) > 64 {
+ return false
+ }
+
+ if !validUsernameChars.MatchString(s) {
+ return false
}
for _, restrictedUsername := range restrictedUsernames {
- if username == restrictedUsername {
+ if s == restrictedUsername {
return false
}
}
return true
}
+
+func CleanUsername(s string) string {
+ s = strings.ToLower(strings.Replace(s, " ", "-", -1))
+
+ for _, value := range reservedName {
+ if s == value {
+ s = strings.Replace(s, value, "", -1)
+ }
+ }
+
+ s = strings.TrimSpace(s)
+
+ for _, c := range s {
+ char := fmt.Sprintf("%c", c)
+ if !validUsernameChars.MatchString(char) {
+ s = strings.Replace(s, char, "-", -1)
+ }
+ }
+
+ s = strings.Trim(s, "-")
+
+ if !IsValidUsername(s) {
+ s = "a" + NewId()
+ }
+
+ return s
+}
diff --git a/model/user_test.go b/model/user_test.go
index a48c3f2e7..a3b4be091 100644
--- a/model/user_test.go
+++ b/model/user_test.go
@@ -150,3 +150,45 @@ func TestUserGetDisplayName(t *testing.T) {
t.Fatal("Display name should be nickname")
}
}
+
+var usernames = []struct {
+ value string
+ expected bool
+}{
+ {"spin-punch", true},
+ {"Spin-punch", false},
+ {"spin punch-", false},
+ {"spin_punch", true},
+ {"spin", true},
+ {"PUNCH", false},
+ {"spin.punch", true},
+ {"spin'punch", false},
+ {"spin*punch", false},
+ {"all", false},
+}
+
+func TestValidUsername(t *testing.T) {
+ for _, v := range usernames {
+ if IsValidUsername(v.value) != v.expected {
+ t.Errorf("expect %v as %v", v.value, v.expected)
+ }
+ }
+}
+
+func TestCleanUsername(t *testing.T) {
+ if CleanUsername("Spin-punch") != "spin-punch" {
+ t.Fatal("didn't clean name properly")
+ }
+ if CleanUsername("PUNCH") != "punch" {
+ t.Fatal("didn't clean name properly")
+ }
+ if CleanUsername("spin'punch") != "spin-punch" {
+ t.Fatal("didn't clean name properly")
+ }
+ if CleanUsername("spin") != "spin" {
+ t.Fatal("didn't clean name properly")
+ }
+ if len(CleanUsername("all")) != 27 {
+ t.Fatal("didn't clean name properly")
+ }
+}
diff --git a/model/utils.go b/model/utils.go
index a8257467b..17d1c6317 100644
--- a/model/utils.go
+++ b/model/utils.go
@@ -168,31 +168,6 @@ var reservedName = []string{
"api",
}
-func IsReservedTeamName(s string) bool {
- s = strings.ToLower(s)
-
- for _, value := range reservedName {
- if strings.Index(s, value) == 0 {
- return true
- }
- }
-
- return false
-}
-
-func IsValidTeamName(s string) bool {
-
- if !IsValidAlphaNum(s) {
- return false
- }
-
- if len(s) <= 3 {
- return false
- }
-
- return true
-}
-
var wwwStart = regexp.MustCompile(`^www`)
var betaStart = regexp.MustCompile(`^beta`)
var ciStart = regexp.MustCompile(`^ci`)
diff --git a/model/utils_test.go b/model/utils_test.go
index dbb448882..0f26526b2 100644
--- a/model/utils_test.go
+++ b/model/utils_test.go
@@ -66,50 +66,6 @@ func TestValidLower(t *testing.T) {
}
}
-var domains = []struct {
- value string
- expected bool
-}{
- {"spin-punch", true},
- {"-spin-punch", false},
- {"spin-punch-", false},
- {"spin_punch", false},
- {"a", false},
- {"aa", false},
- {"aaa", false},
- {"aaa-999b", true},
- {"b00b", true},
- {"b))b", false},
- {"test", true},
-}
-
-func TestValidTeamName(t *testing.T) {
- for _, v := range domains {
- if IsValidTeamName(v.value) != v.expected {
- t.Errorf("expect %v as %v", v.value, v.expected)
- }
- }
-}
-
-var tReservedDomains = []struct {
- value string
- expected bool
-}{
- {"test-hello", true},
- {"test", true},
- {"admin", true},
- {"Admin-punch", true},
- {"spin-punch-admin", false},
-}
-
-func TestReservedTeamName(t *testing.T) {
- for _, v := range tReservedDomains {
- if IsReservedTeamName(v.value) != v.expected {
- t.Errorf("expect %v as %v", v.value, v.expected)
- }
- }
-}
-
func TestEtag(t *testing.T) {
etag := Etag("hello", 24)
if len(etag) <= 0 {
diff --git a/utils/config.go b/utils/config.go
index 36301264c..36193412b 100644
--- a/utils/config.go
+++ b/utils/config.go
@@ -31,6 +31,7 @@ type ServiceSettings struct {
UseLocalStorage bool
StorageDirectory string
AllowedLoginAttempts int
+ AllowEmailSignUp bool
}
type SSOSetting struct {
@@ -277,5 +278,23 @@ func GetAllowedAuthServices() []string {
}
}
+ if Cfg.ServiceSettings.AllowEmailSignUp {
+ authServices = append(authServices, "email")
+ }
+
return authServices
}
+
+func IsServiceAllowed(s string) bool {
+ if len(s) == 0 {
+ return false
+ }
+
+ if service, ok := Cfg.SSOSettings[s]; ok {
+ if service.Allow {
+ return true
+ }
+ }
+
+ return false
+}
diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx
index 678a2ff87..489ff6960 100644
--- a/web/react/components/login.jsx
+++ b/web/react/components/login.jsx
@@ -10,7 +10,9 @@ var Constants = require('../utils/constants.jsx');
export default class Login extends React.Component {
constructor(props) {
super(props);
+
this.handleSubmit = this.handleSubmit.bind(this);
+
this.state = {};
}
handleSubmit(e) {
@@ -96,19 +98,28 @@ export default class Login extends React.Component {
var authServices = JSON.parse(this.props.authServices);
var loginMessage = [];
- if (authServices.indexOf(Constants.GITLAB_SERVICE) >= 0) {
+ if (authServices.indexOf(Constants.GITLAB_SERVICE) !== -1) {
loginMessage.push(
- <div className='form-group form-group--small'>
- <span><a href={'/' + teamName + '/login/gitlab'}>{'Log in with GitLab'}</a></span>
- </div>
- );
+ <a
+ className='btn btn-custom-login gitlab'
+ href={'/' + teamName + '/login/gitlab'}
+ >
+ <span className='icon' />
+ <span>with GitLab</span>
+ </a>
+ );
}
- if (authServices.indexOf(Constants.GOOGLE_SERVICE) >= 0) {
+
+ if (authServices.indexOf(Constants.GOOGLE_SERVICE) !== -1) {
loginMessage.push(
- <div className='form-group form-group--small'>
- <span><a href={'/' + teamName + '/login/google'}>{'Log in with Google'}</a></span>
- </div>
- );
+ <a
+ className='btn btn-custom-login google'
+ href={'/' + teamName + '/login/google'}
+ >
+ <span className='icon' />
+ <span>with Google Apps</span>
+ </a>
+ );
}
var errorClass = '';
@@ -116,15 +127,10 @@ export default class Login extends React.Component {
errorClass = ' has-error';
}
- return (
- <div className='signup-team__container'>
- <h5 className='margin--less'>Sign in to:</h5>
- <h2 className='signup-team__name'>{teamDisplayName}</h2>
- <h2 className='signup-team__subdomain'>on {config.SiteName}</h2>
- <form onSubmit={this.handleSubmit}>
- <div className={'form-group' + errorClass}>
- {serverError}
- </div>
+ var emailSignup;
+ if (authServices.indexOf(Constants.EMAIL_SERVICE) !== -1) {
+ emailSignup = (
+ <div>
<div className={'form-group' + errorClass}>
<input
autoFocus={focusEmail}
@@ -154,13 +160,43 @@ export default class Login extends React.Component {
Sign in
</button>
</div>
+ </div>
+ );
+ }
+
+ var forgotPassword;
+ if (loginMessage.length > 0 && emailSignup) {
+ loginMessage = (
+ <div>
+ {loginMessage}
+ <div className='or__container'>
+ <span>or</span>
+ </div>
+ </div>
+ );
+
+ forgotPassword = (
+ <div className='form-group'>
+ <a href={'/' + teamName + '/reset_password'}>I forgot my password</a>
+ </div>
+ );
+ }
+
+ return (
+ <div className='signup-team__container'>
+ <h5 className='margin--less'>Sign in to:</h5>
+ <h2 className='signup-team__name'>{teamDisplayName}</h2>
+ <h2 className='signup-team__subdomain'>on {config.SiteName}</h2>
+ <form onSubmit={this.handleSubmit}>
+ <div className={'form-group' + errorClass}>
+ {serverError}
+ </div>
{loginMessage}
+ {emailSignup}
<div className='form-group margin--extra form-group--small'>
<span><a href='/find_team'>{'Find other ' + strings.TeamPlural}</a></span>
</div>
- <div className='form-group'>
- <a href={'/' + teamName + '/reset_password'}>I forgot my password</a>
- </div>
+ {forgotPassword}
<div className='margin--extra'>
<span>{'Want to create your own ' + strings.Team + '? '}
<a
diff --git a/web/react/components/signup_team.jsx b/web/react/components/signup_team.jsx
index edd48e0b9..13640b1e5 100644
--- a/web/react/components/signup_team.jsx
+++ b/web/react/components/signup_team.jsx
@@ -1,69 +1,49 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-
-var utils = require('../utils/utils.jsx');
-var client = require('../utils/client.jsx');
-
-module.exports = React.createClass({
- handleSubmit: function(e) {
- e.preventDefault();
- var team = {};
- var state = { server_error: "" };
-
- team.email = this.refs.email.getDOMNode().value.trim().toLowerCase();
- if (!team.email || !utils.isEmail(team.email)) {
- state.email_error = "Please enter a valid email address";
- state.inValid = true;
+var ChoosePage = require('./team_signup_choose_auth.jsx');
+var EmailSignUpPage = require('./team_signup_with_email.jsx');
+var SSOSignupPage = require('./team_signup_with_sso.jsx');
+var Constants = require('../utils/constants.jsx');
+
+export default class TeamSignUp extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.updatePage = this.updatePage.bind(this);
+
+ if (props.services.length === 1) {
+ if (props.services[0] === Constants.EMAIL_SERVICE) {
+ this.state = {page: 'email', service: ''};
+ } else {
+ this.state = {page: 'service', service: props.services[0]};
+ }
+ } else {
+ this.state = {page: 'choose', service: ''};
}
- else {
- state.email_error = "";
- }
-
- if (state.inValid) {
- this.setState(state);
- return;
+ }
+ updatePage(page, service) {
+ this.setState({page: page, service: service});
+ }
+ render() {
+ if (this.state.page === 'email') {
+ return <EmailSignUpPage />;
+ } else if (this.state.page === 'service' && this.state.service !== '') {
+ return <SSOSignupPage service={this.state.service} />;
+ } else {
+ return (
+ <ChoosePage
+ services={this.props.services}
+ updatePage={this.updatePage}
+ />
+ );
}
-
- client.signupTeam(team.email,
- function(data) {
- if (data["follow_link"]) {
- window.location.href = data["follow_link"];
- }
- else {
- window.location.href = "/signup_team_confirm/?email=" + encodeURIComponent(team.email);
- }
- }.bind(this),
- function(err) {
- state.server_error = err.message;
- this.setState(state);
- }.bind(this)
- );
- },
- getInitialState: function() {
- return { };
- },
- render: function() {
-
- var email_error = this.state.email_error ? <label className='control-label'>{ this.state.email_error }</label> : null;
- var server_error = this.state.server_error ? <div className={ "form-group has-error" }><label className='control-label'>{ this.state.server_error }</label></div> : null;
-
- return (
- <form role="form" onSubmit={this.handleSubmit}>
- <div className={ email_error ? "form-group has-error" : "form-group" }>
- <input autoFocus={true} type="email" ref="email" className="form-control" placeholder="Email Address" maxLength="128" />
- { email_error }
- </div>
- { server_error }
- <div className="form-group">
- <button className="btn btn-md btn-primary" type="submit">Sign up</button>
- </div>
- <div className="form-group margin--extra-2x">
- <span><a href="/find_team">{"Find my " + strings.Team}</a></span>
- </div>
- </form>
- );
}
-});
-
-
+}
+
+TeamSignUp.defaultProps = {
+ services: []
+};
+TeamSignUp.propTypes = {
+ services: React.PropTypes.array
+};
diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx
index 0393e0413..e5c602c16 100644
--- a/web/react/components/signup_user_complete.jsx
+++ b/web/react/components/signup_user_complete.jsx
@@ -171,7 +171,35 @@ module.exports = React.createClass({
);
}
- if (signupMessage.length > 0) {
+ var emailSignup;
+ if (authServices.indexOf(Constants.EMAIL_SERVICE) !== -1) {
+ emailSignup = (
+ <div>
+ <div className='inner__content'>
+ {email}
+ {yourEmailIs}
+ <div className='margin--extra'>
+ <h5><strong>Choose your username</strong></h5>
+ <div className={nameDivStyle}>
+ <input type='text' ref='name' className='form-control' placeholder='' maxLength='128' />
+ {nameError}
+ <p className='form__hint'>Username must begin with a letter, and contain between 3 to 15 lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'</p>
+ </div>
+ </div>
+ <div className='margin--extra'>
+ <h5><strong>Choose your password</strong></h5>
+ <div className={passwordDivStyle}>
+ <input type='password' ref='password' className='form-control' placeholder='' maxLength='128' />
+ {passwordError}
+ </div>
+ </div>
+ </div>
+ <p className='margin--extra'><button type='submit' onClick={this.handleSubmit} className='btn-primary btn'>Create Account</button></p>
+ </div>
+ );
+ }
+
+ if (signupMessage.length > 0 && emailSignup) {
signupMessage = (
<div>
{signupMessage}
@@ -196,26 +224,7 @@ module.exports = React.createClass({
<h2 className='signup-team__subdomain'>on {config.SiteName}</h2>
<h4 className='color--light'>Let's create your account</h4>
{signupMessage}
- <div className='inner__content'>
- {email}
- {yourEmailIs}
- <div className='margin--extra'>
- <h5><strong>Choose your username</strong></h5>
- <div className={nameDivStyle}>
- <input type='text' ref='name' className='form-control' placeholder='' maxLength='128' />
- {nameError}
- <p className='form__hint'>Username must begin with a letter, and contain between 3 to 15 lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'</p>
- </div>
- </div>
- <div className='margin--extra'>
- <h5><strong>Choose your password</strong></h5>
- <div className={passwordDivStyle}>
- <input type='password' ref='password' className='form-control' placeholder='' maxLength='128' />
- {passwordError}
- </div>
- </div>
- </div>
- <p className='margin--extra'><button type='submit' onClick={this.handleSubmit} className='btn-primary btn'>Create Account</button></p>
+ {emailSignup}
{serverError}
{termsDisclaimer}
</form>
diff --git a/web/react/components/signup_user_oauth.jsx b/web/react/components/signup_user_oauth.jsx
deleted file mode 100644
index 8b2800bde..000000000
--- a/web/react/components/signup_user_oauth.jsx
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-
-var utils = require('../utils/utils.jsx');
-var client = require('../utils/client.jsx');
-var UserStore = require('../stores/user_store.jsx');
-var BrowserStore = require('../stores/browser_store.jsx');
-
-module.exports = React.createClass({
- handleSubmit: function(e) {
- e.preventDefault();
-
- if (!this.state.user.username) {
- this.setState({name_error: "This field is required", email_error: "", password_error: "", server_error: ""});
- return;
- }
-
- var username_error = utils.isValidUsername(this.state.user.username);
- if (username_error === "Cannot use a reserved word as a username.") {
- this.setState({name_error: "This username is reserved, please choose a new one.", email_error: "", password_error: "", server_error: ""});
- return;
- } else if (username_error) {
- this.setState({name_error: "Username must begin with a letter, and contain between 3 to 15 lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'.", email_error: "", password_error: "", server_error: ""});
- return;
- }
-
- this.setState({name_error: "", server_error: ""});
-
- this.state.user.allow_marketing = this.refs.email_service.getDOMNode().checked;
-
- var user = this.state.user;
- client.createUser(user, "", "",
- function(data) {
- client.track('signup', 'signup_user_oauth_02');
- UserStore.setCurrentUser(data);
- UserStore.setLastEmail(data.email);
-
- window.location.href = '/' + this.props.teamName + '/login/' + user.auth_service + '?login_hint=' + user.email;
- }.bind(this),
- function(err) {
- this.state.server_error = err.message;
- this.setState(this.state);
- }.bind(this)
- );
- },
- handleChange: function() {
- var user = this.state.user;
- user.username = this.refs.name.getDOMNode().value;
- this.setState({ user: user });
- },
- getInitialState: function() {
- var user = JSON.parse(this.props.user);
- return { user: user };
- },
- render: function() {
-
- client.track('signup', 'signup_user_oauth_01');
-
- var name_error = this.state.name_error ? <label className='control-label'>{ this.state.name_error }</label> : null;
- var server_error = this.state.server_error ? <div className={ "form-group has-error" }><label className='control-label'>{ this.state.server_error }</label></div> : null;
-
- var yourEmailIs = this.state.user.email == "" ? "" : <span>Your email address is <b>{ this.state.user.email }.</b></span>;
-
- return (
- <div>
- <img className="signup-team-logo" src="/static/images/logo.png" />
- <h4>Welcome to { config.SiteName }</h4>
- <p>{"To continue signing up with " + this.state.user.auth_service + ", please register a username."}</p>
- <p>Your username can be made of lowercase letters and numbers.</p>
- <label className="control-label">Username</label>
- <div className={ name_error ? "form-group has-error" : "form-group" }>
- <input type="text" ref="name" className="form-control" placeholder="" maxLength="128" value={this.state.user.username} onChange={this.handleChange} />
- { name_error }
- </div>
- <p>{"Pick something " + strings.Team + "mates will recognize. Your username is how you will appear to others."}</p>
- <p>{ yourEmailIs } You’ll use this address to sign in to {config.SiteName}.</p>
- <div className="checkbox"><label><input type="checkbox" ref="email_service" /> It's ok to send me occassional email with updates about the {config.SiteName} service. </label></div>
- <p><button onClick={this.handleSubmit} className="btn-primary btn">Create Account</button></p>
- { server_error }
- <p>By proceeding to create your account and use { config.SiteName }, you agree to our <a href={ config.TermsLink }>Terms of Service</a> and <a href={ config.PrivacyLink }>Privacy Policy</a>. If you do not agree, you cannot use {config.SiteName}.</p>
- </div>
- );
- }
-});
-
-
diff --git a/web/react/components/team_signup_choose_auth.jsx b/web/react/components/team_signup_choose_auth.jsx
new file mode 100644
index 000000000..2d35785c2
--- /dev/null
+++ b/web/react/components/team_signup_choose_auth.jsx
@@ -0,0 +1,88 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+var Constants = require('../utils/constants.jsx');
+
+export default class ChooseAuthPage extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {};
+ }
+ render() {
+ var buttons = [];
+ if (this.props.services.indexOf(Constants.GITLAB_SERVICE) !== -1) {
+ buttons.push(
+ <a
+ className='btn btn-custom-login gitlab btn-full'
+ href='#'
+ onClick={
+ function clickGit(e) {
+ e.preventDefault();
+ this.props.updatePage('service', Constants.GITLAB_SERVICE);
+ }.bind(this)
+ }
+ >
+ <span className='icon' />
+ <span>Create new {strings.Team} with GitLab Account</span>
+ </a>
+ );
+ }
+
+ if (this.props.services.indexOf(Constants.GOOGLE_SERVICE) !== -1) {
+ buttons.push(
+ <a
+ className='btn btn-custom-login google btn-full'
+ href='#'
+ onClick={
+ function clickGoogle(e) {
+ e.preventDefault();
+ this.props.updatePage('service', Constants.GOOGLE_SERVICE);
+ }.bind(this)
+ }
+ >
+ <span className='icon' />
+ <span>Create new {strings.Team} with Google Apps Account</span>
+ </a>
+ );
+ }
+
+ if (this.props.services.indexOf(Constants.EMAIL_SERVICE) !== -1) {
+ buttons.push(
+ <a
+ className='btn btn-custom-login email btn-full'
+ href='#'
+ onClick={
+ function clickEmail(e) {
+ e.preventDefault();
+ this.props.updatePage('email', '');
+ }.bind(this)
+ }
+ >
+ <span className='fa fa-envelope' />
+ <span>Create new {strings.Team} with email address</span>
+ </a>
+ );
+ }
+
+ if (buttons.length === 0) {
+ buttons = <span>No sign-up methods configured, please contact your system administrator.</span>;
+ }
+
+ return (
+ <div>
+ {buttons}
+ <div className='form-group margin--extra-2x'>
+ <span><a href='/find_team'>{'Find my ' + strings.Team}</a></span>
+ </div>
+ </div>
+ );
+ }
+}
+
+ChooseAuthPage.defaultProps = {
+ services: []
+};
+ChooseAuthPage.propTypes = {
+ services: React.PropTypes.array,
+ updatePage: React.PropTypes.func
+};
diff --git a/web/react/components/team_signup_with_email.jsx b/web/react/components/team_signup_with_email.jsx
new file mode 100644
index 000000000..c7204880f
--- /dev/null
+++ b/web/react/components/team_signup_with_email.jsx
@@ -0,0 +1,82 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+var utils = require('../utils/utils.jsx');
+var client = require('../utils/client.jsx');
+
+export default class EmailSignUpPage extends React.Component {
+ constructor() {
+ super();
+
+ this.handleSubmit = this.handleSubmit.bind(this);
+
+ this.state = {};
+ }
+ handleSubmit(e) {
+ e.preventDefault();
+ var team = {};
+ var state = {serverError: ''};
+
+ team.email = this.refs.email.getDOMNode().value.trim().toLowerCase();
+ if (!team.email || !utils.isEmail(team.email)) {
+ state.emailError = 'Please enter a valid email address';
+ state.inValid = true;
+ } else {
+ state.emailError = '';
+ }
+
+ if (state.inValid) {
+ this.setState(state);
+ return;
+ }
+
+ client.signupTeam(team.email,
+ function success(data) {
+ if (data.follow_link) {
+ window.location.href = data.follow_link;
+ } else {
+ window.location.href = '/signup_team_confirm/?email=' + encodeURIComponent(team.email);
+ }
+ },
+ function fail(err) {
+ state.serverError = err.message;
+ this.setState(state);
+ }.bind(this)
+ );
+ }
+ render() {
+ return (
+ <form
+ role='form'
+ onSubmit={this.handleSubmit}
+ >
+ <div className='form-group'>
+ <input
+ autoFocus={true}
+ type='email'
+ ref='email'
+ className='form-control'
+ placeholder='Email Address'
+ maxLength='128'
+ />
+ </div>
+ <div className='form-group'>
+ <button
+ className='btn btn-md btn-primary'
+ type='submit'
+ >
+ Sign up
+ </button>
+ </div>
+ <div className='form-group margin--extra-2x'>
+ <span><a href='/find_team'>{'Find my ' + strings.Team}</a></span>
+ </div>
+ </form>
+ );
+ }
+}
+
+EmailSignUpPage.defaultProps = {
+};
+EmailSignUpPage.propTypes = {
+};
diff --git a/web/react/components/team_signup_with_sso.jsx b/web/react/components/team_signup_with_sso.jsx
new file mode 100644
index 000000000..57996d7cc
--- /dev/null
+++ b/web/react/components/team_signup_with_sso.jsx
@@ -0,0 +1,137 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+var utils = require('../utils/utils.jsx');
+var client = require('../utils/client.jsx');
+var Constants = require('../utils/constants.jsx');
+
+export default class SSOSignUpPage extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleSubmit = this.handleSubmit.bind(this);
+ this.nameChange = this.nameChange.bind(this);
+
+ this.state = {name: ''};
+ }
+ handleSubmit(e) {
+ e.preventDefault();
+ var team = {};
+ var state = this.state;
+ state.nameError = null;
+ state.serverError = null;
+
+ team.display_name = this.state.name;
+
+ if (team.display_name.length <= 3) {
+ return;
+ }
+
+ if (!team.display_name) {
+ state.nameError = 'Please enter a team name';
+ this.setState(state);
+ return;
+ }
+
+ team.name = utils.cleanUpUrlable(team.display_name);
+ team.type = 'O';
+
+ client.createTeamWithSSO(team,
+ this.props.service,
+ function success(data) {
+ if (data.follow_link) {
+ window.location.href = data.follow_link;
+ } else {
+ window.location.href = '/';
+ }
+ },
+ function fail(err) {
+ state.serverError = err.message;
+ this.setState(state);
+ }.bind(this)
+ );
+ }
+ nameChange() {
+ this.setState({name: this.refs.teamname.getDOMNode().value.trim()});
+ }
+ render() {
+ var nameError = null;
+ var nameDivClass = 'form-group';
+ if (this.state.nameError) {
+ nameError = <label className='control-label'>{this.state.nameError}</label>;
+ nameDivClass += ' has-error';
+ }
+
+ var serverError = null;
+ if (this.state.serverError) {
+ serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
+ }
+
+ var disabled = false;
+ if (this.state.name.length <= 3) {
+ disabled = true;
+ }
+
+ var button = null;
+
+ if (this.props.service === Constants.GITLAB_SERVICE) {
+ button = (
+ <a
+ className='btn btn-custom-login gitlab btn-full'
+ href='#'
+ onClick={this.handleSubmit}
+ disabled={disabled}
+ >
+ <span className='icon'/>
+ <span>Create {strings.Team} with GitLab Account</span>
+ </a>
+ );
+ } else if (this.props.service === Constants.GOOGLE_SERVICE) {
+ button = (
+ <a
+ className='btn btn-custom-login google btn-full'
+ href='#'
+ onClick={this.handleSubmit}
+ disabled={disabled}
+ >
+ <span className='icon'/>
+ <span>Create {strings.Team} with Google Apps Account</span>
+ </a>
+ );
+ }
+
+ return (
+ <form
+ role='form'
+ onSubmit={this.handleSubmit}
+ >
+ <div className={nameDivClass}>
+ <input
+ autoFocus={true}
+ type='text'
+ ref='teamname'
+ className='form-control'
+ placeholder='Enter name of new team'
+ maxLength='128'
+ onChange={this.nameChange}
+ />
+ {nameError}
+ </div>
+ <div className='form-group'>
+ {button}
+ {serverError}
+ </div>
+ <div className='form-group margin--extra-2x'>
+ <span><a href='/find_team'>{'Find my ' + strings.Team}</a></span>
+ </div>
+ </form>
+ );
+ }
+}
+
+SSOSignUpPage.defaultProps = {
+ service: ''
+};
+SSOSignUpPage.propTypes = {
+ service: React.PropTypes.string
+};
diff --git a/web/react/pages/signup_team.jsx b/web/react/pages/signup_team.jsx
index 37c441d4f..4b58025ac 100644
--- a/web/react/pages/signup_team.jsx
+++ b/web/react/pages/signup_team.jsx
@@ -5,11 +5,13 @@ var SignupTeam = require('../components/signup_team.jsx');
var AsyncClient = require('../utils/async_client.jsx');
-global.window.setup_signup_team_page = function() {
+global.window.setup_signup_team_page = function(authServices) {
AsyncClient.getConfig();
+ var services = JSON.parse(authServices);
+
React.render(
- <SignupTeam />,
+ <SignupTeam services={services} />,
document.getElementById('signup-team')
);
};
diff --git a/web/react/pages/signup_user_oauth.jsx b/web/react/pages/signup_user_oauth.jsx
deleted file mode 100644
index 6a0707702..000000000
--- a/web/react/pages/signup_user_oauth.jsx
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-var SignupUserOAuth = require('../components/signup_user_oauth.jsx');
-
-global.window.setup_signup_user_oauth_page = function(user, team_name, team_display_name) {
- React.render(
- <SignupUserOAuth user={user} teamName={team_name} teamDisplayName={team_display_name} />,
- document.getElementById('signup-user-complete')
- );
-};
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx
index 70220c71e..082f82a08 100644
--- a/web/react/utils/client.jsx
+++ b/web/react/utils/client.jsx
@@ -70,6 +70,21 @@ module.exports.createTeamFromSignup = function(teamSignup, success, error) {
});
};
+module.exports.createTeamWithSSO = function(team, service, success, error) {
+ $.ajax({
+ url: '/api/v1/teams/create_with_sso/' + service,
+ dataType: 'json',
+ contentType: 'application/json',
+ type: 'POST',
+ data: JSON.stringify(team),
+ success: success,
+ error: function onError(xhr, status, err) {
+ var e = handleError('createTeamWithSSO', xhr, status, err);
+ error(e);
+ }
+ });
+};
+
module.exports.createUser = function(user, data, emailHash, success, error) {
$.ajax({
url: '/api/v1/users/create?d=' + encodeURIComponent(data) + '&h=' + encodeURIComponent(emailHash),
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index 82fc3da22..6678790e2 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -61,6 +61,7 @@ module.exports = {
OFFTOPIC_CHANNEL: 'off-topic',
GITLAB_SERVICE: 'gitlab',
GOOGLE_SERVICE: 'google',
+ EMAIL_SERVICE: 'email',
POST_CHUNK_SIZE: 60,
MAX_POST_CHUNKS: 3,
POST_LOADING: 'loading',
diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss
index 733d81c2b..682809f02 100644
--- a/web/sass-files/sass/partials/_responsive.scss
+++ b/web/sass-files/sass/partials/_responsive.scss
@@ -290,6 +290,14 @@
.signup-team__name {
font-size: 2em;
}
+ .btn.btn-full {
+ padding-left: 10px;
+ }
+ .btn {
+ .icon, .fa {
+ margin-right: 6px;
+ }
+ }
}
.modal {
.info__label {
diff --git a/web/sass-files/sass/partials/_signup.scss b/web/sass-files/sass/partials/_signup.scss
index ddf2aab88..2fb56e537 100644
--- a/web/sass-files/sass/partials/_signup.scss
+++ b/web/sass-files/sass/partials/_signup.scss
@@ -156,9 +156,21 @@
}
.btn {
+ font-size: 1em;
padding: em(7px) em(15px);
font-weight: 600;
margin-right: 5px;
+ .fa {
+ font-size: 17px;
+ margin-right: 8px;
+ }
+ .icon {
+ width: 18px;
+ height: 18px;
+ margin-right: 8px;
+ @include background-size(100% 100%);
+ display: inline-block;
+ }
&.btn-custom-login {
display: block;
min-width: 200px;
@@ -166,7 +178,7 @@
padding: 0 1em;
margin: 1em auto;
height: 40px;
- line-height: 35px;
+ line-height: 34px;
color: #fff;
@include border-radius(2px);
&.gitlab {
@@ -178,12 +190,7 @@
vertical-align: middle;
}
.icon {
- background: url("../images/gitlabLogo.png");
- width: 18px;
- height: 18px;
- margin-right: 8px;
- @include background-size(100% 100%);
- display: inline-block;
+ background-image: url("../images/gitlabLogo.png");
}
}
&.google {
@@ -195,13 +202,22 @@
vertical-align: middle;
}
.icon {
- background: url("../images/googleLogo.png");
- width: 18px;
- height: 18px;
- margin-right: 8px;
- @include background-size(100% 100%);
- display: inline-block;
+ background-image: url("../images/googleLogo.png");
+ }
+ }
+ &.email {
+ background: #2389D7;
+ &:hover {
+ background: darken(#2389D7, 10%);
}
+ span {
+ vertical-align: middle;
+ }
+ }
+ &.btn-full {
+ width: 100%;
+ text-align: left;
+ padding-left: 35px;
}
}
&.btn-default {
diff --git a/web/templates/signup_team.html b/web/templates/signup_team.html
index b896dedf5..8d9d6e0b8 100644
--- a/web/templates/signup_team.html
+++ b/web/templates/signup_team.html
@@ -22,7 +22,7 @@
</div>
</div>
<script>
-window.setup_signup_team_page();
+window.setup_signup_team_page('{{.Props.AuthServices}}');
</script>
</body>
</html>
diff --git a/web/templates/signup_user_oauth.html b/web/templates/signup_user_oauth.html
deleted file mode 100644
index 2eddb50d2..000000000
--- a/web/templates/signup_user_oauth.html
+++ /dev/null
@@ -1,26 +0,0 @@
-{{define "signup_user_oauth"}}
-<!DOCTYPE html>
-<html>
-{{template "head" . }}
-<body class="white">
- <div class="container-fluid">
- <div class="inner__wrap">
- <div class="row content">
- <div class="col-sm-12">
- <div class="signup-team__container">
- <div id="signup-user-complete"></div>
- </div>
- </div>
- <div class="footer-push"></div>
- </div>
- <div class="row footer">
- {{template "footer" . }}
- </div>
- </div>
- </div>
- <script>
- window.setup_signup_user_oauth_page('{{.Props.User}}', '{{.Props.TeamName}}', '{{.Props.TeamDisplayName}}');
- </script>
-</body>
-</html>
-{{end}}
diff --git a/web/web.go b/web/web.go
index dc2b5dced..03dbdde6a 100644
--- a/web/web.go
+++ b/web/web.go
@@ -145,6 +145,7 @@ func root(c *api.Context, w http.ResponseWriter, r *http.Request) {
if len(c.Session.UserId) == 0 {
page := NewHtmlTemplatePage("signup_team", "Signup")
+ page.Props["AuthServices"] = model.ArrayToJson(utils.GetAllowedAuthServices())
page.Render(c, w)
} else {
page := NewHtmlTemplatePage("home", "Home")
@@ -160,6 +161,7 @@ func signup(c *api.Context, w http.ResponseWriter, r *http.Request) {
}
page := NewHtmlTemplatePage("signup_team", "Signup")
+ page.Props["AuthServices"] = model.ArrayToJson(utils.GetAllowedAuthServices())
page.Render(c, w)
}
@@ -529,23 +531,52 @@ func signupCompleteOAuth(c *api.Context, w http.ResponseWriter, r *http.Request)
return
}
- if result := <-api.Srv.Store.User().GetByAuth(team.Id, user.AuthData, service); result.Err == nil {
+ 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 {
+ found := true
+ count := 0
+ for found {
+ if found = api.IsUsernameTaken(user.Username, team.Id); c.Err != nil {
+ return
+ } 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
}
- if result := <-api.Srv.Store.User().GetByEmail(team.Id, user.Email); result.Err == 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
}
user.TeamId = team.Id
- page := NewHtmlTemplatePage("signup_user_oauth", "Complete User Sign Up")
- page.Props["User"] = user.ToJson()
- page.Props["TeamName"] = team.Name
- page.Props["TeamDisplayName"] = team.DisplayName
- page.Render(c, w)
+ ruser := api.CreateUser(c, team, user)
+ if c.Err != nil {
+ return
+ }
+
+ api.Login(c, w, r, ruser, "")
+
+ if c.Err != nil {
+ return
+ }
+
+ root(c, w, r)
}
}