diff --git a/src/Components/CreateImageWizard/utilities/useValidation.tsx b/src/Components/CreateImageWizard/utilities/useValidation.tsx index dc393d026..22da0c7e4 100644 --- a/src/Components/CreateImageWizard/utilities/useValidation.tsx +++ b/src/Components/CreateImageWizard/utilities/useValidation.tsx @@ -34,6 +34,7 @@ import { isHostnameValid, isKernelNameValid, isUserNameValid, + isSshKeyValid, } from '../validators'; export type StepValidation = { @@ -182,10 +183,6 @@ export function useUsersValidation(): StepValidation { const index = 0; const userNameSelector = selectUserNameByIndex(index); const userName = useAppSelector(userNameSelector); -<<<<<<< HEAD -======= - const userNameValid = isUserNameValid(userName); ->>>>>>> d28c394f (add unit test to invalid username) const userPasswordSelector = selectUserPasswordByIndex(index); const userPassword = useAppSelector(userPasswordSelector); const userSshKeySelector = selectUserSshKeyByIndex(index); @@ -196,16 +193,20 @@ export function useUsersValidation(): StepValidation { users.length === 0 || // Case 2: All fields are empty (userName === '' && userPassword === '' && userSshKey === '') || - // Case 3: userName is valid -<<<<<<< HEAD - (userName && isUserNameValid(userName)); -======= - (userName && userNameValid); ->>>>>>> d28c394f (add unit test to invalid username) + // Case 3: userName is valid and SshKey is valid + (userName && + isUserNameValid(userName) && + userSshKey && + isSshKeyValid(userSshKey)); return { errors: { userName: !isUserNameValid(userName) ? 'Invalid user name' : '', + userSshKey: !userSshKey + ? '' + : !isSshKeyValid(userSshKey) + ? 'Invalid SSH key' + : '', }, disabledNext: !canProceed, }; diff --git a/src/Components/CreateImageWizard/validators.ts b/src/Components/CreateImageWizard/validators.ts index c9edc905f..7352f900c 100644 --- a/src/Components/CreateImageWizard/validators.ts +++ b/src/Components/CreateImageWizard/validators.ts @@ -68,6 +68,17 @@ export const isUserNameValid = (userName: string) => { return isLengthValid && isNotNumericOnly && isPatternValid; }; +export const isSshKeyValid = (sshKey: string) => { + // 1. Key types: ssh-rsa, ssh-dss, ssh-ed25519, or ecdsa-sha2-nistp(256|384|521). + // 2. Base64-encoded key material. + // 3. Optional comment at the end. + const isPatternValid = + /^(ssh-(rsa|dss|ed25519)|ecdsa-sha2-nistp(256|384|521))\s+[A-Za-z0-9+=]+(\s+\S+)?$/.test( + sshKey + ); + return 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 6ffe688ef..381c8250f 100644 --- a/src/test/Components/CreateImageWizard/steps/Users/Users.test.tsx +++ b/src/test/Components/CreateImageWizard/steps/Users/Users.test.tsx @@ -24,6 +24,7 @@ import { } from '../../wizardTestUtils'; let router: RemixRouter | undefined = undefined; +const validUserName = 'best'; const goToUsersStep = async () => { await clickNext(); @@ -53,41 +54,29 @@ const clickRevisitButton = async () => { await waitFor(() => user.click(revisitButton)); }; -const addValidUser = async () => { +const clickAddUser = 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, 'best')); - await waitFor(() => expect(enterUserName).toHaveValue('best')); +}; + +const addSshKey = async (sshKey: string) => { + const user = userEvent.setup(); const enterSshKey = await screen.findByRole('textbox', { name: /public SSH key/i, }); - await waitFor(() => user.type(enterSshKey, 'ssh-rsa d')); - await waitFor(() => expect(enterSshKey).toHaveValue('ssh-rsa d')); - await waitFor(() => expect(nextButton).toBeEnabled()); + await waitFor(() => user.type(enterSshKey, sshKey)); + await waitFor(() => expect(enterSshKey).toHaveValue(sshKey)); }; -const addInvalidUser = async () => { +const addUserName = async (userName: string) => { 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()); + await waitFor(() => user.type(enterUserName, userName)); + await waitFor(() => expect(enterUserName).toHaveValue(userName)); }; describe('Step Users', () => { @@ -124,59 +113,44 @@ describe('Step Users', () => { }); test('revisit step button on Review works', async () => { + const sshKey = 'ssh-rsa d'; await renderCreateMode(); await goToRegistrationStep(); await clickRegisterLater(); await goToUsersStep(); - await addValidUser(); + await clickAddUser(); + await addUserName(validUserName); + await addSshKey(sshKey); + const nextButton = await getNextButton(); + await waitFor(() => expect(nextButton).toBeEnabled()); await goToReviewStep(); await clickRevisitButton(); await screen.findByRole('heading', { name: /Users/ }); }); describe('User request generated correctly', () => { - test('with valid name and password', async () => { - await renderCreateMode(); - await goToRegistrationStep(); - await clickRegisterLater(); - await goToUsersStep(); - await addValidUser(); - await goToReviewStep(); - // informational modal pops up in the first test only as it's tied - // to a 'imageBuilder.saveAndBuildModalSeen' variable in localStorage - await openAndDismissSaveAndBuildModal(); - const receivedRequest = await interceptBlueprintRequest(CREATE_BLUEPRINT); - - const expectedRequest = { - ...blueprintRequest, - customizations: { - users: [ - { - name: 'best', - ssh_key: 'ssh-rsa d', - }, - ], - }, - }; - - await waitFor(() => { - expect(receivedRequest).toEqual(expectedRequest); - }); - }); - -<<<<<<< HEAD test('with valid name, ssh key and checked Administrator checkbox', async () => { const user = userEvent.setup(); + const sshKey = 'ssh-rsa d'; await renderCreateMode(); await goToRegistrationStep(); await clickRegisterLater(); await goToUsersStep(); - await addValidUser(); + await clickAddUser(); + await addUserName(validUserName); + await addSshKey(sshKey); + const nextButton = await getNextButton(); + await waitFor(() => expect(nextButton).toBeEnabled()); + const isAdmin = screen.getByRole('checkbox', { name: /administrator/i, }); user.click(isAdmin); + await goToReviewStep(); + // informational modal pops up in the first test only as it's tied + // to a 'imageBuilder.saveAndBuildModalSeen' variable in localStorage + await openAndDismissSaveAndBuildModal(); const receivedRequest = await interceptBlueprintRequest(CREATE_BLUEPRINT); const expectedRequest = { @@ -191,23 +165,34 @@ describe('Step Users', () => { ], }, }; - await waitFor(() => { expect(receivedRequest).toEqual(expectedRequest); }); }); -======= ->>>>>>> d28c394f (add unit test to invalid username) test('with invalid name', async () => { await renderCreateMode(); await goToRegistrationStep(); await clickRegisterLater(); await goToUsersStep(); - await addInvalidUser(); + await clickAddUser(); + await addUserName('ss.'); + await addSshKey('ssh'); const invalidUserMessage = screen.getByText(/invalid user name/i); await waitFor(() => expect(invalidUserMessage)); }); + + test('with invalid ssh key', async () => { + await renderCreateMode(); + await goToRegistrationStep(); + await clickRegisterLater(); + await goToUsersStep(); + await clickAddUser(); + await addSshKey('ssh'); + await addUserName('bestUser'); + const invalidUserMessage = screen.getByText(/invalid ssh key/i); + await waitFor(() => expect(invalidUserMessage)); + }); }); describe('Users edit mode', () => {