Skip to content

Commit

Permalink
[WALL] Aizad/WALL-3546/Update MT5 Password Policy (binary-com#14100)
Browse files Browse the repository at this point in the history
* chore: update mt5 password policy

* fix: type error inside of MT5ChangeInvestorPassword component

* chore: added new validation for create mt5 password

* chore: added new Mt5 reset password modal inside of wallets

* chore: updated password missing character string for new policy

* chore: update MT5ResetPasswordModal

* chore: updatd password field with latest mt5 policy with MT5ChangePassword modal

* fix: update ModalStepWrapper shouldHideFooter prop

* chore: created flows for both success and failures aswell as minor refactoring

* chore: resolve ci issues and comments

* chore: fix gitguardian issue

* chore: rerun git guardian

* chore: refactor MT5PasswordModal

* chore: added return statement for PasswordComponent

* chore: refactor MT5 Password Modal

* chore: resolve comments and made small changes

* chore: fix sonarcloud error

* chore: cleanup compoent export

* chore: revert changes

* chore: use account status instead of mt5 account list

* chore: resolve comments

* Delete packages/wallets/package-lock.json
  • Loading branch information
aizad-deriv committed Mar 26, 2024
1 parent 922c807 commit 3a70862
Show file tree
Hide file tree
Showing 21 changed files with 598 additions and 207 deletions.
1 change: 1 addition & 0 deletions packages/wallets/package.json
Expand Up @@ -13,6 +13,7 @@
"translate": "sh ./scripts/update-translations.sh"
},
"dependencies": {
"@deriv-com/utils": "^0.0.11",
"@deriv/api-v2": "^1.0.0",
"@deriv/quill-icons": "^1.19.5",
"@deriv/integration": "^1.0.0",
Expand Down
@@ -1,5 +1,5 @@
.wallets-modal-step-wrapper {
background: var(--system-light-8-primary-background, #fff);
background: var(--general-main-2, #ffffff);
border-radius: 0.8rem;
animation: popup 0.4s;

Expand Down Expand Up @@ -27,6 +27,12 @@
&--no-header--fixed-footer {
grid-template-rows: auto 7rem;
}

&--no-footer {
.wallets-modal-step-wrapper__footer {
display: none;
}
}
}

&__header {
Expand Down
Expand Up @@ -21,6 +21,7 @@ const ModalStepWrapper: FC<PropsWithChildren<TModalStepWrapperProps>> = ({
renderFooter,
shouldFixedFooter = true,
shouldHideDerivAppHeader = false,
shouldHideFooter = false,
shouldHideHeader = false,
shouldPreventCloseOnEscape = false,
title,
Expand Down Expand Up @@ -53,6 +54,7 @@ const ModalStepWrapper: FC<PropsWithChildren<TModalStepWrapperProps>> = ({
className={classNames('wallets-modal-step-wrapper', {
'wallets-modal-step-wrapper--fixed-footer': fixedFooter && !shouldHideHeader,
'wallets-modal-step-wrapper--hide-deriv-app-header': shouldHideDerivAppHeader,
'wallets-modal-step-wrapper--no-footer': shouldHideFooter,
'wallets-modal-step-wrapper--no-header': shouldHideHeader && !fixedFooter,
'wallets-modal-step-wrapper--no-header--fixed-footer': shouldHideHeader && fixedFooter,
})}
Expand Down
@@ -1,5 +1,5 @@
import React from 'react';
import { Score } from '../../../utils/password';
import { Score } from '../../../utils/password-validation';

export interface PasswordMeterProps {
score: Score;
Expand Down
@@ -1,36 +1,52 @@
import React, { useCallback, useMemo, useState } from 'react';
import { ValidationError } from 'yup';
import { zxcvbn, zxcvbnOptions } from '@zxcvbn-ts/core';
import { dictionary } from '@zxcvbn-ts/language-common';
import { passwordErrorMessage, passwordRegex, warningMessages } from '../../../constants/password';
import { calculateScore, isPasswordValid, passwordKeys, Score, validPassword } from '../../../utils/password';
import { passwordErrorMessage, warningMessages } from '../../../constants/password';
import {
calculateScore,
cfdSchema,
mt5Schema,
passwordKeys,
Score,
validPassword,
validPasswordMT5,
} from '../../../utils/password-validation';
import { WalletPasswordFieldProps } from '../WalletPasswordFieldLazy/WalletPasswordFieldLazy';
import { WalletTextField } from '../WalletTextField';
import PasswordMeter from './PasswordMeter';
import PasswordViewerIcon from './PasswordViewerIcon';
import './WalletPasswordField.scss';

export const validatePassword = (password: string) => {
export const validatePassword = (password: string, mt5Policy: boolean) => {
const score = calculateScore(password);
let errorMessage = '';

const options = { dictionary: { ...dictionary } };
zxcvbnOptions.setOptions(options);

const { feedback } = zxcvbn(password);
if (!passwordRegex.isLengthValid.test(password)) {
errorMessage = passwordErrorMessage.invalidLength;
} else if (!isPasswordValid(password)) {
errorMessage = passwordErrorMessage.missingCharacter;
} else {
try {
if (mt5Policy) {
mt5Schema.validateSync(password);
} else {
cfdSchema.validateSync(password);
}
errorMessage = warningMessages[feedback.warning as passwordKeys] ?? '';
} catch (err) {
if (err instanceof ValidationError) {
errorMessage = err?.message;
}
}

return { errorMessage, score };
};

const WalletPasswordField: React.FC<WalletPasswordFieldProps> = ({
autoComplete,
label,
message,
mt5Policy = false,
name = 'walletPasswordField',
onChange,
password,
Expand All @@ -41,7 +57,8 @@ const WalletPasswordField: React.FC<WalletPasswordFieldProps> = ({
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
const [isTouched, setIsTouched] = useState(false);

const { errorMessage, score } = useMemo(() => validatePassword(password), [password]);
const { errorMessage, score } = useMemo(() => validatePassword(password, mt5Policy), [password, mt5Policy]);
const passwordValidation = mt5Policy ? !validPasswordMT5(password) : !validPassword(password);

const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
Expand Down Expand Up @@ -73,7 +90,7 @@ const WalletPasswordField: React.FC<WalletPasswordFieldProps> = ({
<WalletTextField
autoComplete={autoComplete}
errorMessage={isTouched && (passwordError ? passwordErrorMessage.PasswordError : errorMessage)}
isInvalid={(!validPassword(password) && isTouched) || passwordError}
isInvalid={(passwordValidation && isTouched) || passwordError}
label={label}
message={getMessage()}
messageVariant={errorMessage ? 'warning' : undefined}
Expand Down
@@ -1,8 +1,9 @@
import React, { lazy, Suspense } from 'react';
import { WalletTextFieldProps } from '../WalletTextField/WalletTextField';
import Loader from '../../Loader/Loader';
import { WalletTextFieldProps } from '../WalletTextField/WalletTextField';

export interface WalletPasswordFieldProps extends WalletTextFieldProps {
mt5Policy?: boolean; // This prop is used to utilize the new password validation for MT5.
password: string;
passwordError?: boolean;
shouldDisablePasswordMeter?: boolean;
Expand Down
Expand Up @@ -9,36 +9,44 @@ import { WalletsActionScreen } from '../WalletsActionScreen';

type WalletSuccessResetMT5PasswordProps = {
isInvestorPassword?: boolean;
onClickSuccess?: () => void;
title: string;
};

const WalletSuccessResetMT5Password: FC<WalletSuccessResetMT5PasswordProps> = ({
isInvestorPassword = false,
onClickSuccess,
title,
}) => {
const { hide } = useModal();
const { isDesktop, isMobile } = useDevice();

const handleSuccess = useCallback(() => {
onClickSuccess?.();
hide();
}, [onClickSuccess, hide]);

const renderFooter = useCallback(() => {
return isMobile ? (
<WalletButton isFullWidth onClick={() => hide()} size='lg'>
<WalletButton isFullWidth onClick={handleSuccess} size='lg'>
<Trans defaults='Done' />
</WalletButton>
) : null;
}, [isMobile, hide]);
}, [isMobile, handleSuccess]);

const renderButtons = useCallback(() => {
return isDesktop ? (
<WalletButton onClick={() => hide()} size='lg'>
<WalletButton onClick={handleSuccess} size='lg'>
<Trans defaults='Done' />
</WalletButton>
) : null;
}, [isDesktop, hide]);
}, [isDesktop, handleSuccess]);

return (
<ModalStepWrapper
renderFooter={isMobile ? renderFooter : undefined}
shouldFixedFooter={isMobile}
shouldHideHeader={!isMobile}
title={`Manage ${title} password`}
>
<div className='wallets-reset-mt5-password'>
Expand Down
Expand Up @@ -4,7 +4,7 @@ import { useTradingPlatformInvestorPasswordReset, useTradingPlatformPasswordRese
import { PlatformDetails } from '../../features/cfd/constants';
import useDevice from '../../hooks/useDevice';
import { TPlatforms } from '../../types';
import { validPassword } from '../../utils/password';
import { validPassword } from '../../utils/password-validation';
import { ModalStepWrapper, WalletButton, WalletButtonGroup, WalletPasswordFieldLazy, WalletText } from '../Base';
import { useModal } from '../ModalProvider';
import WalletSuccessResetMT5Password from './WalletSuccessResetMT5Password';
Expand Down
@@ -1 +1,4 @@
export { default as WalletsResetMT5Password } from './WalletsResetMT5Password';
import WalletsResetMT5Password from './WalletsResetMT5Password';
import WalletSuccessResetMT5Password from './WalletSuccessResetMT5Password';

export { WalletsResetMT5Password, WalletSuccessResetMT5Password };
42 changes: 34 additions & 8 deletions packages/wallets/src/constants/password.ts
@@ -1,23 +1,40 @@
import { passwordKeys } from '../utils/password';
import { ValidationConstants } from '@deriv-com/utils';
import { passwordKeys } from '../utils/password-validation';

const {
between8and16Characters,
between8and25Characters,
lowercase,
number,
password,
specialCharacter,
tradingPlatformInvestorPassword,
uppercase,
} = ValidationConstants.patterns;

export const passwordRegex = {
hasLowerCase: /[a-z]/,
hasNumber: /\d/,
hasSymbol: /\W/,
hasUpperCase: /[A-Z]/,
isLengthValid: /^.{8,25}$/,
isPasswordValid: /^(?=.*[a-z])(?=.*\d)(?=.*[A-Z])[!-~]{8,25}$/,
hasLowerCase: lowercase,
hasNumber: number,
hasSymbol: specialCharacter,
hasUpperCase: uppercase,
isLengthValid: between8and25Characters,
isMT5LengthValid: between8and16Characters,
isMT5PasswordValid: tradingPlatformInvestorPassword,
isPasswordValid: password,
};

export const passwordValues = {
longPassword: 12,
maxLength: 25,
maxLengthMT5: 16,
minLength: 8,
};

export const passwordErrorMessage = {
invalidLength: 'You should enter 8-25 characters.',
invalidLength: `You should enter ${passwordValues.minLength}-${passwordValues.maxLength} characters.`,
invalidLengthMT5: `You should enter ${passwordValues.minLength}-${passwordValues.maxLengthMT5} characters.`,
missingCharacter: 'Password should have lower and uppercase English letters with numbers.',
missingCharacterMT5: 'Please include at least 1 special character such as ( _ @ ? ! / # ) in your password.',
PasswordError: 'That password is incorrect. Please try again.',
};

Expand All @@ -39,3 +56,12 @@ export const warningMessages: Record<passwordKeys, string> = {
userInputs: 'There should not be any personal or page related data.',
wordByItself: 'Single words are easy to guess.',
};

// Display on MT5 Password Reset Modal for new password requirements
export const passwordRequirements = [
'8 to 16 characters',
'A special character such as ( _ @ ? ! / # )',
'An uppercase letter',
'A lowercase letter',
'A number',
];
21 changes: 9 additions & 12 deletions packages/wallets/src/features/cfd/ResetMT5PasswordHandler.tsx
@@ -1,4 +1,4 @@
import React, { useEffect, useMemo } from 'react';
import React, { useEffect } from 'react';
import { WalletsResetMT5Password } from '../../components';
import { useModal } from '../../components/ModalProvider';
import { getActionFromUrl } from '../../helpers/urls';
Expand All @@ -9,18 +9,15 @@ const ResetMT5PasswordHandler = () => {
const { show } = useModal();
const resetTradingPlatformActionParams = getActionFromUrl();

const platformMapping: Record<string, Exclude<TPlatforms.All, 'ctrader'>> = useMemo(
() => ({
trading_platform_dxtrade_password_reset: CFD_PLATFORMS?.DXTRADE,
trading_platform_investor_password_reset: CFD_PLATFORMS?.MT5,
trading_platform_mt5_password_reset: CFD_PLATFORMS?.MT5,
}),
[]
);
const platformMapping: Record<string, Exclude<TPlatforms.All, 'ctrader'>> = {
trading_platform_dxtrade_password_reset: CFD_PLATFORMS?.DXTRADE,
trading_platform_investor_password_reset: CFD_PLATFORMS?.MT5,
trading_platform_mt5_password_reset: CFD_PLATFORMS?.MT5,
};

useEffect(() => {
const platformKey = resetTradingPlatformActionParams ? platformMapping[resetTradingPlatformActionParams] : null;
const platformKey = resetTradingPlatformActionParams ? platformMapping[resetTradingPlatformActionParams] : null;

useEffect(() => {
if (platformKey) {
const verificationCode = localStorage.getItem(`verification_code.${resetTradingPlatformActionParams}`);

Expand All @@ -37,7 +34,7 @@ const ResetMT5PasswordHandler = () => {
);
}
}
}, [platformMapping, resetTradingPlatformActionParams, show]);
}, [platformKey, resetTradingPlatformActionParams, show]);

return null;
};
Expand Down

0 comments on commit 3a70862

Please sign in to comment.