Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wizard: add user name validation (HMS-5285) #2716

Merged
merged 1 commit into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/Components/CreateImageWizard/CreateImageWizard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
useRegistrationValidation,
useHostnameValidation,
useKernelValidation,
useUsersValidation,
} from './utilities/useValidation';
import {
isAwsAccountIdValid,
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -462,7 +465,10 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
key="wizard-users"
isHidden={!isUsersEnabled}
footer={
<CustomWizardFooter disableNext={false} optional={true} />
<CustomWizardFooter
disableNext={usersValidation.disabledNext}
optional={true}
/>
}
>
<UsersStep />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
setUserPasswordByIndex,
setUserSshKeyByIndex,
} from '../../../../../store/wizardSlice';
import { useUsersValidation } from '../../../utilities/useValidation';
import { HookValidatedInput } from '../../../ValidatedTextInput';
const UserInfo = () => {
const dispatch = useAppDispatch();
Expand Down Expand Up @@ -45,10 +46,7 @@ const UserInfo = () => {
dispatch(setUserSshKeyByIndex({ index: index, sshKey: value }));
};

const stepValidation = {
errors: {},
disabledNext: false,
};
const stepValidation = useUsersValidation();

return (
<>
Expand Down
30 changes: 30 additions & 0 deletions src/Components/CreateImageWizard/utilities/useValidation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ import {
selectRegistrationType,
selectHostname,
selectKernel,
selectUserNameByIndex,
selectUsers,
selectUserPasswordByIndex,
selectUserSshKeyByIndex,
} from '../../../store/wizardSlice';
import {
getDuplicateMountPoints,
Expand All @@ -29,6 +33,7 @@ import {
isSnapshotValid,
isHostnameValid,
isKernelNameValid,
isUserNameValid,
} from '../validators';

export type StepValidation = {
Expand Down Expand Up @@ -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);
Expand Down
11 changes: 11 additions & 0 deletions src/Components/CreateImageWizard/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ export const isFileSystemConfigValid = (partitions: Partition[]) => {
return duplicates.length === 0;
};

export const isUserNameValid = (userName: string) => {
mgold1234 marked this conversation as resolved.
Show resolved Hide resolved
if (userName === undefined) return false;
mgold1234 marked this conversation as resolved.
Show resolved Hide resolved
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<string> = new Set();
const duplicates: string[] = [];
Expand Down
28 changes: 28 additions & 0 deletions src/test/Components/CreateImageWizard/steps/Users/Users.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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', () => {
Expand Down
Loading