diff options
Diffstat (limited to 'web')
-rw-r--r-- | web/react/components/admin_console/email_settings.jsx | 84 | ||||
-rw-r--r-- | web/react/components/center_panel.jsx | 2 | ||||
-rw-r--r-- | web/react/components/get_link_modal.jsx | 23 | ||||
-rw-r--r-- | web/react/components/get_team_invite_link_modal.jsx | 12 | ||||
-rw-r--r-- | web/react/components/login.jsx | 15 | ||||
-rw-r--r-- | web/react/components/login_username.jsx | 181 | ||||
-rw-r--r-- | web/react/components/team_general_tab.jsx | 4 | ||||
-rw-r--r-- | web/react/stores/user_store.jsx | 10 | ||||
-rw-r--r-- | web/react/utils/client.jsx | 22 | ||||
-rw-r--r-- | web/static/i18n/en.json | 13 |
10 files changed, 354 insertions, 12 deletions
diff --git a/web/react/components/admin_console/email_settings.jsx b/web/react/components/admin_console/email_settings.jsx index ce3c8cd12..17f25a04c 100644 --- a/web/react/components/admin_console/email_settings.jsx +++ b/web/react/components/admin_console/email_settings.jsx @@ -112,6 +112,8 @@ class EmailSettings extends React.Component { buildConfig() { var config = this.props.config; config.EmailSettings.EnableSignUpWithEmail = ReactDOM.findDOMNode(this.refs.allowSignUpWithEmail).checked; + config.EmailSettings.EnableSignInWithEmail = ReactDOM.findDOMNode(this.refs.allowSignInWithEmail).checked; + config.EmailSettings.EnableSignInWithUsername = ReactDOM.findDOMNode(this.refs.allowSignInWithUsername).checked; config.EmailSettings.SendEmailNotifications = ReactDOM.findDOMNode(this.refs.sendEmailNotifications).checked; config.EmailSettings.SendPushNotifications = ReactDOM.findDOMNode(this.refs.sendPushNotifications).checked; config.EmailSettings.RequireEmailVerification = ReactDOM.findDOMNode(this.refs.requireEmailVerification).checked; @@ -320,6 +322,88 @@ class EmailSettings extends React.Component { <div className='form-group'> <label className='control-label col-sm-4' + htmlFor='allowSignInWithEmail' + > + <FormattedMessage + id='admin.email.allowEmailSignInTitle' + defaultMessage='Allow Sign In With Email: ' + /> + </label> + <div className='col-sm-8'> + <label className='radio-inline'> + <input + type='radio' + name='allowSignInWithEmail' + value='true' + ref='allowSignInWithEmail' + defaultChecked={this.props.config.EmailSettings.EnableSignInWithEmail} + onChange={this.handleChange.bind(this, 'allowSignInWithEmail_true')} + /> + {'true'} + </label> + <label className='radio-inline'> + <input + type='radio' + name='allowSignInWithEmail' + value='false' + defaultChecked={!this.props.config.EmailSettings.EnableSignInWithEmail} + onChange={this.handleChange.bind(this, 'allowSignInWithEmail_false')} + /> + {'false'} + </label> + <p className='help-text'> + <FormattedMessage + id='admin.email.allowEmailSignInDescription' + defaultMessage='When true, Mattermost allows users to sign in using their email and password.' + /> + </p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='allowSignInWithUsername' + > + <FormattedMessage + id='admin.email.allowUsernameSignInTitle' + defaultMessage='Allow Sign In With Username: ' + /> + </label> + <div className='col-sm-8'> + <label className='radio-inline'> + <input + type='radio' + name='allowSignInWithUsername' + value='true' + ref='allowSignInWithUsername' + defaultChecked={this.props.config.EmailSettings.EnableSignInWithUsername} + onChange={this.handleChange.bind(this, 'allowSignInWithUsername_true')} + /> + {'true'} + </label> + <label className='radio-inline'> + <input + type='radio' + name='allowSignInWithUsername' + value='false' + defaultChecked={!this.props.config.EmailSettings.EnableSignInWithUsername} + onChange={this.handleChange.bind(this, 'allowSignInWithUsername_false')} + /> + {'false'} + </label> + <p className='help-text'> + <FormattedMessage + id='admin.email.allowUsernameSignInDescription' + defaultMessage='When true, Mattermost allows users to sign in using their username and password. This setting is typically only used when email verification is disabled.' + /> + </p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' htmlFor='sendEmailNotifications' > <FormattedMessage diff --git a/web/react/components/center_panel.jsx b/web/react/components/center_panel.jsx index 53dad1306..443ecefde 100644 --- a/web/react/components/center_panel.jsx +++ b/web/react/components/center_panel.jsx @@ -69,7 +69,7 @@ export default class CenterPanel extends React.Component { onClick={handleClick} > <a href=''> - {'You are viewing the Archives. Click here to jump to recent messages. '} + {'Click here to jump to recent messages. '} {<i className='fa fa-arrow-down'></i>} </a> </div> diff --git a/web/react/components/get_link_modal.jsx b/web/react/components/get_link_modal.jsx index 3fc71ff96..de3387a35 100644 --- a/web/react/components/get_link_modal.jsx +++ b/web/react/components/get_link_modal.jsx @@ -41,6 +41,8 @@ export default class GetLinkModal extends React.Component { } render() { + const userCreationEnabled = global.window.mm_config.EnableUserCreation === 'true'; + let helpText = null; if (this.props.helpText) { helpText = ( @@ -53,7 +55,7 @@ export default class GetLinkModal extends React.Component { } let copyLink = null; - if (document.queryCommandSupported('copy')) { + if (userCreationEnabled && document.queryCommandSupported('copy')) { copyLink = ( <button data-copy-btn='true' @@ -69,6 +71,18 @@ export default class GetLinkModal extends React.Component { ); } + let linkText = null; + if (userCreationEnabled) { + linkText = ( + <textarea + className='form-control no-resize min-height' + readOnly='true' + ref='textarea' + value={this.props.link} + /> + ); + } + var copyLinkConfirm = null; if (this.state.copiedLink) { copyLinkConfirm = ( @@ -92,12 +106,7 @@ export default class GetLinkModal extends React.Component { </Modal.Header> <Modal.Body> {helpText} - <textarea - className='form-control no-resize min-height' - readOnly='true' - ref='textarea' - value={this.props.link} - /> + {linkText} </Modal.Body> <Modal.Footer> <button diff --git a/web/react/components/get_team_invite_link_modal.jsx b/web/react/components/get_team_invite_link_modal.jsx index 883871267..299729250 100644 --- a/web/react/components/get_team_invite_link_modal.jsx +++ b/web/react/components/get_team_invite_link_modal.jsx @@ -16,6 +16,10 @@ const holders = defineMessages({ help: { id: 'get_team_invite_link_modal.help', defaultMessage: 'Send teammates the link below for them to sign-up to this team site.' + }, + helpDisabled: { + id: 'get_team_invite_link_modal.helpDisabled', + defaultMessage: 'User creation has been disabled for your team. Please ask your team administrator for details.' } }); @@ -47,12 +51,18 @@ class GetTeamInviteLinkModal extends React.Component { render() { const {formatMessage} = this.props.intl; + let helpText = formatMessage(holders.helpDisabled); + + if (global.window.mm_config.EnableUserCreation === 'true') { + helpText = formatMessage(holders.help); + } + return ( <GetLinkModal show={this.state.show} onHide={() => this.setState({show: false})} title={formatMessage(holders.title)} - helpText={formatMessage(holders.help)} + helpText={helpText} link={TeamStore.getCurrentInviteLink()} /> ); diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx index c4f530af0..0123a0f3c 100644 --- a/web/react/components/login.jsx +++ b/web/react/components/login.jsx @@ -2,6 +2,7 @@ // See License.txt for license information. import LoginEmail from './login_email.jsx'; +import LoginUsername from './login_username.jsx'; import LoginLdap from './login_ldap.jsx'; import * as Utils from '../utils/utils.jsx'; @@ -35,7 +36,7 @@ export default class Login extends React.Component { /> </span> </a> - ); + ); } if (global.window.mm_config.EnableSignUpWithGoogle === 'true') { @@ -87,7 +88,7 @@ export default class Login extends React.Component { } let emailSignup; - if (global.window.mm_config.EnableSignUpWithEmail === 'true') { + if (global.window.mm_config.EnableSignInWithEmail === 'true') { emailSignup = ( <LoginEmail teamName={this.props.teamName} @@ -189,6 +190,15 @@ export default class Login extends React.Component { ); } + let usernameLogin = null; + if (global.window.mm_config.EnableSignInWithUsername === 'true') { + usernameLogin = ( + <LoginUsername + teamName={this.props.teamName} + /> + ); + } + return ( <div className='signup-team__container'> <h5 className='margin--less'> @@ -210,6 +220,7 @@ export default class Login extends React.Component { {extraBox} {loginMessage} {emailSignup} + {usernameLogin} {ldapLogin} {userSignUp} {findTeams} diff --git a/web/react/components/login_username.jsx b/web/react/components/login_username.jsx new file mode 100644 index 000000000..f787490fa --- /dev/null +++ b/web/react/components/login_username.jsx @@ -0,0 +1,181 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import * as Utils from '../utils/utils.jsx'; +import * as Client from '../utils/client.jsx'; +import UserStore from '../stores/user_store.jsx'; + +import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl'; + +var holders = defineMessages({ + badTeam: { + id: 'login_username.badTeam', + defaultMessage: 'Bad team name' + }, + usernameReq: { + id: 'login_username.usernameReq', + defaultMessage: 'A username is required' + }, + pwdReq: { + id: 'login_username.pwdReq', + defaultMessage: 'A password is required' + }, + verifyEmailError: { + id: 'login_username.verifyEmailError', + defaultMessage: 'Please verify your email address. Check your inbox for an email.' + }, + userNotFoundError: { + id: 'login_username.userNotFoundError', + defaultMessage: "We couldn't find an existing account matching your username for this team." + }, + username: { + id: 'login_username.username', + defaultMessage: 'Username' + }, + pwd: { + id: 'login_username.pwd', + defaultMessage: 'Password' + } +}); + +export default class LoginUsername extends React.Component { + constructor(props) { + super(props); + + this.handleSubmit = this.handleSubmit.bind(this); + + this.state = { + serverError: '' + }; + } + handleSubmit(e) { + e.preventDefault(); + const {formatMessage} = this.props.intl; + var state = {}; + + const name = this.props.teamName; + if (!name) { + state.serverError = formatMessage(holders.badTeam); + this.setState(state); + return; + } + + const username = this.refs.username.value.trim(); + if (!username) { + state.serverError = formatMessage(holders.usernameReq); + this.setState(state); + return; + } + + const password = this.refs.password.value.trim(); + if (!password) { + state.serverError = formatMessage(holders.pwdReq); + this.setState(state); + return; + } + + state.serverError = ''; + this.setState(state); + + Client.loginByUsername(name, username, password, + () => { + UserStore.setLastUsername(username); + + const redirect = Utils.getUrlParameter('redirect'); + if (redirect) { + window.location.href = decodeURIComponent(redirect); + } else { + window.location.href = '/' + name + '/channels/town-square'; + } + }, + (err) => { + if (err.message === 'api.user.login.not_verified.app_error') { + state.serverError = formatMessage(holders.verifyEmailError); + } else if (err.message === 'store.sql_user.get_by_username.app_error') { + state.serverError = formatMessage(holders.userNotFoundError); + } else { + state.serverError = err.message; + } + + this.valid = false; + this.setState(state); + } + ); + } + render() { + let serverError; + let errorClass = ''; + if (this.state.serverError) { + serverError = <label className='control-label'>{this.state.serverError}</label>; + errorClass = ' has-error'; + } + + let priorUsername = UserStore.getLastUsername(); + let focusUsername = false; + let focusPassword = false; + if (priorUsername === '') { + focusUsername = true; + } else { + focusPassword = true; + } + + const emailParam = Utils.getUrlParameter('email'); + if (emailParam) { + priorUsername = decodeURIComponent(emailParam); + } + + const {formatMessage} = this.props.intl; + return ( + <form onSubmit={this.handleSubmit}> + <div className='signup__email-container'> + <div className={'form-group' + errorClass}> + {serverError} + </div> + <div className={'form-group' + errorClass}> + <input + autoFocus={focusUsername} + type='username' + className='form-control' + name='username' + defaultValue={priorUsername} + ref='username' + placeholder={formatMessage(holders.username)} + spellCheck='false' + /> + </div> + <div className={'form-group' + errorClass}> + <input + autoFocus={focusPassword} + type='password' + className='form-control' + name='password' + ref='password' + placeholder={formatMessage(holders.pwd)} + spellCheck='false' + /> + </div> + <div className='form-group'> + <button + type='submit' + className='btn btn-primary' + > + <FormattedMessage + id='login_username.signin' + defaultMessage='Sign in' + /> + </button> + </div> + </div> + </form> + ); + } +} +LoginUsername.defaultProps = { +}; + +LoginUsername.propTypes = { + intl: intlShape.isRequired, + teamName: React.PropTypes.string.isRequired +}; + +export default injectIntl(LoginUsername); diff --git a/web/react/components/team_general_tab.jsx b/web/react/components/team_general_tab.jsx index 0656d3b03..0a1b02853 100644 --- a/web/react/components/team_general_tab.jsx +++ b/web/react/components/team_general_tab.jsx @@ -575,6 +575,8 @@ class GeneralTab extends React.Component { </div> ); + const nameExtraInfo = <span>{formatMessage(holders.teamNameInfo)}</span>; + nameSection = ( <SettingItemMax title={formatMessage({id: 'general_tab.teamName'})} @@ -583,7 +585,7 @@ class GeneralTab extends React.Component { server_error={serverError} client_error={clientError} updateSection={this.onUpdateNameSection} - extraInfo={formatMessage(holders.teamNameInfo)} + extraInfo={nameExtraInfo} /> ); } else { diff --git a/web/react/stores/user_store.jsx b/web/react/stores/user_store.jsx index 3e1871180..b97a0d87b 100644 --- a/web/react/stores/user_store.jsx +++ b/web/react/stores/user_store.jsx @@ -38,6 +38,8 @@ class UserStoreClass extends EventEmitter { this.setCurrentUser = this.setCurrentUser.bind(this); this.getLastEmail = this.getLastEmail.bind(this); this.setLastEmail = this.setLastEmail.bind(this); + this.getLastUsername = this.getLastUsername.bind(this); + this.setLastUsername = this.setLastUsername.bind(this); this.hasProfile = this.hasProfile.bind(this); this.getProfile = this.getProfile.bind(this); this.getProfileByUsername = this.getProfileByUsername.bind(this); @@ -159,6 +161,14 @@ class UserStoreClass extends EventEmitter { BrowserStore.setGlobalItem('last_email', email); } + getLastUsername() { + return BrowserStore.getGlobalItem('last_username', ''); + } + + setLastUsername(username) { + BrowserStore.setGlobalItem('last_username', username); + } + hasProfile(userId) { return this.getProfiles()[userId] != null; } diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx index 09cd4162a..c4b1bc061 100644 --- a/web/react/utils/client.jsx +++ b/web/react/utils/client.jsx @@ -305,6 +305,28 @@ export function loginByEmail(name, email, password, success, error) { }); } +export function loginByUsername(name, username, password, success, error) { + $.ajax({ + url: '/api/v1/users/login', + dataType: 'json', + contentType: 'application/json', + type: 'POST', + data: JSON.stringify({name, username, password}), + success: function onSuccess(data, textStatus, xhr) { + track('api', 'api_users_login_success', data.team_id, 'username', data.username); + sessionStorage.removeItem(data.id + '_last_error'); + BrowserStore.signalLogin(); + success(data, textStatus, xhr); + }, + error: function onError(xhr, status, err) { + track('api', 'api_users_login_fail', name, 'username', username); + + var e = handleError('loginByUsername', xhr, status, err); + error(e); + } + }); +} + export function loginByLdap(teamName, id, password, success, error) { $.ajax({ url: '/api/v1/users/login_ldap', diff --git a/web/static/i18n/en.json b/web/static/i18n/en.json index d6401ab6e..de955349e 100644 --- a/web/static/i18n/en.json +++ b/web/static/i18n/en.json @@ -127,6 +127,10 @@ "admin.email.true": "true", "admin.email.false": "false", "admin.email.allowSignupDescription": "When true, Mattermost allows team creation and account signup using email and password. This value should be false only when you want to limit signup to a single-sign-on service like OAuth or LDAP.", + "admin.email.allowEmailSignInTitle": "Allow Sign In With Email: ", + "admin.email.allowEmailSignInDescription": "When true, Mattermost allows users to sign in using their email and password.", + "admin.email.allowUsernameSignInTitle": "Allow Sign In With Username: ", + "admin.email.allowUsernameSignInDescription": "When true, Mattermost allows users to sign in using their username and password. This setting is typically only used when email verification is disabled.", "admin.email.notificationsTitle": "Send Email Notifications: ", "admin.email.notificationsDescription": "Typically set to true in production. When true, Mattermost attempts to send email notifications. Developers may set this field to false to skip email setup for faster development.<br />Setting this to true removes the Preview Mode banner (requires logging out and logging back in after setting is changed).", "admin.email.requireVerificationTitle": "Require Email Verification: ", @@ -526,6 +530,7 @@ "get_link.close": "Close", "get_team_invite_link_modal.title": "Team Invite Link", "get_team_invite_link_modal.help": "Send teammates the link below for them to sign-up to this team site.", + "get_team_invite_link_modal.helpDisabled": "User creation has been disabled for your team. Please ask your team administrator for details.", "invite_member.emailError": "Please enter a valid email address", "invite_member.firstname": "First name", "invite_member.lastname": "Last name", @@ -550,6 +555,14 @@ "login_email.email": "Email", "login_email.pwd": "Password", "login_email.signin": "Sign in", + "login_username.badTeam": "Bad team name", + "login_username.usernameReq": "A username is required", + "login_username.pwdReq": "A password is required", + "login_username.verifyEmailError": "Please verify your email address. Check your inbox for an email.", + "login_username.userNotFoundError": "We couldn't find an existing account matching your username for this team.", + "login_username.username": "Username", + "login_username.pwd": "Password", + "login_username.signin": "Sign in", "login_ldap.badTeam": "Bad team name", "login_ldap.idlReq": "An LDAP ID is required", "login_ldap.pwdReq": "An LDAP password is required", |