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

refactor(web): simplify page to enforce root password #1821

Merged
merged 1 commit into from
Dec 9, 2024
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
6 changes: 0 additions & 6 deletions web/src/assets/styles/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,3 @@ button.remove-link:hover {
--pf-v5-c-notification-drawer__list-item--before--BackgroundColor: none;
}
}

form#rootAuthMethods {
.pf-v5-c-file-upload__file-select {
display: none;
}
}
38 changes: 5 additions & 33 deletions web/src/components/users/RootAuthMethodsPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,12 @@ jest.mock("~/queries/users", () => ({
}));

describe("RootAuthMethodsPage", () => {
it("allows setting a root authentication method", async () => {
it("allows setting a root password", async () => {
const { user } = installerRender(<RootAuthMethodsPage />);
const passwordInput = screen.getByLabelText("Password");
const sshKeyTextarea = screen.getByLabelText("SSH public key");
const passwordInput = screen.getByLabelText("Password for root user");
const acceptButton = screen.getByRole("button", { name: "Accept" });

// There must be an upload button too (behavior not covered here);
screen.getByRole("button", { name: "upload" });

// The Accept button must be enable only when at least one authentication
// method is defined
// The Accept button must be enable only when password has some value
expect(acceptButton).toHaveAttribute("disabled");

await user.type(passwordInput, "s3cr3t");
Expand All @@ -52,32 +47,9 @@ describe("RootAuthMethodsPage", () => {
await user.clear(passwordInput);
expect(acceptButton).toHaveAttribute("disabled");

await user.type(sshKeyTextarea, "FAKE SSH KEY");
expect(acceptButton).not.toHaveAttribute("disabled");

await user.clear(sshKeyTextarea);
expect(acceptButton).toHaveAttribute("disabled");

await user.type(passwordInput, "s3cr3t");
await user.type(sshKeyTextarea, "FAKE SSH KEY");
expect(acceptButton).not.toHaveAttribute("disabled");

// Request setting defined root method when Accept button is clicked
await user.click(acceptButton);
expect(mockRootUserMutation.mutateAsync).toHaveBeenCalledWith({
password: "s3cr3t",
encryptedPassword: false,
sshkey: "FAKE SSH KEY",
});

await user.clear(passwordInput);
await user.click(acceptButton);
expect(mockRootUserMutation.mutateAsync).toHaveBeenCalledWith({
sshkey: "FAKE SSH KEY",
});

await user.clear(sshKeyTextarea);
await user.type(passwordInput, "t0ps3cr3t");

// Request setting root password when Accept button is clicked
await user.click(acceptButton);
expect(mockRootUserMutation.mutateAsync).toHaveBeenCalledWith({
password: "t0ps3cr3t",
Expand Down
111 changes: 23 additions & 88 deletions web/src/components/users/RootAuthMethodsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,11 @@
*/

import React, { useRef, useState } from "react";
import {
Button,
FileUpload,
Flex,
Form,
FormGroup,
FormHelperText,
HelperText,
HelperTextItem,
} from "@patternfly/react-core";
import { Flex, Form, FormGroup } from "@patternfly/react-core";
import { useLocation, useNavigate } from "react-router-dom";
import { Center } from "~/components/layout";
import { Page, PasswordInput } from "~/components/core";
import { useRootUserMutation } from "~/queries/users";
import { RootUserChanges } from "~/types/users";
import { ROOT as PATHS } from "~/routes/paths";
import { isEmpty } from "~/utils";
import { _ } from "~/i18n";
Expand All @@ -55,45 +45,18 @@ function RootAuthMethodsPage() {
const location = useLocation();
const setRootUser = useRootUserMutation();
const [password, setPassword] = useState("");
const [sshKey, setSSHKey] = useState("");
const [isUploading, setIsUploading] = useState(false);

const startUploading = () => setIsUploading(true);
const stopUploading = () => setIsUploading(false);
const clearKey = () => setSSHKey("");

const isFormValid = !isEmpty(password) || !isEmpty(sshKey);
const uploadFile = () => document.getElementById("sshKey-browse-button").click();
const isFormValid = !isEmpty(password);

const accept = async (e: React.SyntheticEvent) => {
e.preventDefault();
if (isEmpty(password)) return;

const data: Partial<RootUserChanges> = {};

if (!isEmpty(password)) {
data.password = password;
data.encryptedPassword = false;
}

if (!isEmpty(sshKey)) {
data.sshkey = sshKey;
}

if (isEmpty(data)) return;

await setRootUser.mutateAsync(data);
await setRootUser.mutateAsync({ password, encryptedPassword: false });

navigate(location.state?.from || PATHS.root, { replace: true });
};

// TRANSLATORS: %s will be replaced by a link with the text "upload".
const [sshKeyStartHelperText, sshKeyEndHelperText] = _(
"Write, paste, drop, or %s a SSH public key file in the above textarea.",
).split("%s");
// TRANSLATORS: this "upload" is a commanding verb, in the %s place of
// "Write, paste, drop, or %s a SSH public key file in the above textarea."
const uploadLinkText = _("upload");

return (
<Page>
<Page.Content>
Expand All @@ -102,58 +65,30 @@ function RootAuthMethodsPage() {
headerLevel="h2"
title={_("Setup root user authentication")}
description={
<p className={textStyles.fontSizeXl}>
{_(
"You must define at least one authentication method for the root user. You can still edit them anytime before the installation.",
)}
</p>
<Flex direction={{ default: "column" }} gap={{ default: "gapSm" }}>
<p className={textStyles.fontSizeLg}>
{_("Provide a password to ensure administrative access to the system.")}
</p>
<p className={textStyles.fontSizeMd}>
{_(
"You can change it or select another authentication method in the 'Users' section before installing.",
)}
</p>
</Flex>
}
pfCardProps={{ isCompact: false }}
pfCardBodyProps={{ isFilled: true }}
>
<Form id="rootAuthMethods" onSubmit={accept}>
<Flex direction={{ default: "column" }} rowGap={{ default: "rowGapXl" }}>
<FormGroup fieldId="rootPassword" label={_("Password")}>
<PasswordInput
inputRef={passwordRef}
id="rootPassword"
value={password}
className={sizingStyles.wAuto}
onChange={(_, value) => setPassword(value)}
/>
</FormGroup>
<FormGroup fieldId="sshKey" label={_("SSH public key")}>
<FileUpload
id="sshKey"
value={sshKey}
type="text"
aria-label={_(
"Write, paste, or drop an SSH public key here. You can also upload it by using the link below.",
)}
// TRANSLATORS: push button label
browseButtonText={_("Upload")}
// TRANSLATORS: push button label, clears the related input field
clearButtonText={_("Clear")}
isLoading={isUploading}
onDataChange={(_, value) => setSSHKey(value)}
onTextChange={(_, value) => setSSHKey(value)}
onReadStarted={startUploading}
onReadFinished={stopUploading}
onClearClick={clearKey}
/>
<FormHelperText>
<HelperText>
<HelperTextItem variant="indeterminate">
{sshKeyStartHelperText}
<Button variant="link" isInline onClick={uploadFile}>
{uploadLinkText}
</Button>
{sshKeyEndHelperText}
</HelperTextItem>
</HelperText>
</FormHelperText>
</FormGroup>
</Flex>
<FormGroup fieldId="rootPassword" label={_("Password for root user")}>
<PasswordInput
inputRef={passwordRef}
id="rootPassword"
value={password}
className={sizingStyles.w_50OnMd}
onChange={(_, value) => setPassword(value)}
/>
</FormGroup>
</Form>
</Page.Section>
</Center>
Expand Down
Loading