Merge pull request #496 from mattermost/mm-2015

MM-2015 Added the ability to create a team with SSO services and added the ability to turn off email sign up.
This commit is contained in:
Christopher Speller
2015-08-28 09:14:37 -04:00
30 changed files with 880 additions and 342 deletions

View File

@@ -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

View File

@@ -1,69 +1,49 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
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');
var utils = require('../utils/utils.jsx');
var client = require('../utils/client.jsx');
export default class TeamSignUp extends React.Component {
constructor(props) {
super(props);
module.exports = React.createClass({
handleSubmit: function(e) {
e.preventDefault();
var team = {};
var state = { server_error: "" };
this.updatePage = this.updatePage.bind(this);
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;
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;
}
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>
);
}
});
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}
/>
);
}
}
}
TeamSignUp.defaultProps = {
services: []
};
TeamSignUp.propTypes = {
services: React.PropTypes.array
};

View File

@@ -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>

View File

@@ -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 } Youll 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>
);
}
});

View File

@@ -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
};

View File

@@ -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 = {
};

View File

@@ -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
};

View File

@@ -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')
);
};

View File

@@ -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')
);
};

View File

@@ -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),

View File

@@ -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',