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);