diff --git a/src/Components/CreateImageWizard/CreateImageWizard.tsx b/src/Components/CreateImageWizard/CreateImageWizard.tsx
index 0fd25fce6..6e49c8991 100644
--- a/src/Components/CreateImageWizard/CreateImageWizard.tsx
+++ b/src/Components/CreateImageWizard/CreateImageWizard.tsx
@@ -41,6 +41,7 @@ import {
useRegistrationValidation,
useHostnameValidation,
useKernelValidation,
+ useUsersValidation,
} from './utilities/useValidation';
import {
isAwsAccountIdValid,
@@ -230,6 +231,8 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
const firstBootValidation = useFirstBootValidation();
// Details
const detailsValidation = useDetailsValidation();
+ // Users
+ const usersValidation = useUsersValidation();
let startIndex = 1; // default index
if (isEdit) {
@@ -462,7 +465,10 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
key="wizard-users"
isHidden={!isUsersEnabled}
footer={
-
+
}
>
diff --git a/src/Components/CreateImageWizard/steps/Users/component/UserInfo.tsx b/src/Components/CreateImageWizard/steps/Users/component/UserInfo.tsx
index 48b433cdc..ad633f724 100644
--- a/src/Components/CreateImageWizard/steps/Users/component/UserInfo.tsx
+++ b/src/Components/CreateImageWizard/steps/Users/component/UserInfo.tsx
@@ -13,6 +13,7 @@ import {
setUserPasswordByIndex,
setUserSshKeyByIndex,
} from '../../../../../store/wizardSlice';
+import { useUsersValidation } from '../../../utilities/useValidation';
import { HookValidatedInput } from '../../../ValidatedTextInput';
const UserInfo = () => {
const dispatch = useAppDispatch();
@@ -45,10 +46,7 @@ const UserInfo = () => {
dispatch(setUserSshKeyByIndex({ index: index, sshKey: value }));
};
- const stepValidation = {
- errors: {},
- disabledNext: false,
- };
+ const stepValidation = useUsersValidation();
return (
<>
diff --git a/src/Components/CreateImageWizard/utilities/useValidation.tsx b/src/Components/CreateImageWizard/utilities/useValidation.tsx
index d8c3f8a0f..807842536 100644
--- a/src/Components/CreateImageWizard/utilities/useValidation.tsx
+++ b/src/Components/CreateImageWizard/utilities/useValidation.tsx
@@ -20,6 +20,10 @@ import {
selectRegistrationType,
selectHostname,
selectKernel,
+ selectUserNameByIndex,
+ selectUsers,
+ selectUserPasswordByIndex,
+ selectUserSshKeyByIndex,
} from '../../../store/wizardSlice';
import {
getDuplicateMountPoints,
@@ -29,6 +33,7 @@ import {
isSnapshotValid,
isHostnameValid,
isKernelNameValid,
+ isUserNameValid,
} from '../validators';
export type StepValidation = {
@@ -173,6 +178,31 @@ export function useKernelValidation(): StepValidation {
return { errors: {}, disabledNext: false };
}
+export function useUsersValidation(): StepValidation {
+ const index = 0;
+ const userNameSelector = selectUserNameByIndex(index);
+ const userName = useAppSelector(userNameSelector);
+ const userPasswordSelector = selectUserPasswordByIndex(index);
+ const userPassword = useAppSelector(userPasswordSelector);
+ const userSshKeySelector = selectUserSshKeyByIndex(index);
+ const userSshKey = useAppSelector(userSshKeySelector);
+ const users = useAppSelector(selectUsers);
+ const canProceed =
+ // Case 1: there is no users
+ users.length === 0 ||
+ // Case 2: All fields are empty
+ (userName === '' && userPassword === '' && userSshKey === '') ||
+ // Case 3: userName is valid
+ (userName && isUserNameValid(userName));
+
+ return {
+ errors: {
+ userName: !isUserNameValid(userName) ? 'Invalid user name' : '',
+ },
+ disabledNext: !canProceed,
+ };
+}
+
export function useDetailsValidation(): StepValidation {
const name = useAppSelector(selectBlueprintName);
const description = useAppSelector(selectBlueprintDescription);
diff --git a/src/Components/CreateImageWizard/validators.ts b/src/Components/CreateImageWizard/validators.ts
index 05e6a2f8b..c9edc905f 100644
--- a/src/Components/CreateImageWizard/validators.ts
+++ b/src/Components/CreateImageWizard/validators.ts
@@ -57,6 +57,17 @@ export const isFileSystemConfigValid = (partitions: Partition[]) => {
return duplicates.length === 0;
};
+export const isUserNameValid = (userName: string) => {
+ if (userName === undefined) return false;
+ const isLengthValid = userName.length <= 32;
+ const isNotNumericOnly = !/^\d+$/.test(userName);
+ const isPatternValid = /^[a-zA-Z0-9][a-zA-Z0-9_.-]*[a-zA-Z0-9_$]$/.test(
+ userName
+ );
+
+ return isLengthValid && isNotNumericOnly && isPatternValid;
+};
+
export const getDuplicateMountPoints = (partitions: Partition[]): string[] => {
const mountPointSet: Set = new Set();
const duplicates: string[] = [];
diff --git a/src/test/Components/CreateImageWizard/steps/Users/Users.test.tsx b/src/test/Components/CreateImageWizard/steps/Users/Users.test.tsx
index 4439b1c2e..813fed11b 100644
--- a/src/test/Components/CreateImageWizard/steps/Users/Users.test.tsx
+++ b/src/test/Components/CreateImageWizard/steps/Users/Users.test.tsx
@@ -72,6 +72,24 @@ const addValidUser = async () => {
await waitFor(() => expect(nextButton).toBeEnabled());
};
+const addInvalidUser = async () => {
+ const user = userEvent.setup();
+ const addUser = await screen.findByRole('button', { name: /add a user/i });
+ expect(addUser).toBeEnabled();
+ await waitFor(() => user.click(addUser));
+ const enterUserName = screen.getByRole('textbox', {
+ name: /blueprint user name/i,
+ });
+ const nextButton = await getNextButton();
+ await waitFor(() => user.type(enterUserName, '..'));
+ await waitFor(() => expect(enterUserName).toHaveValue('..'));
+ const enterSshKey = await screen.findByRole('textbox', {
+ name: /public SSH key/i,
+ });
+ await waitFor(() => user.type(enterSshKey, 'ssh-rsa d'));
+ await waitFor(() => expect(nextButton).toBeDisabled());
+};
+
describe('Step Users', () => {
beforeEach(async () => {
vi.clearAllMocks();
@@ -145,6 +163,16 @@ describe('Step Users', () => {
expect(receivedRequest).toEqual(expectedRequest);
});
});
+
+ test('with invalid name', async () => {
+ await renderCreateMode();
+ await goToRegistrationStep();
+ await clickRegisterLater();
+ await goToUsersStep();
+ await addInvalidUser();
+ const invalidUserMessage = screen.getByText(/invalid user name/i);
+ await waitFor(() => expect(invalidUserMessage));
+ });
});
describe('Users edit mode', () => {