diff --git a/site/e2e/tests/deployment/security.spec.ts b/site/e2e/tests/deployment/security.spec.ts new file mode 100644 index 0000000000..fe88dcfba2 --- /dev/null +++ b/site/e2e/tests/deployment/security.spec.ts @@ -0,0 +1,71 @@ +import { expect, test, type Page } from "@playwright/test"; +import * as API from "api/api"; +import { setupApiCalls } from "../../api"; + +test("enabled security settings", async ({ page }) => { + await setupApiCalls(page); + + const config = await API.getDeploymentConfig(); + + await page.goto("/deployment/security", { waitUntil: "domcontentloaded" }); + + const flags = [ + "ssh-keygen-algorithm", + "secure-auth-cookie", + "disable-owner-workspace-access", + + "tls-redirect-http-to-https", + "strict-transport-security", + "tls-address", + "tls-allow-insecure-ciphers", + "tls-client-auth", + "tls-enable", + "tls-min-version", + ]; + + for (const flag of flags) { + await verifyConfigFlag(page, config, flag); + } +}); + +const verifyConfigFlag = async ( + page: Page, + config: API.DeploymentConfig, + flag: string, +) => { + const opt = config.options.find((option) => option.flag === flag); + if (opt === undefined) { + throw new Error(`Option with env ${flag} has undefined value.`); + } + + // Map option type to test class name. + let type = "", + value = opt.value; + if (typeof value === "boolean") { + // Boolean options map to string (Enabled/Disabled). + type = value ? "option-enabled" : "option-disabled"; + value = value ? "Enabled" : "Disabled"; + } else if (typeof value === "number") { + type = "option-value-number"; + value = String(value); + } else if (!value || value.length === 0) { + type = "option-value-empty"; + } else if (typeof value === "string") { + type = "option-value-string"; + } else if (typeof value === "object") { + type = "object-array"; + } else { + type = "option-value-json"; + } + + // Special cases + if (opt.flag === "strict-transport-security" && opt.value === 0) { + type = "option-value-string"; + value = "Disabled"; // Display "Disabled" instead of zero seconds. + } + + const configOption = page.locator( + `div.options-table .option-${flag} .${type}`, + ); + await expect(configOption).toHaveText(String(value)); +}; diff --git a/site/src/components/Badges/Badges.tsx b/site/src/components/Badges/Badges.tsx index 152e7735fd..7fc76fb879 100644 --- a/site/src/components/Badges/Badges.tsx +++ b/site/src/components/Badges/Badges.tsx @@ -41,7 +41,11 @@ const styles = { } satisfies Record>; export const EnabledBadge: FC = () => { - return Enabled; + return ( + + Enabled + + ); }; export const EntitledBadge: FC = () => { @@ -95,6 +99,7 @@ export const DisabledBadge: FC = forwardRef< color: theme.experimental.l1.text, }), ]} + className="option-disabled" > Disabled diff --git a/site/src/pages/DeploySettingsPage/Option.tsx b/site/src/pages/DeploySettingsPage/Option.tsx index 9e5e9f7abd..b79aeef3ab 100644 --- a/site/src/pages/DeploySettingsPage/Option.tsx +++ b/site/src/pages/DeploySettingsPage/Option.tsx @@ -34,19 +34,35 @@ export const OptionValue: FC = (props) => { const theme = useTheme(); if (typeof value === "boolean") { - return value ? : ; + return ( +
+ {value ? : } +
+ ); } if (typeof value === "number") { - return {value}; + return ( + + {value} + + ); } if (!value || value.length === 0) { - return Not set; + return ( + + Not set + + ); } if (typeof value === "string") { - return {value}; + return ( + + {value} + + ); } if (typeof value === "object" && !Array.isArray(value)) { @@ -94,7 +110,7 @@ export const OptionValue: FC = (props) => { if (Array.isArray(value)) { return ( -
    +
      {value.map((item) => (
    • {item} @@ -104,7 +120,11 @@ export const OptionValue: FC = (props) => { ); } - return {JSON.stringify(value)}; + return ( + + {JSON.stringify(value)} + + ); }; type OptionConfigProps = HTMLAttributes & { isSource: boolean };