Move password utilities to their own file (#27366)
* Move password utilities to their own file * Move passwordErrors to utils/password * Update file path in test * Actually commit the whole thing
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
cca0db7e32
Коммит
7d06ac8812
@@ -9,6 +9,7 @@ import type {AdminConfig} from '@mattermost/types/config';
|
|||||||
import type {DeepPartial} from '@mattermost/types/utilities';
|
import type {DeepPartial} from '@mattermost/types/utilities';
|
||||||
|
|
||||||
import Constants from 'utils/constants';
|
import Constants from 'utils/constants';
|
||||||
|
import {passwordErrors} from 'utils/password';
|
||||||
|
|
||||||
import AdminSettings from './admin_settings';
|
import AdminSettings from './admin_settings';
|
||||||
import type {BaseProps, BaseState} from './admin_settings';
|
import type {BaseProps, BaseState} from './admin_settings';
|
||||||
@@ -63,25 +64,6 @@ export const searchableStrings: Array<string|MessageDescriptor|[MessageDescripto
|
|||||||
messages.attemptDescription,
|
messages.attemptDescription,
|
||||||
];
|
];
|
||||||
|
|
||||||
const passwordErrors = defineMessages({
|
|
||||||
passwordError: {id: 'user.settings.security.passwordError', defaultMessage: 'Must be {min}-{max} characters long.'},
|
|
||||||
passwordErrorLowercase: {id: 'user.settings.security.passwordErrorLowercase', defaultMessage: 'Must be {min}-{max} characters long and include lowercase letters.'},
|
|
||||||
passwordErrorLowercaseNumber: {id: 'user.settings.security.passwordErrorLowercaseNumber', defaultMessage: 'Must be {min}-{max} characters long and include lowercase letters and numbers.'},
|
|
||||||
passwordErrorLowercaseNumberSymbol: {id: 'user.settings.security.passwordErrorLowercaseNumberSymbol', defaultMessage: 'Must be {min}-{max} characters long and include lowercase letters, numbers, and special characters.'},
|
|
||||||
passwordErrorLowercaseSymbol: {id: 'user.settings.security.passwordErrorLowercaseSymbol', defaultMessage: 'Must be {min}-{max} characters long and include lowercase letters and special characters.'},
|
|
||||||
passwordErrorLowercaseUppercase: {id: 'user.settings.security.passwordErrorLowercaseUppercase', defaultMessage: 'Must be {min}-{max} characters long and include both lowercase and uppercase letters.'},
|
|
||||||
passwordErrorLowercaseUppercaseNumber: {id: 'user.settings.security.passwordErrorLowercaseUppercaseNumber', defaultMessage: 'Must be {min}-{max} characters long and include both lowercase and uppercase letters, and numbers.'},
|
|
||||||
passwordErrorLowercaseUppercaseNumberSymbol: {id: 'user.settings.security.passwordErrorLowercaseUppercaseNumberSymbol', defaultMessage: 'Must be {min}-{max} characters long and include both lowercase and uppercase letters, numbers, and special characters.'},
|
|
||||||
passwordErrorLowercaseUppercaseSymbol: {id: 'user.settings.security.passwordErrorLowercaseUppercaseSymbol', defaultMessage: 'Must be {min}-{max} characters long and include both lowercase and uppercase letters, and special characters.'},
|
|
||||||
passwordErrorNumber: {id: 'user.settings.security.passwordErrorNumber', defaultMessage: 'Must be {min}-{max} characters long and include numbers.'},
|
|
||||||
passwordErrorNumberSymbol: {id: 'user.settings.security.passwordErrorNumberSymbol', defaultMessage: 'Must be {min}-{max} characters long and include numbers and special characters.'},
|
|
||||||
passwordErrorSymbol: {id: 'user.settings.security.passwordErrorSymbol', defaultMessage: 'Must be {min}-{max} characters long and include special characters.'},
|
|
||||||
passwordErrorUppercase: {id: 'user.settings.security.passwordErrorUppercase', defaultMessage: 'Must be {min}-{max} characters long and include uppercase letters.'},
|
|
||||||
passwordErrorUppercaseNumber: {id: 'user.settings.security.passwordErrorUppercaseNumber', defaultMessage: 'Must be {min}-{max} characters long and include uppercase letters, and numbers.'},
|
|
||||||
passwordErrorUppercaseNumberSymbol: {id: 'user.settings.security.passwordErrorUppercaseNumberSymbol', defaultMessage: 'Must be {min}-{max} characters long and include uppercase letters, numbers, and special characters.'},
|
|
||||||
passwordErrorUppercaseSymbol: {id: 'user.settings.security.passwordErrorUppercaseSymbol', defaultMessage: 'Must be {min}-{max} characters long and include uppercase letters, and special characters.'},
|
|
||||||
});
|
|
||||||
|
|
||||||
function getPasswordErrorsMessage(lowercase?: boolean, uppercase?: boolean, number?: boolean, symbol?: boolean) {
|
function getPasswordErrorsMessage(lowercase?: boolean, uppercase?: boolean, number?: boolean, symbol?: boolean) {
|
||||||
type KeyType = keyof typeof passwordErrors;
|
type KeyType = keyof typeof passwordErrors;
|
||||||
|
|
||||||
|
|||||||
@@ -6,21 +6,17 @@ import {bindActionCreators} from 'redux';
|
|||||||
import type {Dispatch} from 'redux';
|
import type {Dispatch} from 'redux';
|
||||||
|
|
||||||
import {updateUserPassword} from 'mattermost-redux/actions/users';
|
import {updateUserPassword} from 'mattermost-redux/actions/users';
|
||||||
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
import {getPasswordConfig} from 'mattermost-redux/selectors/entities/general';
|
||||||
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
||||||
|
|
||||||
import {getPasswordConfig} from 'utils/utils';
|
|
||||||
|
|
||||||
import type {GlobalState} from 'types/store';
|
import type {GlobalState} from 'types/store';
|
||||||
|
|
||||||
import ResetPasswordModal from './reset_password_modal';
|
import ResetPasswordModal from './reset_password_modal';
|
||||||
|
|
||||||
function mapStateToProps(state: GlobalState) {
|
function mapStateToProps(state: GlobalState) {
|
||||||
const config = getConfig(state);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentUserId: getCurrentUserId(state),
|
currentUserId: getCurrentUserId(state),
|
||||||
passwordConfig: getPasswordConfig(config),
|
passwordConfig: getPasswordConfig(state),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import type {UserProfile} from '@mattermost/types/users';
|
|||||||
|
|
||||||
import type {ActionResult} from 'mattermost-redux/types/actions';
|
import type {ActionResult} from 'mattermost-redux/types/actions';
|
||||||
|
|
||||||
import * as Utils from 'utils/utils';
|
import {isValidPassword} from 'utils/password';
|
||||||
|
|
||||||
interface PasswordConfig {
|
interface PasswordConfig {
|
||||||
minimumLength: number;
|
minimumLength: number;
|
||||||
@@ -83,7 +83,7 @@ export default class ResetPasswordModal extends React.PureComponent<Props, State
|
|||||||
|
|
||||||
const password = (this.passwordRef.current as HTMLInputElement).value;
|
const password = (this.passwordRef.current as HTMLInputElement).value;
|
||||||
|
|
||||||
const {valid, error} = Utils.isValidPassword(password, this.props.passwordConfig);
|
const {valid, error} = isValidPassword(password, this.props.passwordConfig);
|
||||||
if (!valid && error) {
|
if (!valid && error) {
|
||||||
this.setState({serverErrorNewPass: error});
|
this.setState({serverErrorNewPass: error});
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ import type {ActionResult} from 'mattermost-redux/types/actions';
|
|||||||
import LoginMfa from 'components/login/login_mfa';
|
import LoginMfa from 'components/login/login_mfa';
|
||||||
|
|
||||||
import {ClaimErrors} from 'utils/constants';
|
import {ClaimErrors} from 'utils/constants';
|
||||||
import {isValidPassword, localizeMessage} from 'utils/utils';
|
import {isValidPassword} from 'utils/password';
|
||||||
|
import {localizeMessage} from 'utils/utils';
|
||||||
|
|
||||||
import type {SubmitOptions} from './email_to_ldap';
|
import type {SubmitOptions} from './email_to_ldap';
|
||||||
import ErrorLabel from './error_label';
|
import ErrorLabel from './error_label';
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ import {FormattedMessage, useIntl} from 'react-intl';
|
|||||||
|
|
||||||
import type {AuthChangeResponse} from '@mattermost/types/users';
|
import type {AuthChangeResponse} from '@mattermost/types/users';
|
||||||
|
|
||||||
|
import type {PasswordConfig} from 'mattermost-redux/selectors/entities/general';
|
||||||
|
|
||||||
import {oauthToEmail} from 'actions/admin_actions.jsx';
|
import {oauthToEmail} from 'actions/admin_actions.jsx';
|
||||||
|
|
||||||
import Constants from 'utils/constants';
|
import Constants from 'utils/constants';
|
||||||
import {isValidPassword, localizeMessage, toTitleCase} from 'utils/utils';
|
import {isValidPassword} from 'utils/password';
|
||||||
import type {getPasswordConfig} from 'utils/utils';
|
import {localizeMessage, toTitleCase} from 'utils/utils';
|
||||||
|
|
||||||
import ErrorLabel from './error_label';
|
import ErrorLabel from './error_label';
|
||||||
|
|
||||||
@@ -19,7 +21,7 @@ type Props = {
|
|||||||
currentType: string | null;
|
currentType: string | null;
|
||||||
email: string | null;
|
email: string | null;
|
||||||
siteName?: string;
|
siteName?: string;
|
||||||
passwordConfig?: ReturnType<typeof getPasswordConfig>;
|
passwordConfig?: PasswordConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OAuthToEmail = (props: Props) => {
|
const OAuthToEmail = (props: Props) => {
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ import type {Dispatch} from 'redux';
|
|||||||
import type {GlobalState} from '@mattermost/types/store';
|
import type {GlobalState} from '@mattermost/types/store';
|
||||||
|
|
||||||
import {switchLdapToEmail} from 'mattermost-redux/actions/users';
|
import {switchLdapToEmail} from 'mattermost-redux/actions/users';
|
||||||
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
import {getConfig, getPasswordConfig} from 'mattermost-redux/selectors/entities/general';
|
||||||
|
|
||||||
import {getPasswordConfig} from 'utils/utils';
|
|
||||||
|
|
||||||
import ClaimController from './claim_controller';
|
import ClaimController from './claim_controller';
|
||||||
|
|
||||||
@@ -22,7 +20,7 @@ function mapStateToProps(state: GlobalState) {
|
|||||||
return {
|
return {
|
||||||
siteName,
|
siteName,
|
||||||
ldapLoginFieldName,
|
ldapLoginFieldName,
|
||||||
passwordConfig: getPasswordConfig(config),
|
passwordConfig: getPasswordConfig(state),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import type {UserProfile} from '@mattermost/types/users';
|
|||||||
import {getTeamInviteInfo} from 'mattermost-redux/actions/teams';
|
import {getTeamInviteInfo} from 'mattermost-redux/actions/teams';
|
||||||
import {createUser, loadMe} from 'mattermost-redux/actions/users';
|
import {createUser, loadMe} from 'mattermost-redux/actions/users';
|
||||||
import {Client4} from 'mattermost-redux/client';
|
import {Client4} from 'mattermost-redux/client';
|
||||||
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
|
import {getConfig, getLicense, getPasswordConfig} from 'mattermost-redux/selectors/entities/general';
|
||||||
import {getIsOnboardingFlowEnabled} from 'mattermost-redux/selectors/entities/preferences';
|
import {getIsOnboardingFlowEnabled} from 'mattermost-redux/selectors/entities/preferences';
|
||||||
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
||||||
import {isEmail} from 'mattermost-redux/utils/helpers';
|
import {isEmail} from 'mattermost-redux/utils/helpers';
|
||||||
@@ -54,8 +54,9 @@ import type {CustomMessageInputType} from 'components/widgets/inputs/input/input
|
|||||||
import PasswordInput from 'components/widgets/inputs/password_input/password_input';
|
import PasswordInput from 'components/widgets/inputs/password_input/password_input';
|
||||||
|
|
||||||
import {Constants, HostedCustomerLinks, ItemStatus, ValidationErrors} from 'utils/constants';
|
import {Constants, HostedCustomerLinks, ItemStatus, ValidationErrors} from 'utils/constants';
|
||||||
|
import {isValidPassword} from 'utils/password';
|
||||||
import {isDesktopApp} from 'utils/user_agent';
|
import {isDesktopApp} from 'utils/user_agent';
|
||||||
import {isValidUsername, isValidPassword, getPasswordConfig, getRoleFromTrackFlow, getMediumFromTrackFlow} from 'utils/utils';
|
import {isValidUsername, getRoleFromTrackFlow, getMediumFromTrackFlow} from 'utils/utils';
|
||||||
|
|
||||||
import type {GlobalState} from 'types/store';
|
import type {GlobalState} from 'types/store';
|
||||||
|
|
||||||
@@ -148,7 +149,8 @@ const Signup = ({onCustomizeHeader}: SignupProps) => {
|
|||||||
const enableExternalSignup = enableSignUpWithGitLab || enableSignUpWithOffice365 || enableSignUpWithGoogle || enableSignUpWithOpenId || enableLDAP || enableSAML;
|
const enableExternalSignup = enableSignUpWithGitLab || enableSignUpWithOffice365 || enableSignUpWithGoogle || enableSignUpWithOpenId || enableLDAP || enableSAML;
|
||||||
const hasError = Boolean(emailError || nameError || passwordError || serverError || alertBanner);
|
const hasError = Boolean(emailError || nameError || passwordError || serverError || alertBanner);
|
||||||
const canSubmit = Boolean(email && name && password) && !hasError && !loading;
|
const canSubmit = Boolean(email && name && password) && !hasError && !loading;
|
||||||
const {error: passwordInfo} = isValidPassword('', getPasswordConfig(config), intl);
|
const passwordConfig = useSelector(getPasswordConfig);
|
||||||
|
const {error: passwordInfo} = isValidPassword('', passwordConfig, intl);
|
||||||
|
|
||||||
const [desktopLoginLink, setDesktopLoginLink] = useState('');
|
const [desktopLoginLink, setDesktopLoginLink] = useState('');
|
||||||
|
|
||||||
@@ -553,7 +555,7 @@ const Signup = ({onCustomizeHeader}: SignupProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const providedPassword = passwordInput.current?.value ?? '';
|
const providedPassword = passwordInput.current?.value ?? '';
|
||||||
const {error, telemetryErrorIds} = isValidPassword(providedPassword, getPasswordConfig(config), intl);
|
const {error, telemetryErrorIds} = isValidPassword(providedPassword, passwordConfig, intl);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
setPasswordError(error as string);
|
setPasswordError(error as string);
|
||||||
|
|||||||
@@ -10,12 +10,11 @@ import type {UserProfile} from '@mattermost/types/users';
|
|||||||
|
|
||||||
import {getAuthorizedOAuthApps, deauthorizeOAuthApp} from 'mattermost-redux/actions/integrations';
|
import {getAuthorizedOAuthApps, deauthorizeOAuthApp} from 'mattermost-redux/actions/integrations';
|
||||||
import {getMe, updateUserPassword} from 'mattermost-redux/actions/users';
|
import {getMe, updateUserPassword} from 'mattermost-redux/actions/users';
|
||||||
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
import {getConfig, getPasswordConfig} from 'mattermost-redux/selectors/entities/general';
|
||||||
import {getBool} from 'mattermost-redux/selectors/entities/preferences';
|
import {getBool} from 'mattermost-redux/selectors/entities/preferences';
|
||||||
import * as UserUtils from 'mattermost-redux/utils/user_utils';
|
import * as UserUtils from 'mattermost-redux/utils/user_utils';
|
||||||
|
|
||||||
import {Preferences} from 'utils/constants';
|
import {Preferences} from 'utils/constants';
|
||||||
import {getPasswordConfig} from 'utils/utils';
|
|
||||||
|
|
||||||
import SecurityTab from './user_settings_security';
|
import SecurityTab from './user_settings_security';
|
||||||
|
|
||||||
@@ -55,7 +54,7 @@ function mapStateToProps(state: GlobalState, ownProps: Props) {
|
|||||||
enableSaml,
|
enableSaml,
|
||||||
enableSignUpWithOffice365,
|
enableSignUpWithOffice365,
|
||||||
experimentalEnableAuthenticationTransfer,
|
experimentalEnableAuthenticationTransfer,
|
||||||
passwordConfig: getPasswordConfig(config),
|
passwordConfig: getPasswordConfig(state),
|
||||||
militaryTime: getBool(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.USE_MILITARY_TIME, false),
|
militaryTime: getBool(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.USE_MILITARY_TIME, false),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,15 @@ import React from 'react';
|
|||||||
import type {OAuthApp} from '@mattermost/types/integrations';
|
import type {OAuthApp} from '@mattermost/types/integrations';
|
||||||
import type {UserProfile} from '@mattermost/types/users';
|
import type {UserProfile} from '@mattermost/types/users';
|
||||||
|
|
||||||
|
import type {PasswordConfig} from 'mattermost-redux/selectors/entities/general';
|
||||||
|
|
||||||
import type {MockIntl} from 'tests/helpers/intl-test-helper';
|
import type {MockIntl} from 'tests/helpers/intl-test-helper';
|
||||||
import Constants from 'utils/constants';
|
import Constants from 'utils/constants';
|
||||||
import type * as Utils from 'utils/utils';
|
|
||||||
|
|
||||||
import {SecurityTab} from './user_settings_security';
|
import {SecurityTab} from './user_settings_security';
|
||||||
|
|
||||||
jest.mock('utils/utils', () => {
|
jest.mock('utils/password', () => {
|
||||||
const original = jest.requireActual('utils/utils');
|
const original = jest.requireActual('utils/password');
|
||||||
return {...original, isValidPassword: () => ({valid: true})};
|
return {...original, isValidPassword: () => ({valid: true})};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -46,7 +47,7 @@ describe('components/user_settings/display/UserSettingsDisplay', () => {
|
|||||||
enableSaml: true,
|
enableSaml: true,
|
||||||
enableSignUpWithOffice365: false,
|
enableSignUpWithOffice365: false,
|
||||||
experimentalEnableAuthenticationTransfer: true,
|
experimentalEnableAuthenticationTransfer: true,
|
||||||
passwordConfig: {} as ReturnType<typeof Utils.getPasswordConfig>,
|
passwordConfig: {} as PasswordConfig,
|
||||||
militaryTime: false,
|
militaryTime: false,
|
||||||
intl: {
|
intl: {
|
||||||
formatMessage: jest.fn(({id, defaultMessage}) => defaultMessage || id),
|
formatMessage: jest.fn(({id, defaultMessage}) => defaultMessage || id),
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {Link} from 'react-router-dom';
|
|||||||
import type {OAuthApp} from '@mattermost/types/integrations';
|
import type {OAuthApp} from '@mattermost/types/integrations';
|
||||||
import type {UserProfile} from '@mattermost/types/users';
|
import type {UserProfile} from '@mattermost/types/users';
|
||||||
|
|
||||||
|
import type {PasswordConfig} from 'mattermost-redux/selectors/entities/general';
|
||||||
import type {ActionResult} from 'mattermost-redux/types/actions';
|
import type {ActionResult} from 'mattermost-redux/types/actions';
|
||||||
|
|
||||||
import AccessHistoryModal from 'components/access_history_modal';
|
import AccessHistoryModal from 'components/access_history_modal';
|
||||||
@@ -22,7 +23,7 @@ import ToggleModalButton from 'components/toggle_modal_button';
|
|||||||
|
|
||||||
import icon50 from 'images/icon50x50.png';
|
import icon50 from 'images/icon50x50.png';
|
||||||
import Constants from 'utils/constants';
|
import Constants from 'utils/constants';
|
||||||
import * as Utils from 'utils/utils';
|
import {isValidPassword} from 'utils/password';
|
||||||
|
|
||||||
import MfaSection from './mfa_section';
|
import MfaSection from './mfa_section';
|
||||||
import UserAccessTokenSection from './user_access_token_section';
|
import UserAccessTokenSection from './user_access_token_section';
|
||||||
@@ -64,7 +65,7 @@ type Props = {
|
|||||||
enableSaml: boolean;
|
enableSaml: boolean;
|
||||||
enableSignUpWithOffice365: boolean;
|
enableSignUpWithOffice365: boolean;
|
||||||
experimentalEnableAuthenticationTransfer: boolean;
|
experimentalEnableAuthenticationTransfer: boolean;
|
||||||
passwordConfig: ReturnType<typeof Utils.getPasswordConfig>;
|
passwordConfig: PasswordConfig;
|
||||||
militaryTime: boolean;
|
militaryTime: boolean;
|
||||||
actions: Actions;
|
actions: Actions;
|
||||||
intl: IntlShape;
|
intl: IntlShape;
|
||||||
@@ -135,7 +136,7 @@ export class SecurityTab extends React.PureComponent<Props, State> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {valid, error} = Utils.isValidPassword(
|
const {valid, error} = isValidPassword(
|
||||||
newPassword,
|
newPassword,
|
||||||
this.props.passwordConfig,
|
this.props.passwordConfig,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -21,6 +21,28 @@ export function getFeatureFlagValue(state: GlobalState, key: keyof FeatureFlags)
|
|||||||
return getConfig(state)?.[`FeatureFlag${key}` as keyof Partial<ClientConfig>];
|
return getConfig(state)?.[`FeatureFlag${key}` as keyof Partial<ClientConfig>];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PasswordConfig = {
|
||||||
|
minimumLength: number;
|
||||||
|
requireLowercase: boolean;
|
||||||
|
requireUppercase: boolean;
|
||||||
|
requireNumber: boolean;
|
||||||
|
requireSymbol: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getPasswordConfig: (state: GlobalState) => PasswordConfig = createSelector(
|
||||||
|
'getPasswordConfig',
|
||||||
|
getConfig,
|
||||||
|
(config) => {
|
||||||
|
return {
|
||||||
|
minimumLength: parseInt(config.PasswordMinimumLength!, 10),
|
||||||
|
requireLowercase: config.PasswordRequireLowercase === 'true',
|
||||||
|
requireUppercase: config.PasswordRequireUppercase === 'true',
|
||||||
|
requireNumber: config.PasswordRequireNumber === 'true',
|
||||||
|
requireSymbol: config.PasswordRequireSymbol === 'true',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export function getLicense(state: GlobalState): ClientLicense {
|
export function getLicense(state: GlobalState): ClientLicense {
|
||||||
return state.entities.general.license;
|
return state.entities.general.license;
|
||||||
}
|
}
|
||||||
|
|||||||
197
webapp/channels/src/utils/password.test.tsx
Обычный файл
197
webapp/channels/src/utils/password.test.tsx
Обычный файл
@@ -0,0 +1,197 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import {isValidPassword} from './password';
|
||||||
|
|
||||||
|
describe('isValidPassword', () => {
|
||||||
|
test('Minimum length enforced', () => {
|
||||||
|
for (const data of [
|
||||||
|
{
|
||||||
|
password: 'tooshort',
|
||||||
|
config: {
|
||||||
|
minimumLength: 10,
|
||||||
|
requireLowercase: false,
|
||||||
|
requireUppercase: false,
|
||||||
|
requireNumber: false,
|
||||||
|
requireSymbol: false,
|
||||||
|
},
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
password: 'longenoughpassword',
|
||||||
|
config: {
|
||||||
|
minimumLength: 10,
|
||||||
|
requireLowercase: false,
|
||||||
|
requireUppercase: false,
|
||||||
|
requireNumber: false,
|
||||||
|
requireSymbol: false,
|
||||||
|
},
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
]) {
|
||||||
|
const {valid} = isValidPassword(data.password, data.config);
|
||||||
|
expect(data.valid).toEqual(valid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Maximum length enforced', () => {
|
||||||
|
for (const data of [
|
||||||
|
{
|
||||||
|
password: 'justright',
|
||||||
|
config: {
|
||||||
|
minimumLength: 8,
|
||||||
|
requireLowercase: false,
|
||||||
|
requireUppercase: false,
|
||||||
|
requireNumber: false,
|
||||||
|
requireSymbol: false,
|
||||||
|
},
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
password: 'iamaverylongstringthathas72charactersandwillpasswithoutanyissuesthiscall',
|
||||||
|
config: {
|
||||||
|
minimumLength: 8,
|
||||||
|
requireLowercase: false,
|
||||||
|
requireUppercase: false,
|
||||||
|
requireNumber: false,
|
||||||
|
requireSymbol: false,
|
||||||
|
},
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
password: 'iamaverylongstringthathas73charactersandwontpassthisvalidationatall!!!:-(',
|
||||||
|
config: {
|
||||||
|
minimumLength: 8,
|
||||||
|
requireLowercase: false,
|
||||||
|
requireUppercase: false,
|
||||||
|
requireNumber: false,
|
||||||
|
requireSymbol: false,
|
||||||
|
},
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
]) {
|
||||||
|
const {valid} = isValidPassword(data.password, data.config);
|
||||||
|
expect(data.valid).toEqual(valid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Require lowercase enforced', () => {
|
||||||
|
for (const data of [
|
||||||
|
{
|
||||||
|
password: 'UPPERCASE',
|
||||||
|
config: {
|
||||||
|
minimumLength: 5,
|
||||||
|
requireLowercase: true,
|
||||||
|
requireUppercase: false,
|
||||||
|
requireNumber: false,
|
||||||
|
requireSymbol: false,
|
||||||
|
},
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
password: 'SOMELowercase',
|
||||||
|
config: {
|
||||||
|
minimumLength: 5,
|
||||||
|
requireLowercase: true,
|
||||||
|
requireUppercase: false,
|
||||||
|
requireNumber: false,
|
||||||
|
requireSymbol: false,
|
||||||
|
},
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
]) {
|
||||||
|
const {valid} = isValidPassword(data.password, data.config);
|
||||||
|
expect(data.valid).toEqual(valid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Require uppercase enforced', () => {
|
||||||
|
for (const data of [
|
||||||
|
{
|
||||||
|
password: 'lowercase',
|
||||||
|
config: {
|
||||||
|
minimumLength: 5,
|
||||||
|
requireLowercase: false,
|
||||||
|
requireUppercase: true,
|
||||||
|
requireNumber: false,
|
||||||
|
requireSymbol: false,
|
||||||
|
},
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
password: 'SOMEUppercase',
|
||||||
|
config: {
|
||||||
|
minimumLength: 5,
|
||||||
|
requireLowercase: false,
|
||||||
|
requireUppercase: true,
|
||||||
|
requireNumber: false,
|
||||||
|
requireSymbol: false,
|
||||||
|
},
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
]) {
|
||||||
|
const {valid} = isValidPassword(data.password, data.config);
|
||||||
|
expect(data.valid).toEqual(valid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Require number enforced', () => {
|
||||||
|
for (const data of [
|
||||||
|
{
|
||||||
|
password: 'NoNumbers',
|
||||||
|
config: {
|
||||||
|
minimumLength: 5,
|
||||||
|
requireLowercase: true,
|
||||||
|
requireUppercase: true,
|
||||||
|
requireNumber: true,
|
||||||
|
requireSymbol: false,
|
||||||
|
},
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
password: 'S0m3Numb3rs',
|
||||||
|
config: {
|
||||||
|
minimumLength: 5,
|
||||||
|
requireLowercase: true,
|
||||||
|
requireUppercase: true,
|
||||||
|
requireNumber: true,
|
||||||
|
requireSymbol: false,
|
||||||
|
},
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
]) {
|
||||||
|
const {valid} = isValidPassword(data.password, data.config);
|
||||||
|
expect(data.valid).toEqual(valid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Require symbol enforced', () => {
|
||||||
|
for (const data of [
|
||||||
|
{
|
||||||
|
password: 'N0Symb0ls',
|
||||||
|
config: {
|
||||||
|
minimumLength: 5,
|
||||||
|
requireLowercase: true,
|
||||||
|
requireUppercase: true,
|
||||||
|
requireNumber: true,
|
||||||
|
requireSymbol: true,
|
||||||
|
},
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
password: 'S0m3Symb0!s',
|
||||||
|
config: {
|
||||||
|
minimumLength: 5,
|
||||||
|
requireLowercase: true,
|
||||||
|
requireUppercase: true,
|
||||||
|
requireNumber: true,
|
||||||
|
requireSymbol: true,
|
||||||
|
},
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
]) {
|
||||||
|
const {valid} = isValidPassword(data.password, data.config);
|
||||||
|
expect(data.valid).toEqual(valid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
104
webapp/channels/src/utils/password.tsx
Обычный файл
104
webapp/channels/src/utils/password.tsx
Обычный файл
@@ -0,0 +1,104 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import type {IntlShape} from 'react-intl';
|
||||||
|
import {FormattedMessage, defineMessages} from 'react-intl';
|
||||||
|
|
||||||
|
import type {PasswordConfig} from 'mattermost-redux/selectors/entities/general';
|
||||||
|
|
||||||
|
import Constants from 'utils/constants';
|
||||||
|
|
||||||
|
export function isValidPassword(password: string, passwordConfig: PasswordConfig, intl?: IntlShape) {
|
||||||
|
let errorId = passwordErrors.passwordError.id;
|
||||||
|
const telemetryErrorIds = [];
|
||||||
|
let valid = true;
|
||||||
|
const minimumLength = passwordConfig.minimumLength || Constants.MIN_PASSWORD_LENGTH;
|
||||||
|
|
||||||
|
if (password.length < minimumLength || password.length > Constants.MAX_PASSWORD_LENGTH) {
|
||||||
|
valid = false;
|
||||||
|
telemetryErrorIds.push({field: 'password', rule: 'error_length'});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (passwordConfig.requireLowercase) {
|
||||||
|
if (!password.match(/[a-z]/)) {
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
errorId += 'Lowercase';
|
||||||
|
telemetryErrorIds.push({field: 'password', rule: 'lowercase'});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (passwordConfig.requireUppercase) {
|
||||||
|
if (!password.match(/[A-Z]/)) {
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
errorId += 'Uppercase';
|
||||||
|
telemetryErrorIds.push({field: 'password', rule: 'uppercase'});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (passwordConfig.requireNumber) {
|
||||||
|
if (!password.match(/[0-9]/)) {
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
errorId += 'Number';
|
||||||
|
telemetryErrorIds.push({field: 'password', rule: 'number'});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (passwordConfig.requireSymbol) {
|
||||||
|
if (!password.match(/[ !"\\#$%&'()*+,-./:;<=>?@[\]^_`|~]/)) {
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
errorId += 'Symbol';
|
||||||
|
telemetryErrorIds.push({field: 'password', rule: 'symbol'});
|
||||||
|
}
|
||||||
|
|
||||||
|
let error;
|
||||||
|
if (!valid) {
|
||||||
|
error = intl ? (
|
||||||
|
intl.formatMessage(
|
||||||
|
{
|
||||||
|
id: errorId,
|
||||||
|
defaultMessage: 'Must be {min}-{max} characters long.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
min: minimumLength,
|
||||||
|
max: Constants.MAX_PASSWORD_LENGTH,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<FormattedMessage
|
||||||
|
id={errorId}
|
||||||
|
defaultMessage='Must be {min}-{max} characters long.'
|
||||||
|
values={{
|
||||||
|
min: minimumLength,
|
||||||
|
max: Constants.MAX_PASSWORD_LENGTH,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {valid, error, telemetryErrorIds};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const passwordErrors = defineMessages({
|
||||||
|
passwordError: {id: 'user.settings.security.passwordError', defaultMessage: 'Must be {min}-{max} characters long.'},
|
||||||
|
passwordErrorLowercase: {id: 'user.settings.security.passwordErrorLowercase', defaultMessage: 'Must be {min}-{max} characters long and include lowercase letters.'},
|
||||||
|
passwordErrorLowercaseNumber: {id: 'user.settings.security.passwordErrorLowercaseNumber', defaultMessage: 'Must be {min}-{max} characters long and include lowercase letters and numbers.'},
|
||||||
|
passwordErrorLowercaseNumberSymbol: {id: 'user.settings.security.passwordErrorLowercaseNumberSymbol', defaultMessage: 'Must be {min}-{max} characters long and include lowercase letters, numbers, and special characters.'},
|
||||||
|
passwordErrorLowercaseSymbol: {id: 'user.settings.security.passwordErrorLowercaseSymbol', defaultMessage: 'Must be {min}-{max} characters long and include lowercase letters and special characters.'},
|
||||||
|
passwordErrorLowercaseUppercase: {id: 'user.settings.security.passwordErrorLowercaseUppercase', defaultMessage: 'Must be {min}-{max} characters long and include both lowercase and uppercase letters.'},
|
||||||
|
passwordErrorLowercaseUppercaseNumber: {id: 'user.settings.security.passwordErrorLowercaseUppercaseNumber', defaultMessage: 'Must be {min}-{max} characters long and include both lowercase and uppercase letters, and numbers.'},
|
||||||
|
passwordErrorLowercaseUppercaseNumberSymbol: {id: 'user.settings.security.passwordErrorLowercaseUppercaseNumberSymbol', defaultMessage: 'Must be {min}-{max} characters long and include both lowercase and uppercase letters, numbers, and special characters.'},
|
||||||
|
passwordErrorLowercaseUppercaseSymbol: {id: 'user.settings.security.passwordErrorLowercaseUppercaseSymbol', defaultMessage: 'Must be {min}-{max} characters long and include both lowercase and uppercase letters, and special characters.'},
|
||||||
|
passwordErrorNumber: {id: 'user.settings.security.passwordErrorNumber', defaultMessage: 'Must be {min}-{max} characters long and include numbers.'},
|
||||||
|
passwordErrorNumberSymbol: {id: 'user.settings.security.passwordErrorNumberSymbol', defaultMessage: 'Must be {min}-{max} characters long and include numbers and special characters.'},
|
||||||
|
passwordErrorSymbol: {id: 'user.settings.security.passwordErrorSymbol', defaultMessage: 'Must be {min}-{max} characters long and include special characters.'},
|
||||||
|
passwordErrorUppercase: {id: 'user.settings.security.passwordErrorUppercase', defaultMessage: 'Must be {min}-{max} characters long and include uppercase letters.'},
|
||||||
|
passwordErrorUppercaseNumber: {id: 'user.settings.security.passwordErrorUppercaseNumber', defaultMessage: 'Must be {min}-{max} characters long and include uppercase letters, and numbers.'},
|
||||||
|
passwordErrorUppercaseNumberSymbol: {id: 'user.settings.security.passwordErrorUppercaseNumberSymbol', defaultMessage: 'Must be {min}-{max} characters long and include uppercase letters, numbers, and special characters.'},
|
||||||
|
passwordErrorUppercaseSymbol: {id: 'user.settings.security.passwordErrorUppercaseSymbol', defaultMessage: 'Must be {min}-{max} characters long and include uppercase letters, and special characters.'},
|
||||||
|
});
|
||||||
@@ -93,199 +93,6 @@ describe('Utils.getDisplayNameByUser', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Utils.isValidPassword', () => {
|
|
||||||
test('Minimum length enforced', () => {
|
|
||||||
for (const data of [
|
|
||||||
{
|
|
||||||
password: 'tooshort',
|
|
||||||
config: {
|
|
||||||
minimumLength: 10,
|
|
||||||
requireLowercase: false,
|
|
||||||
requireUppercase: false,
|
|
||||||
requireNumber: false,
|
|
||||||
requireSymbol: false,
|
|
||||||
},
|
|
||||||
valid: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
password: 'longenoughpassword',
|
|
||||||
config: {
|
|
||||||
minimumLength: 10,
|
|
||||||
requireLowercase: false,
|
|
||||||
requireUppercase: false,
|
|
||||||
requireNumber: false,
|
|
||||||
requireSymbol: false,
|
|
||||||
},
|
|
||||||
valid: true,
|
|
||||||
},
|
|
||||||
]) {
|
|
||||||
const {valid} = Utils.isValidPassword(data.password, data.config);
|
|
||||||
expect(data.valid).toEqual(valid);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Maximum length enforced', () => {
|
|
||||||
for (const data of [
|
|
||||||
{
|
|
||||||
password: 'justright',
|
|
||||||
config: {
|
|
||||||
minimumLength: 8,
|
|
||||||
requireLowercase: false,
|
|
||||||
requireUppercase: false,
|
|
||||||
requireNumber: false,
|
|
||||||
requireSymbol: false,
|
|
||||||
},
|
|
||||||
valid: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
password: 'iamaverylongstringthathas72charactersandwillpasswithoutanyissuesthiscall',
|
|
||||||
config: {
|
|
||||||
minimumLength: 8,
|
|
||||||
requireLowercase: false,
|
|
||||||
requireUppercase: false,
|
|
||||||
requireNumber: false,
|
|
||||||
requireSymbol: false,
|
|
||||||
},
|
|
||||||
valid: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
password: 'iamaverylongstringthathas73charactersandwontpassthisvalidationatall!!!:-(',
|
|
||||||
config: {
|
|
||||||
minimumLength: 8,
|
|
||||||
requireLowercase: false,
|
|
||||||
requireUppercase: false,
|
|
||||||
requireNumber: false,
|
|
||||||
requireSymbol: false,
|
|
||||||
},
|
|
||||||
valid: false,
|
|
||||||
},
|
|
||||||
]) {
|
|
||||||
const {valid} = Utils.isValidPassword(data.password, data.config);
|
|
||||||
expect(data.valid).toEqual(valid);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Require lowercase enforced', () => {
|
|
||||||
for (const data of [
|
|
||||||
{
|
|
||||||
password: 'UPPERCASE',
|
|
||||||
config: {
|
|
||||||
minimumLength: 5,
|
|
||||||
requireLowercase: true,
|
|
||||||
requireUppercase: false,
|
|
||||||
requireNumber: false,
|
|
||||||
requireSymbol: false,
|
|
||||||
},
|
|
||||||
valid: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
password: 'SOMELowercase',
|
|
||||||
config: {
|
|
||||||
minimumLength: 5,
|
|
||||||
requireLowercase: true,
|
|
||||||
requireUppercase: false,
|
|
||||||
requireNumber: false,
|
|
||||||
requireSymbol: false,
|
|
||||||
},
|
|
||||||
valid: true,
|
|
||||||
},
|
|
||||||
]) {
|
|
||||||
const {valid} = Utils.isValidPassword(data.password, data.config);
|
|
||||||
expect(data.valid).toEqual(valid);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Require uppercase enforced', () => {
|
|
||||||
for (const data of [
|
|
||||||
{
|
|
||||||
password: 'lowercase',
|
|
||||||
config: {
|
|
||||||
minimumLength: 5,
|
|
||||||
requireLowercase: false,
|
|
||||||
requireUppercase: true,
|
|
||||||
requireNumber: false,
|
|
||||||
requireSymbol: false,
|
|
||||||
},
|
|
||||||
valid: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
password: 'SOMEUppercase',
|
|
||||||
config: {
|
|
||||||
minimumLength: 5,
|
|
||||||
requireLowercase: false,
|
|
||||||
requireUppercase: true,
|
|
||||||
requireNumber: false,
|
|
||||||
requireSymbol: false,
|
|
||||||
},
|
|
||||||
valid: true,
|
|
||||||
},
|
|
||||||
]) {
|
|
||||||
const {valid} = Utils.isValidPassword(data.password, data.config);
|
|
||||||
expect(data.valid).toEqual(valid);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Require number enforced', () => {
|
|
||||||
for (const data of [
|
|
||||||
{
|
|
||||||
password: 'NoNumbers',
|
|
||||||
config: {
|
|
||||||
minimumLength: 5,
|
|
||||||
requireLowercase: true,
|
|
||||||
requireUppercase: true,
|
|
||||||
requireNumber: true,
|
|
||||||
requireSymbol: false,
|
|
||||||
},
|
|
||||||
valid: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
password: 'S0m3Numb3rs',
|
|
||||||
config: {
|
|
||||||
minimumLength: 5,
|
|
||||||
requireLowercase: true,
|
|
||||||
requireUppercase: true,
|
|
||||||
requireNumber: true,
|
|
||||||
requireSymbol: false,
|
|
||||||
},
|
|
||||||
valid: true,
|
|
||||||
},
|
|
||||||
]) {
|
|
||||||
const {valid} = Utils.isValidPassword(data.password, data.config);
|
|
||||||
expect(data.valid).toEqual(valid);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Require symbol enforced', () => {
|
|
||||||
for (const data of [
|
|
||||||
{
|
|
||||||
password: 'N0Symb0ls',
|
|
||||||
config: {
|
|
||||||
minimumLength: 5,
|
|
||||||
requireLowercase: true,
|
|
||||||
requireUppercase: true,
|
|
||||||
requireNumber: true,
|
|
||||||
requireSymbol: true,
|
|
||||||
},
|
|
||||||
valid: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
password: 'S0m3Symb0!s',
|
|
||||||
config: {
|
|
||||||
minimumLength: 5,
|
|
||||||
requireLowercase: true,
|
|
||||||
requireUppercase: true,
|
|
||||||
requireNumber: true,
|
|
||||||
requireSymbol: true,
|
|
||||||
},
|
|
||||||
valid: true,
|
|
||||||
},
|
|
||||||
]) {
|
|
||||||
const {valid} = Utils.isValidPassword(data.password, data.config);
|
|
||||||
expect(data.valid).toEqual(valid);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Utils.isValidUsername', () => {
|
describe('Utils.isValidUsername', () => {
|
||||||
const tests = [
|
const tests = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,12 +9,9 @@ import isNil from 'lodash/isNil';
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type {LinkHTMLAttributes} from 'react';
|
import type {LinkHTMLAttributes} from 'react';
|
||||||
import {FormattedMessage} from 'react-intl';
|
|
||||||
import type {IntlShape} from 'react-intl';
|
|
||||||
|
|
||||||
import type {Channel} from '@mattermost/types/channels';
|
import type {Channel} from '@mattermost/types/channels';
|
||||||
import type {Address} from '@mattermost/types/cloud';
|
import type {Address} from '@mattermost/types/cloud';
|
||||||
import type {ClientConfig} from '@mattermost/types/config';
|
|
||||||
import type {FileInfo} from '@mattermost/types/files';
|
import type {FileInfo} from '@mattermost/types/files';
|
||||||
import type {Group} from '@mattermost/types/groups';
|
import type {Group} from '@mattermost/types/groups';
|
||||||
import type {GlobalState} from '@mattermost/types/store';
|
import type {GlobalState} from '@mattermost/types/store';
|
||||||
@@ -1224,92 +1221,6 @@ export function mod(a: number, b: number): number {
|
|||||||
|
|
||||||
export const REACTION_PATTERN = /^(\+|-):([^:\s]+):\s*$/;
|
export const REACTION_PATTERN = /^(\+|-):([^:\s]+):\s*$/;
|
||||||
|
|
||||||
export function getPasswordConfig(config: Partial<ClientConfig>) {
|
|
||||||
return {
|
|
||||||
minimumLength: parseInt(config.PasswordMinimumLength!, 10),
|
|
||||||
requireLowercase: config.PasswordRequireLowercase === 'true',
|
|
||||||
requireUppercase: config.PasswordRequireUppercase === 'true',
|
|
||||||
requireNumber: config.PasswordRequireNumber === 'true',
|
|
||||||
requireSymbol: config.PasswordRequireSymbol === 'true',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isValidPassword(password: string, passwordConfig: ReturnType<typeof getPasswordConfig>, intl?: IntlShape) {
|
|
||||||
// The translation strings used by this function are defined in admin_console/password_settings
|
|
||||||
let errorId = 'user.settings.security.passwordError';
|
|
||||||
const telemetryErrorIds = [];
|
|
||||||
let valid = true;
|
|
||||||
const minimumLength = passwordConfig.minimumLength || Constants.MIN_PASSWORD_LENGTH;
|
|
||||||
|
|
||||||
if (password.length < minimumLength || password.length > Constants.MAX_PASSWORD_LENGTH) {
|
|
||||||
valid = false;
|
|
||||||
telemetryErrorIds.push({field: 'password', rule: 'error_length'});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (passwordConfig.requireLowercase) {
|
|
||||||
if (!password.match(/[a-z]/)) {
|
|
||||||
valid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
errorId += 'Lowercase';
|
|
||||||
telemetryErrorIds.push({field: 'password', rule: 'lowercase'});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (passwordConfig.requireUppercase) {
|
|
||||||
if (!password.match(/[A-Z]/)) {
|
|
||||||
valid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
errorId += 'Uppercase';
|
|
||||||
telemetryErrorIds.push({field: 'password', rule: 'uppercase'});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (passwordConfig.requireNumber) {
|
|
||||||
if (!password.match(/[0-9]/)) {
|
|
||||||
valid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
errorId += 'Number';
|
|
||||||
telemetryErrorIds.push({field: 'password', rule: 'number'});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (passwordConfig.requireSymbol) {
|
|
||||||
if (!password.match(/[ !"\\#$%&'()*+,-./:;<=>?@[\]^_`|~]/)) {
|
|
||||||
valid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
errorId += 'Symbol';
|
|
||||||
telemetryErrorIds.push({field: 'password', rule: 'symbol'});
|
|
||||||
}
|
|
||||||
|
|
||||||
let error;
|
|
||||||
if (!valid) {
|
|
||||||
error = intl ? (
|
|
||||||
intl.formatMessage(
|
|
||||||
{
|
|
||||||
id: errorId,
|
|
||||||
defaultMessage: 'Must be {min}-{max} characters long.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
min: minimumLength,
|
|
||||||
max: Constants.MAX_PASSWORD_LENGTH,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<FormattedMessage
|
|
||||||
id={errorId}
|
|
||||||
defaultMessage='Must be {min}-{max} characters long.'
|
|
||||||
values={{
|
|
||||||
min: minimumLength,
|
|
||||||
max: Constants.MAX_PASSWORD_LENGTH,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {valid, error, telemetryErrorIds};
|
|
||||||
}
|
|
||||||
|
|
||||||
function isChannelOrPermalink(link: string) {
|
function isChannelOrPermalink(link: string) {
|
||||||
let match = (/\/([a-z0-9\-_]+)\/channels\/([a-z0-9\-__][a-z0-9\-__.]+)/).exec(link);
|
let match = (/\/([a-z0-9\-_]+)\/channels\/([a-z0-9\-__][a-z0-9\-__.]+)/).exec(link);
|
||||||
if (match) {
|
if (match) {
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user