diff options
Diffstat (limited to 'webapp/components')
-rw-r--r-- | webapp/components/admin_console/admin_sidebar.jsx | 6 | ||||
-rw-r--r-- | webapp/components/admin_console/login_settings.jsx | 125 | ||||
-rw-r--r-- | webapp/components/admin_console/password_settings.jsx | 310 | ||||
-rw-r--r-- | webapp/components/claim/components/ldap_to_email.jsx | 10 | ||||
-rw-r--r-- | webapp/components/claim/components/oauth_to_email.jsx | 9 | ||||
-rw-r--r-- | webapp/components/signup_user_complete.jsx | 13 | ||||
-rw-r--r-- | webapp/components/user_settings/user_settings_security.jsx | 10 |
7 files changed, 341 insertions, 142 deletions
diff --git a/webapp/components/admin_console/admin_sidebar.jsx b/webapp/components/admin_console/admin_sidebar.jsx index 5a31519c9..49df8f820 100644 --- a/webapp/components/admin_console/admin_sidebar.jsx +++ b/webapp/components/admin_console/admin_sidebar.jsx @@ -428,11 +428,11 @@ export default class AdminSidebar extends React.Component { } /> <AdminSidebarSection - name='login' + name='password' title={ <FormattedMessage - id='admin.sidebar.login' - defaultMessage='Login' + id='admin.sidebar.password' + defaultMessage='Password' /> } /> diff --git a/webapp/components/admin_console/login_settings.jsx b/webapp/components/admin_console/login_settings.jsx deleted file mode 100644 index 651d8352b..000000000 --- a/webapp/components/admin_console/login_settings.jsx +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import React from 'react'; - -import * as Utils from 'utils/utils.jsx'; - -import AdminSettings from './admin_settings.jsx'; -import BooleanSetting from './boolean_setting.jsx'; -import {FormattedMessage} from 'react-intl'; -import GeneratedSetting from './generated_setting.jsx'; -import SettingsGroup from './settings_group.jsx'; -import TextSetting from './text_setting.jsx'; - -export default class LoginSettings extends AdminSettings { - constructor(props) { - super(props); - - this.getConfigFromState = this.getConfigFromState.bind(this); - - this.renderSettings = this.renderSettings.bind(this); - } - - getConfigFromState(config) { - config.EmailSettings.PasswordResetSalt = this.state.passwordResetSalt; - config.ServiceSettings.MaximumLoginAttempts = this.parseIntNonZero(this.state.maximumLoginAttempts); - if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.MFA === 'true') { - config.ServiceSettings.EnableMultifactorAuthentication = this.state.enableMultifactorAuthentication; - } - - return config; - } - - getStateFromConfig(config) { - return { - passwordResetSalt: config.EmailSettings.PasswordResetSalt, - maximumLoginAttempts: config.ServiceSettings.MaximumLoginAttempts, - enableMultifactorAuthentication: config.ServiceSettings.EnableMultifactorAuthentication - }; - } - - renderTitle() { - return ( - <h3> - <FormattedMessage - id='admin.security.login' - defaultMessage='Login' - /> - </h3> - ); - } - - renderSettings() { - let mfaSetting = null; - if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.MFA === 'true') { - mfaSetting = ( - <BooleanSetting - id='enableMultifactorAuthentication' - label={ - <FormattedMessage - id='admin.service.mfaTitle' - defaultMessage='Enable Multi-factor Authentication:' - /> - } - helpText={ - <FormattedMessage - id='admin.service.mfaDesc' - defaultMessage='When true, users will be given the option to add multi-factor authentication to their account. They will need a smartphone and an authenticator app such as Google Authenticator.' - /> - } - value={this.state.enableMultifactorAuthentication} - onChange={this.handleChange} - /> - ); - } - - return ( - <SettingsGroup> - <GeneratedSetting - id='passwordResetSalt' - label={ - <FormattedMessage - id='admin.email.passwordSaltTitle' - defaultMessage='Password Reset Salt:' - /> - } - helpText={ - <FormattedMessage - id='admin.email.passwordSaltDescription' - defaultMessage='32-character salt added to signing of password reset emails. Randomly generated on install. Click "Regenerate" to create new salt.' - /> - } - value={this.state.passwordResetSalt} - onChange={this.handleChange} - disabled={this.state.sendEmailNotifications} - disabledText={ - <FormattedMessage - id='admin.security.passwordResetSalt.disabled' - defaultMessage='Password reset salt cannot be changed while sending emails is disabled.' - /> - } - /> - <TextSetting - id='maximumLoginAttempts' - label={ - <FormattedMessage - id='admin.service.attemptTitle' - defaultMessage='Maximum Login Attempts:' - /> - } - placeholder={Utils.localizeMessage('admin.service.attemptExample', 'Ex "10"')} - helpText={ - <FormattedMessage - id='admin.service.attemptDescription' - defaultMessage='Login attempts allowed before user is locked out and required to reset password via email.' - /> - } - value={this.state.maximumLoginAttempts} - onChange={this.handleChange} - /> - {mfaSetting} - </SettingsGroup> - ); - } -} diff --git a/webapp/components/admin_console/password_settings.jsx b/webapp/components/admin_console/password_settings.jsx new file mode 100644 index 000000000..9d335c539 --- /dev/null +++ b/webapp/components/admin_console/password_settings.jsx @@ -0,0 +1,310 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; +import AdminSettings from './admin_settings.jsx'; +import {FormattedMessage} from 'react-intl'; +import SettingsGroup from './settings_group.jsx'; +import TextSetting from './text_setting.jsx'; +import BooleanSetting from './boolean_setting.jsx'; +import Setting from './setting.jsx'; +import * as Utils from 'utils/utils.jsx'; +import Constants from 'utils/constants.jsx'; +import GeneratedSetting from './generated_setting.jsx'; + +export default class PasswordSettings extends AdminSettings { + constructor(props) { + super(props); + + this.getConfigFromState = this.getConfigFromState.bind(this); + + this.renderSettings = this.renderSettings.bind(this); + + this.getSampleErrorMsg = this.getSampleErrorMsg.bind(this); + + this.state = Object.assign(this.state, { + passwordMinimumLength: props.config.PasswordSettings.MinimumLength, + passwordLowercase: props.config.PasswordSettings.Lowercase, + passwordNumber: props.config.PasswordSettings.Number, + passwordUppercase: props.config.PasswordSettings.Uppercase, + passwordSymbol: props.config.PasswordSettings.Symbol, + maximumLoginAttempts: props.config.ServiceSettings.MaximumLoginAttempts, + enableMultifactorAuthentication: props.config.ServiceSettings.EnableMultifactorAuthentication, + passwordResetSalt: props.config.EmailSettings.PasswordResetSalt + }); + + // Update sample message from config settings + let sampleErrorMsgId = 'user.settings.security.passwordError'; + if (props.config.PasswordSettings.Lowercase) { + sampleErrorMsgId = sampleErrorMsgId + 'Lowercase'; + } + if (props.config.PasswordSettings.Uppercase) { + sampleErrorMsgId = sampleErrorMsgId + 'Uppercase'; + } + if (props.config.PasswordSettings.Number) { + sampleErrorMsgId = sampleErrorMsgId + 'Number'; + } + if (props.config.PasswordSettings.Symbol) { + sampleErrorMsgId = sampleErrorMsgId + 'Symbol'; + } + this.sampleErrorMsg = ( + <FormattedMessage + id={sampleErrorMsgId} + default='Your password must be at least {min} characters.' + values={{ + min: props.config.PasswordSettings.MinimumLength + }} + /> + ); + } + + componentWillUpdate() { + this.sampleErrorMsg = this.getSampleErrorMsg(); + } + + getConfigFromState(config) { + if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.PasswordRequirements === 'true') { + config.PasswordSettings.MinimumLength = this.parseIntNonZero(this.state.passwordMinimumLength, 10); + config.PasswordSettings.Lowercase = this.refs.lowercase.checked; + config.PasswordSettings.Uppercase = this.refs.uppercase.checked; + config.PasswordSettings.Number = this.refs.number.checked; + config.PasswordSettings.Symbol = this.refs.symbol.checked; + } + + config.ServiceSettings.MaximumLoginAttempts = this.parseIntNonZero(this.state.maximumLoginAttempts); + config.EmailSettings.PasswordResetSalt = this.state.passwordResetSalt; + if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.MFA === 'true') { + config.ServiceSettings.EnableMultifactorAuthentication = this.state.enableMultifactorAuthentication; + } + + return config; + } + + getSampleErrorMsg() { + if (this.props.config.PasswordSettings.MinimumLength > Constants.MAX_PASSWORD_LENGTH || this.props.config.PasswordSettings.MinimumLength < Constants.MIN_PASSWORD_LENGTH) { + return ( + <FormattedMessage + id='user.settings.security.passwordMinLength' + default='Invalid minimum length, cannot show preview.' + /> + ); + } + + let sampleErrorMsgId = 'user.settings.security.passwordError'; + if (this.refs.lowercase.checked) { + sampleErrorMsgId = sampleErrorMsgId + 'Lowercase'; + } + if (this.refs.uppercase.checked) { + sampleErrorMsgId = sampleErrorMsgId + 'Uppercase'; + } + if (this.refs.number.checked) { + sampleErrorMsgId = sampleErrorMsgId + 'Number'; + } + if (this.refs.symbol.checked) { + sampleErrorMsgId = sampleErrorMsgId + 'Symbol'; + } + return ( + <FormattedMessage + id={sampleErrorMsgId} + default='Your password must be at least {min} characters.' + values={{ + min: this.props.config.PasswordSettings.MinimumLength + }} + /> + ); + } + + renderTitle() { + return ( + <h3> + <FormattedMessage + id='admin.security.password' + defaultMessage='Password' + /> + </h3> + ); + } + + renderSettings() { + let mfaSetting = null; + if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.MFA === 'true') { + mfaSetting = ( + <BooleanSetting + id='enableMultifactorAuthentication' + label={ + <FormattedMessage + id='admin.service.mfaTitle' + defaultMessage='Enable Multi-factor Authentication:' + /> + } + helpText={ + <FormattedMessage + id='admin.service.mfaDesc' + defaultMessage='When true, users will be given the option to add multi-factor authentication to their account. They will need a smartphone and an authenticator app such as Google Authenticator.' + /> + } + value={this.state.enableMultifactorAuthentication} + onChange={this.handleChange} + /> + ); + } + + let passwordSettings = null; + if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.PasswordRequirements === 'true') { + passwordSettings = ( + <div> + <TextSetting + id='passwordMinimumLength' + label={ + <FormattedMessage + id='admin.password.minimumLength' + defaultMessage='Minimum Password Length:' + /> + } + helpText={ + <FormattedMessage + id='admin.password.minimumLengthDescription' + defaultMessage='Minimum number of characters required for a valid password. Must be a whole number greater than or equal to {min} and less than or equal to {max}.' + values={{ + min: Constants.MIN_PASSWORD_LENGTH, + max: Constants.MAX_PASSWORD_LENGTH + }} + /> + } + value={this.state.passwordMinimumLength} + onChange={this.handleChange} + /> + <Setting + label={ + <FormattedMessage + id='passwordRequirements' + defaultMessage='Password Requirements:' + /> + } + > + <div> + <label className='checkbox-inline'> + <input + type='checkbox' + ref='lowercase' + defaultChecked={this.state.passwordLowercase} + name='admin.password.lowercase' + onChange={this.handleChange} + /> + <FormattedMessage + id='admin.password.lowercase' + defaultMessage='At least one lowercase letter' + /> + </label> + </div> + <div> + <label className='checkbox-inline'> + <input + type='checkbox' + ref='uppercase' + defaultChecked={this.state.passwordUppercase} + name='admin.password.uppercase' + onChange={this.handleChange} + /> + <FormattedMessage + id='admin.password.uppercase' + defaultMessage='At least one uppercase letter' + /> + </label> + </div> + <div> + <label className='checkbox-inline'> + <input + type='checkbox' + ref='number' + defaultChecked={this.state.passwordNumber} + name='admin.password.number' + onChange={this.handleChange} + /> + <FormattedMessage + id='admin.password.number' + defaultMessage='At least one number' + /> + </label> + </div> + <div> + <label className='checkbox-inline'> + <input + type='checkbox' + ref='symbol' + defaultChecked={this.state.passwordSymbol} + name='admin.password.symbol' + onChange={this.handleChange} + /> + <FormattedMessage + id='admin.password.symbol' + defaultMessage='At least one symbol (e.g. "~!@#$%^&*()")' + /> + </label> + </div> + <div> + <br/> + <label> + <FormattedMessage + id='admin.password.preview' + defaultMessage='Error message preview:' + /> + </label> + <br/> + {this.sampleErrorMsg} + </div> + </Setting> + </div> + ); + } + + return ( + <SettingsGroup> + {passwordSettings} + <GeneratedSetting + id='passwordResetSalt' + label={ + <FormattedMessage + id='admin.email.passwordSaltTitle' + defaultMessage='Password Reset Salt:' + /> + } + helpText={ + <FormattedMessage + id='admin.email.passwordSaltDescription' + defaultMessage='32-character salt added to signing of password reset emails. Randomly generated on install. Click "Regenerate" to create new salt.' + /> + } + value={this.state.passwordResetSalt} + onChange={this.handleChange} + disabled={this.state.sendEmailNotifications} + disabledText={ + <FormattedMessage + id='admin.security.passwordResetSalt.disabled' + defaultMessage='Password reset salt cannot be changed while sending emails is disabled.' + /> + } + /> + <TextSetting + id='maximumLoginAttempts' + label={ + <FormattedMessage + id='admin.service.attemptTitle' + defaultMessage='Maximum Login Attempts:' + /> + } + placeholder={Utils.localizeMessage('admin.service.attemptExample', 'Ex "10"')} + helpText={ + <FormattedMessage + id='admin.service.attemptDescription' + defaultMessage='Login attempts allowed before user is locked out and required to reset password via email.' + /> + } + value={this.state.maximumLoginAttempts} + onChange={this.handleChange} + /> + {mfaSetting} + </SettingsGroup> + ); + } +}
\ No newline at end of file diff --git a/webapp/components/claim/components/ldap_to_email.jsx b/webapp/components/claim/components/ldap_to_email.jsx index 002ff89bd..c3bbab23c 100644 --- a/webapp/components/claim/components/ldap_to_email.jsx +++ b/webapp/components/claim/components/ldap_to_email.jsx @@ -46,9 +46,17 @@ export default class LDAPToEmail extends React.Component { return; } + const passwordErr = Utils.isValidPassword(password); + if (passwordErr !== '') { + this.setState({ + passwordError: passwordErr + }); + return; + } + const confirmPassword = ReactDOM.findDOMNode(this.refs.passwordconfirm).value; if (!confirmPassword || password !== confirmPassword) { - state.confirmError = Utils.localizeMessage('claim.ldap_to_email.pwdNotMatch', 'Passwords do not match.'); + state.error = Utils.localizeMessage('claim.ldap_to_email.pwdNotMatch', 'Passwords do not match.'); this.setState(state); return; } diff --git a/webapp/components/claim/components/oauth_to_email.jsx b/webapp/components/claim/components/oauth_to_email.jsx index 6a0f6431b..79392849f 100644 --- a/webapp/components/claim/components/oauth_to_email.jsx +++ b/webapp/components/claim/components/oauth_to_email.jsx @@ -18,6 +18,7 @@ export default class OAuthToEmail extends React.Component { this.state = {}; } + submit(e) { e.preventDefault(); const state = {}; @@ -29,6 +30,14 @@ export default class OAuthToEmail extends React.Component { return; } + const passwordErr = Utils.isValidPassword(password); + if (passwordErr !== '') { + this.setState({ + error: passwordErr + }); + return; + } + const confirmPassword = ReactDOM.findDOMNode(this.refs.passwordconfirm).value; if (!confirmPassword || password !== confirmPassword) { state.error = Utils.localizeMessage('claim.oauth_to_email.pwdNotMatch', 'Password do not match.'); diff --git a/webapp/components/signup_user_complete.jsx b/webapp/components/signup_user_complete.jsx index f342dc792..629fb2f78 100644 --- a/webapp/components/signup_user_complete.jsx +++ b/webapp/components/signup_user_complete.jsx @@ -317,21 +317,14 @@ export default class SignupUserComplete extends React.Component { } const providedPassword = ReactDOM.findDOMNode(this.refs.password).value; - if (!providedPassword || providedPassword.length < Constants.MIN_PASSWORD_LENGTH) { + const pwdError = Utils.isValidPassword(providedPassword); + if (pwdError != null) { this.setState({ nameError: '', emailError: '', - passwordError: ( - <FormattedMessage - id='signup_user_completed.passwordLength' - values={{ - min: Constants.MIN_PASSWORD_LENGTH - }} - /> - ), + passwordError: pwdError, serverError: '' }); - return; } this.setState({ diff --git a/webapp/components/user_settings/user_settings_security.jsx b/webapp/components/user_settings/user_settings_security.jsx index ec84f4cb5..976e65981 100644 --- a/webapp/components/user_settings/user_settings_security.jsx +++ b/webapp/components/user_settings/user_settings_security.jsx @@ -26,7 +26,7 @@ const holders = defineMessages({ }, passwordLengthError: { id: 'user.settings.security.passwordLengthError', - defaultMessage: 'New passwords must be at least {chars} characters' + defaultMessage: 'New passwords must be at least {min} characters and at most {max} characters.' }, passwordMatchError: { id: 'user.settings.security.passwordMatchError', @@ -90,8 +90,12 @@ class SecurityTab extends React.Component { return; } - if (newPassword.length < Constants.MIN_PASSWORD_LENGTH) { - this.setState({passwordError: formatMessage(holders.passwordLengthError, {chars: Constants.MIN_PASSWORD_LENGTH}), serverError: ''}); + const passwordErr = Utils.isValidPassword(newPassword); + if (passwordErr !== '') { + this.setState({ + passwordError: passwordErr, + serverError: '' + }); return; } |