summaryrefslogtreecommitdiffstats
path: root/webapp/components
diff options
context:
space:
mode:
authorDavid Lu <david.lu@hotmail.com>2016-07-06 18:54:54 -0400
committerCorey Hulen <corey@hulen.com>2016-07-06 14:54:54 -0800
commit683f7133190aa350cdd1ea2608c90fe5f47b35cd (patch)
tree3f1bcc19d3bc1a7dedd407c266ea63cdda5ed9c9 /webapp/components
parent0c3c52b8d3a3503c35481a287ba27f626749503a (diff)
downloadchat-683f7133190aa350cdd1ea2608c90fe5f47b35cd.tar.gz
chat-683f7133190aa350cdd1ea2608c90fe5f47b35cd.tar.bz2
chat-683f7133190aa350cdd1ea2608c90fe5f47b35cd.zip
PLT-1465 Added password requirements (#3489)
* Added password requirements * added tweaks * fixed error code * removed http.StatusNotAcceptable
Diffstat (limited to 'webapp/components')
-rw-r--r--webapp/components/admin_console/admin_sidebar.jsx6
-rw-r--r--webapp/components/admin_console/login_settings.jsx125
-rw-r--r--webapp/components/admin_console/password_settings.jsx310
-rw-r--r--webapp/components/claim/components/ldap_to_email.jsx10
-rw-r--r--webapp/components/claim/components/oauth_to_email.jsx9
-rw-r--r--webapp/components/signup_user_complete.jsx13
-rw-r--r--webapp/components/user_settings/user_settings_security.jsx10
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;
}