diff --git a/packages/manager/apps/sap-features-hub/public/translations/installation/Messages_fr_FR.json b/packages/manager/apps/sap-features-hub/public/translations/installation/Messages_fr_FR.json index d41a75758c3d..d0a7f6a36a33 100644 --- a/packages/manager/apps/sap-features-hub/public/translations/installation/Messages_fr_FR.json +++ b/packages/manager/apps/sap-features-hub/public/translations/installation/Messages_fr_FR.json @@ -47,7 +47,8 @@ "system_title": "Précisez les détails de votre futur système SAP", "system_subtitle": "Saisissez les détails de votre futur système SAP correspondant à vos besoins.", "system_sid_validation_message": "Un SID doit comporter 3 caractères alphanumériques en majuscule et commence par une lettre.", - "system_password_validation_message": "Le mot de passe doit contenir au moins 8 caractères, incluant une lettre minuscule, une lettre majuscule, un chiffre et un caractère spécial parmi : !\"@ $%&/()=?'*+~#-.,;:{[]}\\<>│_", + "system_password_sap_validation_message": "Le mot de passe doit contenir entre 10 et 30 caractères, incluant une lettre minuscule, une lettre majuscule, un chiffre et un caractère spécial excepté \\ (antislash), ' (simple guillemet), \" (double guillemet), ` (backtick), $ (dollar), et ne doit pas comporter de caractères spéciaux successifs", + "system_password_sap_hana_validation_message": "Le mot de passe doit contenir entre 10 et 30 caractères, incluant une lettre minuscule, une lettre majuscule, un chiffre et un caractère spécial parmi _ (tiret du bas), # (dièse), @ (arobase), $ (dollar), ! (point d'exclamation), ne doit pas démarrer par un chiffre ou un tiret du bas (_), ni comporter de caractères spéciaux successifs", "system_password_sap_master_helper": "Sera utilisé pour tous les utilisateurs SAP demandés par l'installeur (DDIC, sapadm, etc.)", "system_cta": "Saisissez l'emplacement des sources SAP", "source_summary": "Sources d'installation", diff --git a/packages/manager/apps/sap-features-hub/src/pages/installation/stepSystemInformation/InstallationStepSystemInformation.page.tsx b/packages/manager/apps/sap-features-hub/src/pages/installation/stepSystemInformation/InstallationStepSystemInformation.page.tsx index 8f06a11e1e08..972efb5e729a 100644 --- a/packages/manager/apps/sap-features-hub/src/pages/installation/stepSystemInformation/InstallationStepSystemInformation.page.tsx +++ b/packages/manager/apps/sap-features-hub/src/pages/installation/stepSystemInformation/InstallationStepSystemInformation.page.tsx @@ -10,7 +10,11 @@ import { import { useInstallationFormContext } from '@/context/InstallationForm.context'; import { TextField } from '@/components/Form/TextField.component'; import { getSystemFormData } from '@/utils/formStepData'; -import { isValidInput, isValidSapPassword } from '@/utils/formValidation'; +import { isValidInput } from '@/utils/formValidation'; +import { + isValidSapPassword, + isValidSapHanaPassword, +} from '@/utils/passwordValidation'; import FormLayout from '@/components/Form/FormLayout.component'; import { FORM_LABELS } from '@/constants/form.constants'; import { TRACKING } from '@/tracking.constants'; @@ -39,7 +43,6 @@ export default function InstallationStepSystemInformation() { ); const sidRule = t('system_sid_validation_message'); - const pwdRule = t('system_password_validation_message'); return ( ))} {t('passwords')} - {pwdRule} - {SYSTEM_PASSWORD_INPUTS.map(({ name, helperKey, ...inputProps }) => ( - { - const isValid = isValidSapPassword(e.detail.value as string); - setValues((val) => ({ ...val, [name]: e.detail.value })); - setErrors((err) => ({ ...err, [name]: isValid ? '' : pwdRule })); - }} - value={values[name]} - error={values[name] && errors[name]} - helperText={t(helperKey)} - {...inputProps} - /> - ))} + {SYSTEM_PASSWORD_INPUTS.map( + ({ name, helperKey, passwordType, ...inputProps }) => ( + { + const isValid = + passwordType === 'sapHana' + ? isValidSapHanaPassword(e.detail.value as string) + : isValidSapPassword(e.detail.value as string); + const rule = + passwordType === 'sapHana' + ? t('system_password_sap_hana_validation_message') + : t('system_password_sap_validation_message'); + + setValues((val) => ({ ...val, [name]: e.detail.value })); + setErrors((err) => ({ ...err, [name]: isValid ? '' : rule })); + }} + value={values[name]} + error={values[name] && errors[name]} + helperText={t(helperKey)} + {...inputProps} + /> + ), + )} ); } diff --git a/packages/manager/apps/sap-features-hub/src/pages/installation/stepSystemInformation/installationStepSystemInformation.constants.tsx b/packages/manager/apps/sap-features-hub/src/pages/installation/stepSystemInformation/installationStepSystemInformation.constants.tsx index b1841d5b6841..dd8719b69839 100644 --- a/packages/manager/apps/sap-features-hub/src/pages/installation/stepSystemInformation/installationStepSystemInformation.constants.tsx +++ b/packages/manager/apps/sap-features-hub/src/pages/installation/stepSystemInformation/installationStepSystemInformation.constants.tsx @@ -2,6 +2,12 @@ import { SystemForm, TextInputData } from '@/types/form.type'; import { FORM_LABELS } from '@/constants/form.constants'; import { LABELS } from '@/utils/label.constants'; +type SapHanaPasswordInputs = Array< + TextInputData & { + passwordType: 'sap' | 'sapHana'; + } +>; + // A SAP SID consists of three uppercase alphanumeric characters and starts with a letter. const SAP_SID_PATTERN = /^[A-Z][A-Z0-9]{2}$/.source; @@ -18,26 +24,30 @@ export const SYSTEM_TEXT_INPUTS: TextInputData[] = [ }, ] as const; -export const SYSTEM_PASSWORD_INPUTS: TextInputData[] = [ +export const SYSTEM_PASSWORD_INPUTS: SapHanaPasswordInputs = [ { name: 'masterSapPassword', label: FORM_LABELS.masterSapPassword, helperKey: 'system_password_sap_master_helper', validator: { isRequired: true }, + passwordType: 'sap', }, { name: 'masterSapHanaPassword', label: FORM_LABELS.masterSapHanaPassword, validator: { isRequired: true }, + passwordType: 'sapHana', }, { name: 'sidadmPassword', label: FORM_LABELS.sidadmPassword, validator: { isRequired: true }, + passwordType: 'sap', }, { name: 'systemPassword', label: FORM_LABELS.systemPassword, validator: { isRequired: true }, + passwordType: 'sapHana', }, ] as const; diff --git a/packages/manager/apps/sap-features-hub/src/routes/routes.tsx b/packages/manager/apps/sap-features-hub/src/routes/routes.tsx index eb381cd9d3db..a9e37e25f08f 100644 --- a/packages/manager/apps/sap-features-hub/src/routes/routes.tsx +++ b/packages/manager/apps/sap-features-hub/src/routes/routes.tsx @@ -33,7 +33,7 @@ export const Routes: any = [ ), handle: { tracking: { - pageName: 'dashboard', + pageName: 'catalog', pageType: PageType.dashboard, }, }, diff --git a/packages/manager/apps/sap-features-hub/src/utils/formValidation.spec.ts b/packages/manager/apps/sap-features-hub/src/utils/formValidation.spec.ts index fa13eb4fdc31..a4f6c0657972 100644 --- a/packages/manager/apps/sap-features-hub/src/utils/formValidation.spec.ts +++ b/packages/manager/apps/sap-features-hub/src/utils/formValidation.spec.ts @@ -1,11 +1,6 @@ import { describe, expect, test } from 'vitest'; import { OdsInputChangeEvent } from '@ovhcloud/ods-components'; -import { - isValidDomain, - isValidInput, - isValidSapPassword, - isValidUrl, -} from './formValidation'; +import { isValidDomain, isValidInput, isValidUrl } from './formValidation'; describe('isValid test suite', () => { it('should return true if input is valid', () => { @@ -58,21 +53,3 @@ describe('isValidDomain test suite', () => { expect(isValidDomain(input)).toBe(expected); }); }); - -describe('isValidSapPassword test suite', () => { - test.each([ - ['Password1!', true], - ["Pa1!@$%&/()=? '*+~#-.,;:{[]}<>_|", true], - ['Short1!', false], - ['NOLOWER1!', false], - ['noupper1!', false], - ['Nonumber!', false], - ['Nospecial1', false], - ['Invalid1char€', false], - ])( - 'should evaluate validity of SAP password %s as: %s', - (input, expected) => { - expect(isValidSapPassword(input)).toBe(expected); - }, - ); -}); diff --git a/packages/manager/apps/sap-features-hub/src/utils/formValidation.ts b/packages/manager/apps/sap-features-hub/src/utils/formValidation.ts index 2b1c3955f1e3..e935a317e9c1 100644 --- a/packages/manager/apps/sap-features-hub/src/utils/formValidation.ts +++ b/packages/manager/apps/sap-features-hub/src/utils/formValidation.ts @@ -18,15 +18,3 @@ export const isValidDomain = (value: string) => { value, ); }; - -export const isValidSapPassword = (password: string): boolean => { - if (password?.length < 8) return false; - if (!/[a-z]/.test(password)) return false; - if (!/[A-Z]/.test(password)) return false; - if (!/\d/.test(password)) return false; - if (!/[!" @$%&/()=?'*+~#\-.,;:{[\]}<>_|]/.test(password)) return false; - if (!/^[A-Za-z\d!" @$%&/()=?'*+~#\-.,;:{[\]}<>_|]*$/.test(password)) { - return false; - } - return true; -}; diff --git a/packages/manager/apps/sap-features-hub/src/utils/passwordValidation.spec.ts b/packages/manager/apps/sap-features-hub/src/utils/passwordValidation.spec.ts new file mode 100644 index 000000000000..2b38141398e8 --- /dev/null +++ b/packages/manager/apps/sap-features-hub/src/utils/passwordValidation.spec.ts @@ -0,0 +1,57 @@ +import { describe, test, expect } from 'vitest'; +import { + isValidSapPassword, + isValidSapHanaPassword, +} from './passwordValidation'; + +describe('common sapPassword & sapHanaPassword requirements', () => { + test.each([ + ['Short123!', false], + ['WITHOUTLOWERCASE1!', false], + ['withoutuppercase1!', false], + ['withoutnumber!', false], + ['With2ConsecutiveSpecialCharacters!?', false], + ])('should be invalid for both SAP password types: %s', (input) => { + expect(isValidSapPassword(input)).toBe(false); + expect(isValidSapHanaPassword(input)).toBe(false); + }); +}); + +describe('isValidSapPassword test suite', () => { + test.each([ + ['Password1WithoutSpecialCharacter', false], + ['Password1\\', false], + ["Password1'", false], + ['Password1"', false], + ['Password1`', false], + ['Password1$', false], + + ['Password1!', true], + ['1PasswordStartingWithDigit_', true], + ['_1PasswordStartingWithUnderscore', true], + ])( + 'should evaluate validity of SAP password %s as: %s', + (input, expected) => { + expect(isValidSapPassword(input)).toBe(expected); + }, + ); +}); + +describe('isValidSapHanaPassword test suite', () => { + test.each([ + ['1PasswordStartingWithDigit_', false], + ['_1PasswordStartingWithUnderscore', false], + ['Password1WithoutRequiredSpecialChar,?;.:/=+(){}[]&"\'`§°-€£%', false], + + ['Password1!', true], + ['Password1_', true], + ['Password1#', true], + ['Password1@', true], + ['Password1$', true], + ])( + 'should evaluate validity of SAP_HANA password %s as: %s', + (input, expected) => { + expect(isValidSapHanaPassword(input)).toBe(expected); + }, + ); +}); diff --git a/packages/manager/apps/sap-features-hub/src/utils/passwordValidation.ts b/packages/manager/apps/sap-features-hub/src/utils/passwordValidation.ts new file mode 100644 index 000000000000..dad6df6ae78a --- /dev/null +++ b/packages/manager/apps/sap-features-hub/src/utils/passwordValidation.ts @@ -0,0 +1,41 @@ +type Rule = (password: string) => boolean; + +export const sapPasswordRules = { + hasMinLength: (pwd: string) => pwd?.length >= 10, + hasLowerCase: (pwd: string) => /[a-z]/.test(pwd), + hasUpperCase: (pwd: string) => /[A-Z]/.test(pwd), + hasDigit: (pwd: string) => /\d/.test(pwd), + hasSpecialChar: (pwd: string) => /[^A-Za-z0-9]/.test(pwd), + doesNotStartWithDigitOrUnderscore: (pwd: string) => !/^[0-9_]/.test(pwd), + doesNotHaveConsecutiveSpecialChars: (pwd: string) => + !/[^A-Za-z0-9]{2,}/.test(pwd), + doesNotHaveSapForbiddenChar: (pwd: string) => !/[\\'"`$]/.test(pwd), + hasRequiredSapHanaSpecialChar: (pwd: string) => /[_#@$!]/.test(pwd), +}; + +const baseRules: Rule[] = [ + sapPasswordRules.hasMinLength, + sapPasswordRules.hasLowerCase, + sapPasswordRules.hasUpperCase, + sapPasswordRules.hasDigit, + sapPasswordRules.doesNotHaveConsecutiveSpecialChars, +]; +const sapRules: Rule[] = [ + ...baseRules, + sapPasswordRules.hasSpecialChar, + sapPasswordRules.doesNotHaveSapForbiddenChar, +]; +const sapHanaRules: Rule[] = [ + ...baseRules, + sapPasswordRules.hasRequiredSapHanaSpecialChar, + sapPasswordRules.doesNotStartWithDigitOrUnderscore, +]; + +const validatePassword = (password: string, rules: Rule[]): boolean => + rules.every((rule) => rule(password)); + +export const isValidSapPassword = (password: string): boolean => + validatePassword(password, sapRules); + +export const isValidSapHanaPassword = (password: string): boolean => + validatePassword(password, sapHanaRules);