diff --git a/packages/account/src/Components/reset-trading-password-modal/reset-trading-password-modal.tsx b/packages/account/src/Components/reset-trading-password-modal/reset-trading-password-modal.tsx index eafe2db12a4a..949437f899d0 100644 --- a/packages/account/src/Components/reset-trading-password-modal/reset-trading-password-modal.tsx +++ b/packages/account/src/Components/reset-trading-password-modal/reset-trading-password-modal.tsx @@ -64,12 +64,10 @@ const ResetTradingPassword = ({ min_number: 8, max_number: max_length, }); + } else if (platform === CFD_PLATFORMS.MT5 && !validMT5Password(values.password)) { + errors.password = getErrorMessages().special_characters(); } else if (!validPassword(values.password)) { errors.password = getErrorMessages().password(); - } else if (platform === CFD_PLATFORMS.MT5 && !validMT5Password(values.password)) { - errors.password = localize( - 'Please include at least 1 special character such as ( _ @ ? ! / # ) in your password.' - ); } return errors; diff --git a/packages/appstore/src/components/cfds-listing/cfds-listing.scss b/packages/appstore/src/components/cfds-listing/cfds-listing.scss index b495d57780c5..24cd9d5a53af 100644 --- a/packages/appstore/src/components/cfds-listing/cfds-listing.scss +++ b/packages/appstore/src/components/cfds-listing/cfds-listing.scss @@ -2215,17 +2215,17 @@ justify-content: center; @include typeface(--paragraph-left-normal-black); color: var(--text-prominent); + padding: 2.4rem; &__container { display: flex; flex-direction: column; align-items: center; height: 100%; - width: 100%; + width: 34rem; justify-items: center; flex-grow: 1; gap: 4rem; - padding: 2.4rem 0; @include mobile-or-tablet-screen { min-height: 24rem; @@ -2250,7 +2250,7 @@ } &__password-area { @include desktop-screen { - width: calc(min(34rem, 100vw)); + width: 100%; } @include mobile-or-tablet-screen { width: 100%; diff --git a/packages/cfd/src/Containers/cfd-password-change.tsx b/packages/cfd/src/Containers/cfd-password-change.tsx index 0dd66adc8e7c..8f4405671cab 100644 --- a/packages/cfd/src/Containers/cfd-password-change.tsx +++ b/packages/cfd/src/Containers/cfd-password-change.tsx @@ -94,13 +94,7 @@ const CFDPasswordChange = observer( if (response.error.code === 'PasswordError') actions.setFieldError('old_password', response.error.message); if (response.error.code === 'InputValidationFailed') - actions.setFieldError( - 'new_password', - // Localize is employed to convert the customized error message since the backend error lacks clarity. - localize( - 'Please include at least 1 special character such as ( _ @ ? ! / # ) in your password.' - ) - ); + actions.setFieldError('new_password', getErrorMessages().special_characters()); } if (!response.error) { diff --git a/packages/cfd/src/Containers/cfd-password-manager-modal.tsx b/packages/cfd/src/Containers/cfd-password-manager-modal.tsx index 41276c3d41d4..009e58e2024f 100644 --- a/packages/cfd/src/Containers/cfd-password-manager-modal.tsx +++ b/packages/cfd/src/Containers/cfd-password-manager-modal.tsx @@ -13,7 +13,7 @@ import { } from '@deriv/components'; import { useDevice } from '@deriv-com/ui'; import { localize, Localize } from '@deriv/translations'; -import { getCFDPlatformLabel } from '@deriv/shared'; +import { getCFDPlatformLabel, getErrorMessages, validMT5Password } from '@deriv/shared'; import { FormikErrors } from 'formik'; import CFDStore from '../Stores/Modules/CFD/cfd-store'; import TradingPasswordManager from './trading-password-manager'; @@ -169,7 +169,9 @@ const CFDPasswordManagerTabContent = ({ errors.new_password = localize('This field is required'); } - if (validatePassword(values.new_password)) errors.new_password = validatePassword(values.new_password); + if (platform === CFD_PLATFORMS.MT5 && !validMT5Password(values.new_password)) { + errors.new_password = getErrorMessages().special_characters(); + } else if (validatePassword(values.new_password)) errors.new_password = validatePassword(values.new_password); return errors; }; diff --git a/packages/cfd/src/Containers/cfd-password-modal.tsx b/packages/cfd/src/Containers/cfd-password-modal.tsx index 3bd3a1f3f0d5..30262ef1b9ab 100644 --- a/packages/cfd/src/Containers/cfd-password-modal.tsx +++ b/packages/cfd/src/Containers/cfd-password-modal.tsx @@ -738,16 +738,14 @@ const CFDPasswordModal = observer(({ form_error, platform }: TCFDPasswordModalPr min_number: 8, max_number: max_length, }); - } else if (!validPassword(values.password)) { - errors.password = getErrorMessages().password(); } else if ( platform === CFD_PLATFORMS.MT5 && should_set_trading_password && !validMT5Password(values.password) ) { - errors.password = localize( - 'Please include at least 1 special character such as ( _ @ ? ! / # ) in your password.' - ); + errors.password = getErrorMessages().special_characters(); + } else if (!validPassword(values.password)) { + getErrorMessages().password(); } if (values.password?.toLowerCase() === email.toLowerCase()) { errors.password = localize('Your password cannot be the same as your email address.'); diff --git a/packages/cfd/src/Containers/cfd-reset-password-modal.tsx b/packages/cfd/src/Containers/cfd-reset-password-modal.tsx index 8515beaee4fb..31b153103eac 100644 --- a/packages/cfd/src/Containers/cfd-reset-password-modal.tsx +++ b/packages/cfd/src/Containers/cfd-reset-password-modal.tsx @@ -101,12 +101,10 @@ const CFDResetPasswordModal = observer(({ platform }: TCFDResetPasswordModal) => min_number: 8, max_number: max_length, }); - } else if (!validPassword(values.new_password)) { - errors.new_password = getErrorMessages().password(); } else if (platform !== CFD_PLATFORMS.DXTRADE && !validMT5Password(values.new_password)) { - errors.new_password = localize( - 'Please include at least 1 special character such as ( _ @ ? ! / # ) in your password.' - ); + errors.new_password = getErrorMessages().special_characters(); + } else if (!validPassword(values.new_password)) { + errors.new_password = getErrorMessages().special_characters(); } if (values.new_password.toLowerCase() === email.toLowerCase()) { errors.new_password = localize('Your password cannot be the same as your email address.'); @@ -212,7 +210,7 @@ const CFDResetPasswordModal = observer(({ platform }: TCFDResetPasswordModal) => align='center' className='cfd-reset-password__description2' > - + )} diff --git a/packages/cfd/src/Containers/mt5-migration-modal/__test__/mt5-migration-back-side-content.spec.tsx b/packages/cfd/src/Containers/mt5-migration-modal/__test__/mt5-migration-back-side-content.spec.tsx index 3e80704ed184..13d64c56094d 100644 --- a/packages/cfd/src/Containers/mt5-migration-modal/__test__/mt5-migration-back-side-content.spec.tsx +++ b/packages/cfd/src/Containers/mt5-migration-modal/__test__/mt5-migration-back-side-content.spec.tsx @@ -23,6 +23,7 @@ jest.mock('@deriv/shared', () => ({ ...jest.requireActual('@deriv/shared'), getErrorMessages: jest.fn().mockReturnValue({ password: jest.fn(), + special_chracters: jest.fn(), password_warnings: '', }), WS: { @@ -119,7 +120,7 @@ describe('MT5MigrationBackSideContent', () => { renderComponent(); const input_field = screen.getByLabelText('Deriv MT5 password'); - userEvent.type(input_field, 'Abcd1234'); + userEvent.type(input_field, 'Abcd1234!'); const upgrade_button = screen.getByRole('button', { name: 'Upgrade' }); userEvent.click(upgrade_button); await waitFor(() => { diff --git a/packages/cfd/src/Containers/mt5-migration-modal/mt5-migration-back-side-content.tsx b/packages/cfd/src/Containers/mt5-migration-modal/mt5-migration-back-side-content.tsx index 0cf68b938f9b..da1fa9b8b826 100644 --- a/packages/cfd/src/Containers/mt5-migration-modal/mt5-migration-back-side-content.tsx +++ b/packages/cfd/src/Containers/mt5-migration-modal/mt5-migration-back-side-content.tsx @@ -1,6 +1,6 @@ import { InlineMessage, Modal, Text, PasswordInput, FormSubmitButton } from '@deriv/components'; import { useMT5SVGEligibleToMigrate } from '@deriv/hooks'; -import { CFD_PLATFORMS, WS, validLength, validPassword, getErrorMessages } from '@deriv/shared'; +import { CFD_PLATFORMS, WS, validLength, validMT5Password, getErrorMessages } from '@deriv/shared'; import { observer, useStore } from '@deriv/stores'; import { Localize, localize } from '@deriv/translations'; import React from 'react'; @@ -58,7 +58,7 @@ const MT5MigrationBackSideContent = observer(() => { min_number: 8, max_number: 25, }); - } else if (!validPassword(values.password)) { + } else if (!validMT5Password(values.password)) { errors.password = getErrorMessages().password(); } if (values.password?.toLowerCase() === email.toLowerCase()) { diff --git a/packages/cfd/src/Helpers/constants.ts b/packages/cfd/src/Helpers/constants.ts index 9dc6e632da17..a8f542158583 100644 --- a/packages/cfd/src/Helpers/constants.ts +++ b/packages/cfd/src/Helpers/constants.ts @@ -6,6 +6,7 @@ import { validPassword, validMT5Password, mobileOSDetectAsync, + hasBritishPound, } from '@deriv/shared'; import { localize } from '@deriv/translations'; import { TCFDsPlatformType, TDetailsOfEachMT5Loginid, TMobilePlatforms } from 'Components/props.types'; @@ -139,10 +140,10 @@ const validatePassword = (password: string): string | undefined => { min_number: 8, max_number: 16, }); - } else if (!validPassword(password)) { - return getErrorMessages().password(); } else if (!validMT5Password(password)) { - return localize('Please include at least 1 special character such as ( _ @ ? ! / # ) in your password.'); + return getErrorMessages().special_characters(); + } else if (!validPassword(password)) { + return hasBritishPound(password) ? getErrorMessages().special_characters() : getErrorMessages().password(); } }; diff --git a/packages/cfd/src/sass/cfd.scss b/packages/cfd/src/sass/cfd.scss index 0965b987a81b..7c573168c0a3 100644 --- a/packages/cfd/src/sass/cfd.scss +++ b/packages/cfd/src/sass/cfd.scss @@ -310,17 +310,17 @@ justify-content: center; @include typeface(--paragraph-left-normal-black); color: var(--text-prominent); + padding: 2.4rem; &__container { display: flex; flex-direction: column; align-items: center; height: 100%; - width: 100%; + width: 34rem; justify-items: center; flex-grow: 1; gap: 4rem; - padding: 2.4rem 0; @include mobile-or-tablet-screen { min-height: 24rem; @@ -345,7 +345,7 @@ } &__password-area { @include desktop-screen { - width: calc(min(34rem, 100vw)); + width: 100%; } @include mobile-or-tablet-screen { width: 100%; diff --git a/packages/core/src/App/Containers/ResetPasswordModal/reset-password-modal.tsx b/packages/core/src/App/Containers/ResetPasswordModal/reset-password-modal.tsx index 13d52bbfbe3f..63821223102a 100644 --- a/packages/core/src/App/Containers/ResetPasswordModal/reset-password-modal.tsx +++ b/packages/core/src/App/Containers/ResetPasswordModal/reset-password-modal.tsx @@ -2,7 +2,15 @@ import React from 'react'; import classNames from 'classnames'; import { Formik, Form, FormikHelpers, FormikErrors } from 'formik'; import { Button, Dialog, PasswordInput, PasswordMeter, Text } from '@deriv/components'; -import { redirectToLogin, validPassword, validLength, getErrorMessages, WS, removeActionParam } from '@deriv/shared'; +import { + redirectToLogin, + validPassword, + validLength, + getErrorMessages, + WS, + removeActionParam, + validMT5Password, +} from '@deriv/shared'; import { getLanguage, localize, Localize } from '@deriv/translations'; import { observer, useStore } from '@deriv/stores'; import { TSocketError, TSocketRequest, TSocketResponse } from '@deriv/api/types'; @@ -84,6 +92,8 @@ const ResetPasswordModal = observer(() => { min_number: 8, max_number: 25, }); + } else if (!validMT5Password(values.password)) { + errors.password = getErrorMessages().special_characters(); } else if (!validPassword(values.password)) { errors.password = getErrorMessages().password(); } diff --git a/packages/core/src/Constants/form-error-messages.js b/packages/core/src/Constants/form-error-messages.js index 491d4095bca5..c34269512ecd 100644 --- a/packages/core/src/Constants/form-error-messages.js +++ b/packages/core/src/Constants/form-error-messages.js @@ -16,6 +16,8 @@ export const FORM_ERROR_MESSAGES = { general: () => localize('Only letters, numbers, space, hyphen, period, and apostrophe are allowed.'), name: () => localize('Letters, spaces, periods, hyphens, apostrophes only.'), password: () => localize('Password should have lower and uppercase English letters with numbers.'), + special_characters: () => + localize('Password must have at least one of these special characters: !&‘’*-“%+.#&(),:;?=@<>\\[]^_{}|~'), po_box: () => localize('P.O. Box is not accepted in address'), phone: () => localize('Please enter a valid phone number (e.g. +15417541234).'), postcode: () => localize('Only letters, numbers, space and hyphen are allowed.'), diff --git a/packages/shared/src/utils/validation/declarative-validation-rules.ts b/packages/shared/src/utils/validation/declarative-validation-rules.ts index 0fc8148ea6d9..c1edda3387d1 100644 --- a/packages/shared/src/utils/validation/declarative-validation-rules.ts +++ b/packages/shared/src/utils/validation/declarative-validation-rules.ts @@ -62,6 +62,7 @@ export const validName = (value: string) => /^(?!.*\s{2,})(?!\s)[\p{L}\s'.-]{1,5 export const validLength = (value = '', options: TOptions) => (options.min ? value.length >= Number(options.min) : true) && (options.max ? value.length <= Number(options.max) : true); +export const hasBritishPound = (value: string) => /£/.test(value); export const validPassword = (value: string) => /^(?=.*[a-z])(?=.*\d)(?=.*[A-Z])[!-~]{8,25}$/.test(value); export const validEmail = (value: string) => /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,63}$/.test(value); const validBarrier = (value: string) => /^[+-]?\d+\.?\d*$/.test(value); @@ -74,7 +75,8 @@ export const hasInvalidCharacters = (target_string: string) => /[^\dX\s]/.test(t export const isFormattedCardNumber = (target_string: string) => /(^\d{4})\s(\d{2}X{2})\s(X{4})\s(\d{4}$)/.test(target_string); export const validFile = (file: File) => file?.type && /(image|application)\/(jpe?g|pdf|png)$/.test(file?.type); -export const validMT5Password = (value: string) => /^(?=.*[!@#$%^&*()+\-=[\]{};':"|,.<>/?_~])[ -~]{8,16}$/.test(value); +export const validMT5Password = (value: string) => + /^(?=.*[!@#$%^&*()+\-=[\]{};':"|,.<>/?_~])[ -~]{8,16}$/.test(value) && !hasBritishPound(value); let pre_build_dvrs: TInitPreBuildDVRs, form_error_messages: TFormErrorMessagesTypes; diff --git a/packages/shared/src/utils/validation/form-error-messages-types.ts b/packages/shared/src/utils/validation/form-error-messages-types.ts index b04937dbb60d..0dd38062eb67 100644 --- a/packages/shared/src/utils/validation/form-error-messages-types.ts +++ b/packages/shared/src/utils/validation/form-error-messages-types.ts @@ -9,6 +9,7 @@ export type TFormErrorMessagesTypes = Record< | 'general' | 'name' | 'password' + | 'special_characters' | 'po_box' | 'phone' | 'postcode' diff --git a/packages/wallets/src/constants/password.ts b/packages/wallets/src/constants/password.ts index fe969b4058b7..71a54f34e5be 100644 --- a/packages/wallets/src/constants/password.ts +++ b/packages/wallets/src/constants/password.ts @@ -42,7 +42,7 @@ export const getPasswordErrorMessage = () => ({ }), missingCharacter: localize('Password should have lower and uppercase English letters with numbers.'), missingCharacterMT5: localize( - 'Please include at least 1 special character such as ( _ @ ? ! / # ) in your password.' + 'Password must have at least one of these special characters: !&‘’*-“%+.#&(),:;?=@<>\\[]^_{}|~' ), PasswordError: localize('That password is incorrect. Please try again.'), });