summaryrefslogtreecommitdiffstats
path: root/webapp
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
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')
-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
-rw-r--r--webapp/i18n/en.json39
-rw-r--r--webapp/routes/route_admin_console.jsx6
-rw-r--r--webapp/utils/constants.jsx2
-rw-r--r--webapp/utils/utils.jsx68
11 files changed, 449 insertions, 149 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;
}
diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json
index 322c9ccad..0547af506 100644
--- a/webapp/i18n/en.json
+++ b/webapp/i18n/en.json
@@ -407,6 +407,15 @@
"admin.notifications.email": "Email",
"admin.notifications.push": "Mobile Push",
"admin.notifications.title": "Notification Settings",
+ "admin.password.lowercase": "At least one lowercase letter",
+ "admin.password.minimumLength": "Minimum Password Length:",
+ "admin.password.minimumLengthDescription": "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}.",
+ "admin.password.number": "At least one number",
+ "admin.password.preview": "Error message preview",
+ "admin.password.requirements": "Password Requirements:",
+ "admin.password.requirementsDescription": "Character types required in a valid password.",
+ "admin.password.symbol": "At least one symbol (e.g. \"~!@#$%^&*()\")",
+ "admin.password.uppercase": "At least one uppercase letter",
"admin.privacy.showEmailDescription": "When false, hides email address of users from other users in the user interface, including team owners and team administrators. Used when system is set up for managing teams where some users choose to keep their contact information private.",
"admin.privacy.showEmailTitle": "Show Email Address: ",
"admin.privacy.showFullNameDescription": "When false, hides full name of users from other users, including team owners and team administrators. Username is shown in place of full name.",
@@ -504,7 +513,16 @@
"admin.select_team.close": "Close",
"admin.select_team.select": "Select",
"admin.select_team.selectTeam": "Select Team",
- "admin.service.attemptDescription": "Login attempts allowed before user is locked out and required to reset password via email.",
+ "admin.security.password": "Password",
+ "admin.security.login": "Login",
+ "admin.security.connection": "Connections",
+ "admin.security.public_links": "Public Links",
+ "admin.security.session": "Sessions",
+ "admin.security.signup": "Signup",
+ "admin.security.requireEmailVerification.disabled": "Email verification cannot be changed while sending emails is disabled.",
+ "admin.security.passwordResetSalt.disabled": "Password reset salt cannot be changed while sending emails is disabled.",
+ "admin.security.inviteSalt.disabled": "Invite salt cannot be changed while sending emails is disabled.",
+ "admin.service.attemptDescription": "Number of login attempts allowed before a user is locked out and required to reset their password via email.",
"admin.service.attemptExample": "Ex \"10\"",
"admin.service.attemptTitle": "Maximum Login Attempts:",
"admin.service.cmdsDesc": "When true, user created slash commands will be allowed.",
@@ -575,6 +593,7 @@
"admin.sidebar.localization": "Localization",
"admin.sidebar.logging": "Logging",
"admin.sidebar.login": "Login",
+ "admin.sidebar.password": "Password",
"admin.sidebar.logs": "Logs",
"admin.sidebar.notifications": "Notifications",
"admin.sidebar.other": "OTHER",
@@ -1632,7 +1651,23 @@
"user.settings.security.password": "Password",
"user.settings.security.passwordGitlabCantUpdate": "Login occurs through GitLab. Password cannot be updated.",
"user.settings.security.passwordLdapCantUpdate": "Login occurs through LDAP. Password cannot be updated.",
- "user.settings.security.passwordLengthError": "New passwords must be at least {chars} characters",
+ "user.settings.security.passwordError": "Your password must contain at least {min} characters.",
+ "user.settings.security.passwordErrorLowercase": "Your password must contain at least {min} characters made up of at least one lowercase letter.",
+ "user.settings.security.passwordErrorLowercaseNumber": "Your password must contain at least {min} characters made up of at least one lowercase letter and at least one number.",
+ "user.settings.security.passwordErrorLowercaseUppercase": "Your password must contain at least {min} characters made up of at least one lowercase letter and at least one uppercase letter.",
+ "user.settings.security.passwordErrorLowercaseSymbol": "Your password must contain at least {min} characters made up of at least one lowercase letter and at least one symbol (e.g. \"~!@#$%^&*()\").",
+ "user.settings.security.passwordErrorLowercaseUppercaseNumber": "Your password must contain at least {min} characters made up of at least one lowercase letter, at least one uppercase letter, and at least one number.",
+ "user.settings.security.passwordErrorLowercaseNumberSymbol": "Your password must contain at least {min} characters made up of at least one lowercase letter, at least one number, and at least one symbol (e.g. \"~!@#$%^&*()\").",
+ "user.settings.security.passwordErrorLowercaseUppercaseSymbol": "Your password must contain at least {min} characters made up of at least one lowercase letter, at least one uppercase letter, and at least one symbol (e.g. \"~!@#$%^&*()\").",
+ "user.settings.security.passwordErrorLowercaseUppercaseNumberSymbol": "Your password must contain at least {min} characters made up of at least one lowercase letter, at least one uppercase letter, at least one number, and at least one symbol (e.g. \"~!@#$%^&*()\").",
+ "user.settings.security.passwordErrorUppercase": "Your password must contain at least {min} characters made up of at least one uppercase letter.",
+ "user.settings.security.passwordErrorUppercaseNumber": "Your password must contain at least {min} characters made up of at least one uppercase letter and at least one number.",
+ "user.settings.security.passwordErrorUppercaseSymbol": "Your password must contain at least {min} characters made up of at least one uppercase letter and at least one symbol (e.g. \"~!@#$%^&*()\").",
+ "user.settings.security.passwordErrorUppercaseNumberSymbol": "Your password must contain at least {min} characters made up of at least one uppercase letter, at least one number, and at least one symbol (e.g. \"~!@#$%^&*()\").",
+ "user.settings.security.passwordErrorNumber": "Your password must contain at least {min} characters made up of at least one number.",
+ "user.settings.security.passwordErrorNumberSymbol": "Your password must contain at least {min} characters made up of at least one number and at least one symbol (e.g. \"~!@#$%^&*()\").",
+ "user.settings.security.passwordErrorSymbol": "Your password must contain at least {min} characters made up of at least one symbol (e.g. \"~!@#$%^&*()\").",
+ "user.settings.security.passwordMinLength": "Invalid minimum length, cannot show preview.",
"user.settings.security.passwordMatchError": "The new passwords you entered do not match",
"user.settings.security.retypePassword": "Retype New Password",
"user.settings.security.saml": "SAML",
diff --git a/webapp/routes/route_admin_console.jsx b/webapp/routes/route_admin_console.jsx
index 1f5e69c2d..9fde948c2 100644
--- a/webapp/routes/route_admin_console.jsx
+++ b/webapp/routes/route_admin_console.jsx
@@ -17,7 +17,7 @@ import GitLabSettings from 'components/admin_console/gitlab_settings.jsx';
import LdapSettings from 'components/admin_console/ldap_settings.jsx';
import SamlSettings from 'components/admin_console/saml_settings.jsx';
import SignupSettings from 'components/admin_console/signup_settings.jsx';
-import LoginSettings from 'components/admin_console/login_settings.jsx';
+import PasswordSettings from 'components/admin_console/password_settings.jsx';
import PublicLinkSettings from 'components/admin_console/public_link_settings.jsx';
import SessionSettings from 'components/admin_console/session_settings.jsx';
import ConnectionSettings from 'components/admin_console/connection_settings.jsx';
@@ -103,8 +103,8 @@ export default (
component={SignupSettings}
/>
<Route
- path='login'
- component={LoginSettings}
+ path='password'
+ component={PasswordSettings}
/>
<Route
path='public_links'
diff --git a/webapp/utils/constants.jsx b/webapp/utils/constants.jsx
index f0cea9e52..52fb23d51 100644
--- a/webapp/utils/constants.jsx
+++ b/webapp/utils/constants.jsx
@@ -758,7 +758,7 @@ export default {
MAX_USERNAME_LENGTH: 22,
MAX_NICKNAME_LENGTH: 22,
MIN_PASSWORD_LENGTH: 5,
- MAX_PASSWORD_LENGTH: 50,
+ MAX_PASSWORD_LENGTH: 64,
MIN_TRIGGER_LENGTH: 1,
MAX_TRIGGER_LENGTH: 128,
TIME_SINCE_UPDATE_INTERVAL: 30000,
diff --git a/webapp/utils/utils.jsx b/webapp/utils/utils.jsx
index 1b4b504f2..5f88f5b73 100644
--- a/webapp/utils/utils.jsx
+++ b/webapp/utils/utils.jsx
@@ -14,10 +14,13 @@ import * as AsyncClient from './async_client.jsx';
import Client from './web_client.jsx';
import {browserHistory} from 'react-router/es6';
+import {FormattedMessage} from 'react-intl';
import icon50 from 'images/icon50x50.png';
import bing from 'images/bing.mp3';
+import React from 'react';
+
export function isEmail(email) {
// writing a regex to match all valid email addresses is really, really hard (see http://stackoverflow.com/a/201378)
// so we just do a simple check and rely on a verification email to tell if it's a real address
@@ -1366,4 +1369,67 @@ export function canCreateCustomEmoji(user) {
}
return true;
-} \ No newline at end of file
+}
+
+export function isValidPassword(password) {
+ let errorMsg = '';
+ let errorId = 'user.settings.security.passwordError';
+ let error = false;
+ let minimumLength = Constants.MIN_PASSWORD_LENGTH;
+
+ if (global.window.mm_config.BuildEnterpriseReady === 'true' && global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.PasswordRequirements === 'true') {
+ if (password.length < parseInt(global.window.mm_config.PasswordMinimumLength, 10) || password.length > Constants.MAX_PASSWORD_LENGTH) {
+ error = true;
+ }
+
+ if (global.window.mm_config.PasswordRequireLowercase === 'true') {
+ if (!password.match(/[a-z]/)) {
+ error = true;
+ }
+
+ errorId = errorId + 'Lowercase';
+ }
+
+ if (global.window.mm_config.PasswordRequireUppercase === 'true') {
+ if (!password.match(/[0-9]/)) {
+ error = true;
+ }
+
+ errorId = errorId + 'Uppercase';
+ }
+
+ if (global.window.mm_config.PasswordRequireNumber === 'true') {
+ if (!password.match(/[A-Z]/)) {
+ error = true;
+ }
+
+ errorId = errorId + 'Number';
+ }
+
+ if (global.window.mm_config.PasswordRequireSymbol === 'true') {
+ if (!password.match(/[ !"\\#$%&'()*+,-./:;<=>?@[\]^_`|~]/)) {
+ error = true;
+ }
+
+ errorId = errorId + 'Symbol';
+ }
+
+ minimumLength = global.window.mm_config.PasswordMinimumLength;
+ } else if (password.length < Constants.MIN_PASSWORD_LENGTH) {
+ error = true;
+ }
+
+ if (error) {
+ errorMsg = (
+ <FormattedMessage
+ id={errorId}
+ default='Your password must be at least {min} characters.'
+ values={{
+ min: minimumLength
+ }}
+ />
+ );
+ }
+
+ return errorMsg;
+}