Fleet UI: Consistent URL validation (#9806)

This commit is contained in:
RachelElysia
2023-02-22 09:05:38 -05:00
committed by GitHub
parent b3e8b6007a
commit dbec2f85df
21 changed files with 186 additions and 148 deletions
+1
View File
@@ -0,0 +1 @@
* Update to better URL validation in UI
@@ -55,12 +55,12 @@ const enableJiraPoliciesIntegration = {
},
failing_policies_webhook: {
enable_failing_policies_webhook: false,
destination_url: "ok.com",
destination_url: "https://www.foo.com/bar",
policy_ids: [5, 10],
host_batch_size: 0,
},
vulnerabilities_webhook: {
destination_url: "www.foo.com/bar",
destination_url: "https://www.foo.com/bar",
enable_vulnerabilities_webhook: false,
},
},
@@ -115,12 +115,12 @@ const enableZendeskPoliciesIntegration = {
},
failing_policies_webhook: {
enable_failing_policies_webhook: false,
destination_url: "ok.com",
destination_url: "https://www.foo.com/bar",
policy_ids: [5, 10],
host_batch_size: 0,
},
vulnerabilities_webhook: {
destination_url: "www.foo.com/bar",
destination_url: "https://www.foo.com/bar",
enable_vulnerabilities_webhook: false,
},
},
@@ -508,11 +508,15 @@ describe("Policies flow (seeded)", () => {
cy.findByRole("button", { name: /manage automations/i }).click();
});
cy.getAttached(".manage-automations-modal").within(() => {
// Ensure clicking on slider after modal animation
cy.wait(300); // eslint-disable-line cypress/no-unnecessary-waiting
cy.getAttached(".fleet-slider").click();
cy.getAttached(".fleet-checkbox__input").check({ force: true });
});
cy.getAttached("#webhook-url").click().type("www.foo.com/bar");
cy.getAttached("#webhook-url").click().type("http://www.foo.com/bar");
cy.findByRole("button", { name: /^Save$/ }).click();
// Ensure update
cy.wait(1000); // eslint-disable-line cypress/no-unnecessary-waiting
// Confirm failing policies webhook was added successfully
cy.findByText(/updated policy automations/i).should("exist");
cy.getAttached(".button-wrap").within(() => {
@@ -523,6 +527,8 @@ describe("Policies flow (seeded)", () => {
});
// reset slider for subsequent tests
cy.getAttached(".manage-automations-modal").within(() => {
// Ensure clicking on slider after modal animation
cy.wait(300); // eslint-disable-line cypress/no-unnecessary-waiting
cy.getAttached(".fleet-slider").click();
});
cy.findByRole("button", { name: /^Save$/ }).click();
@@ -194,7 +194,7 @@ describe("App settings flow", () => {
// specifically targeting this one to avoid conflict
// with cypress seeing multiple "metadata url" - one
// in a tooltip, the other as the actual label
cy.getAttached("[for='metadataURL']")
cy.getAttached("[for='metadataUrl']")
.click()
.type("http://github.com/fleetdm/fleet");
@@ -223,7 +223,7 @@ describe("App settings flow", () => {
"https://http.cat/100"
);
cy.getAttached("#metadataURL").should(
cy.getAttached("#metadataUrl").should(
"have.value",
"http://github.com/fleetdm/fleet"
);
@@ -280,7 +280,7 @@ describe("App settings flow", () => {
);
cy.findByText(/single sign-on options/i).click();
cy.getAttached("#metadataURL").should(
cy.getAttached("#metadataUrl").should(
"have.value",
"http://github.com/fleetdm/fleet"
);
+7 -7
View File
@@ -55,12 +55,12 @@ const enableWebhook = {
},
failing_policies_webhook: {
enable_failing_policies_webhook: false,
destination_url: "ok.com",
destination_url: "https://www.foo.com/bar",
policy_ids: [5, 10],
host_batch_size: 0,
},
vulnerabilities_webhook: {
destination_url: "www.foo.com/bar",
destination_url: "http://www.foo.com/bar",
enable_vulnerabilities_webhook: true,
},
},
@@ -115,12 +115,12 @@ const enableJiraSoftwareIntegration = {
},
failing_policies_webhook: {
enable_failing_policies_webhook: false,
destination_url: "ok.com",
destination_url: "https://www.foo.com/bar",
policy_ids: [5, 10],
host_batch_size: 0,
},
vulnerabilities_webhook: {
destination_url: "www.foo.com/bar",
destination_url: "http://www.foo.com/bar",
enable_vulnerabilities_webhook: false,
},
},
@@ -175,12 +175,12 @@ const enableZendeskSoftwareIntegration = {
},
failing_policies_webhook: {
enable_failing_policies_webhook: false,
destination_url: "ok.com",
destination_url: "https://www.foo.com/bar",
policy_ids: [5, 10],
host_batch_size: 0,
},
vulnerabilities_webhook: {
destination_url: "www.foo.com/bar",
destination_url: "http://www.foo.com/bar",
enable_vulnerabilities_webhook: false,
},
},
@@ -259,7 +259,7 @@ describe("Software", () => {
cy.getAttached(".fleet-slider").click();
cy.getAttached("#webhook-radio-btn").next().click();
});
cy.getAttached("#webhook-url").click().type("www.foo.com/bar");
cy.getAttached("#webhook-url").click().type("http://www.foo.com/bar");
cy.intercept("PATCH", "/api/latest/fleet/config", enableWebhook).as(
"createWebhook"
);
+2 -2
View File
@@ -577,13 +577,13 @@ describe("Premium tier - Global Admin user", () => {
cy.getAttached("[id=transparency_url")
.should("have.value", "https://fleetdm.com/transparency")
.clear()
.type("example.com/transparency");
.type("http://example.com/transparency");
cy.findByRole("button", { name: /save/i }).click();
cy.findByText(/successfully updated/i).should("exist");
cy.visit("settings/organization/fleet-desktop");
cy.getAttached("[id=transparency_url").should(
"have.value",
"example.com/transparency"
"http://example.com/transparency"
);
});
});
+4 -4
View File
@@ -422,15 +422,15 @@ Cypress.Commands.add("addUser", (options = {}) => {
Cypress.Commands.add("addDockerHost", (team = "") => {
const serverPort = new URL(Cypress.config().baseUrl).port;
// Get enroll secret
let enrollSecretURL = "/api/latest/fleet/spec/enroll_secret";
let enrollSecretUrl = "/api/latest/fleet/spec/enroll_secret";
if (team === "apples") {
enrollSecretURL = "/api/latest/fleet/teams/1/secrets";
enrollSecretUrl = "/api/latest/fleet/teams/1/secrets";
} else if (team === "oranges") {
enrollSecretURL = "/api/latest/fleet/teams/2/secrets";
enrollSecretUrl = "/api/latest/fleet/teams/2/secrets";
}
cy.request({
url: enrollSecretURL,
url: enrollSecretUrl,
auth: {
bearer: window.localStorage.getItem("FLEET::auth_token"),
},
+3 -3
View File
@@ -79,7 +79,7 @@ const CONFIG_INTEGRATIONS_AUTOMATIONS = {
},
failing_policies_webhook: {
enable_failing_policies_webhook: false,
destination_url: "ok.com",
destination_url: "https://www.foo.com/bar",
policy_ids: [5, 10],
host_batch_size: 0,
},
@@ -224,12 +224,12 @@ const CONFIG_INTEGRATIONS_AUTOMATIONS_DISABLED = {
},
failing_policies_webhook: {
enable_failing_policies_webhook: false,
destination_url: "ok.com",
destination_url: "https://www.foo.com/bar",
policy_ids: [5, 10],
host_batch_size: 0,
},
vulnerabilities_webhook: {
destination_url: "www.foo.com/bar",
destination_url: "http://www.foo.com/bar",
enable_vulnerabilities_webhook: false,
},
},
@@ -32,6 +32,7 @@ class InputField extends Component {
hint: PropTypes.oneOfType([
PropTypes.string,
PropTypes.arrayOf(PropTypes.string),
PropTypes.object,
]),
};
@@ -91,6 +92,7 @@ class InputField extends Component {
blockAutoComplete,
value,
} = this.props;
const { onInputChange } = this;
const shouldShowPasswordClass = type === "password";
const inputClasses = classnames(baseClass, inputClassName, {
@@ -1,13 +1,19 @@
const URL_REGEX_PATTERN = new RegExp(
"^(https?:\\/\\/)?" + // protocol
"((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // domain name
"((\\d{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address
"(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path
"(\\?[;&a-z\\d%_.~+=-]*)?" + // query string
"(\\#[-a-z\\d_]*)?$",
"i"
); // fragment locator
interface IValidUrl {
url: string;
protocol?: string;
}
export default (url: string): boolean => {
return !!URL_REGEX_PATTERN.test(url);
export default ({ url, protocol }: IValidUrl): boolean => {
try {
const newUrl = new URL(url);
if (protocol === "http") {
return newUrl.protocol === "http:" || newUrl.protocol === "https:";
}
if (protocol === "https") {
return newUrl.protocol === "https:";
}
return true;
} catch (e) {
return false;
}
};
+14 -14
View File
@@ -111,36 +111,36 @@ export interface IConfigFormData {
smtpAuthenticationMethod: string;
smtpAuthenticationType: string;
domain: string;
smtpEnableSSLTLS: boolean;
enableStartTLS: boolean;
serverURL: string;
orgLogoURL: string;
smtpEnableSslTls: boolean;
enableStartTls: boolean;
serverUrl: string;
orgLogoUrl: string;
orgName: string;
smtpPassword: string;
smtpPort?: number;
smtpSenderAddress: string;
smtpServer: string;
smtpUsername: string;
verifySSLCerts: boolean;
entityID: string;
issuerURI: string;
idpImageURL: string;
verifySslCerts: boolean;
entityId: string;
issuerUri: string;
idpImageUrl: string;
metadata: string;
metadataURL: string;
metadataUrl: string;
idpName: string;
enableSSO: boolean;
enableSSOIDPLogin: boolean;
enableSMTP: boolean;
enableSso: boolean;
enableSsoIdpLogin: boolean;
enableSmtp: boolean;
enableHostExpiry: boolean;
hostExpiryWindow: number;
disableLiveQuery: boolean;
agentOptions: any;
enableHostStatusWebhook: boolean;
hostStatusWebhookDestinationURL?: string;
hostStatusWebhookDestinationUrl?: string;
hostStatusWebhookHostPercentage?: number;
hostStatusWebhookDaysCount?: number;
enableUsageStatistics: boolean;
transparency_url: string;
transparencyUrl: string;
}
export interface IConfigFeatures {
+24 -24
View File
@@ -2760,20 +2760,20 @@
"index": false
},
{
"name":"issuer2",
"description":"Certificate issuer distinguished name",
"type":"text",
"hidden":true,
"required":false,
"index":false
"name": "issuer2",
"description": "Certificate issuer distinguished name",
"type": "text",
"hidden": true,
"required": false,
"index": false
},
{
"name":"subject2",
"description":"Certificate distinguished name",
"type":"text",
"hidden":true,
"required":false,
"index":false
"name": "subject2",
"description": "Certificate distinguished name",
"type": "text",
"hidden": true,
"required": false,
"index": false
}
]
},
@@ -8196,12 +8196,12 @@
"index": false
},
{
"name":"original_filename",
"description":"(Executable files only) Original filename",
"type":"text",
"hidden":true,
"required":false,
"index":false
"name": "original_filename",
"description": "(Executable files only) Original filename",
"type": "text",
"hidden": true,
"required": false,
"index": false
},
{
"name": "bsd_flags",
@@ -16664,12 +16664,12 @@
"index": false
},
{
"name":"translated",
"description":"Indicates whether the process is running under the Rosetta Translation Environment, yes=1, no=0, error=-1.",
"type":"integer",
"hidden":false,
"required":false,
"index":false
"name": "translated",
"description": "Indicates whether the process is running under the Rosetta Translation Environment, yes=1, no=0, error=-1.",
"type": "integer",
"hidden": false,
"required": false,
"index": false
}
]
},
@@ -12,6 +12,8 @@ import {
import Button from "components/buttons/Button";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import validUrl from "components/forms/validators/valid_url";
import Spinner from "components/Spinner";
const baseClass = "integration-form";
@@ -86,8 +88,8 @@ const IntegrationForm = ({
const validateForm = () => {
let error = null;
if (url.slice(0, 8) !== "https://") {
error = "URL must begin with https://";
if (url && !validUrl({ url, protocol: "https" })) {
error = `${url} is not a valid HTTPS URL`;
}
setUrlError(error);
@@ -226,7 +228,7 @@ const IntegrationForm = ({
tooltip={
"\
To find the Jira project key, head to your project in <br /> \
Jira. Your project key is in URL. For example, in <br /> \
Jira. Your project key is located in the URL. For example, in <br /> \
jira.example.com/projects/JRAEXAMPLE, <br /> \
JRAEXAMPLE is your project key. \
"
@@ -138,7 +138,7 @@
}
&:nth-child(2) {
padding-top: 10px;
padding-top: 6px;
}
&--checkbox {
@@ -24,27 +24,25 @@ const FleetDesktop = ({
isUpdatingSettings,
}: IAppConfigFormProps): JSX.Element => {
const [formData, setFormData] = useState<
Pick<IConfigFormData, "transparency_url">
Pick<IConfigFormData, "transparencyUrl">
>({
transparency_url:
transparencyUrl:
appConfig.fleet_desktop?.transparency_url || DEFAULT_TRANSPARENCY_URL,
});
const [formErrors, setFormErrors] = useState<IAppConfigFormErrors>({});
const handleInputChange = ({ value }: IFormField) => {
setFormData({ transparency_url: value.toString() });
setFormData({ transparencyUrl: value.toString() });
setFormErrors({});
};
const validateForm = () => {
const { transparency_url } = formData;
const { transparencyUrl } = formData;
const errors: IAppConfigFormErrors = {};
if (!transparency_url) {
errors.transparency_url = "Transparency URL name must be present";
} else if (!validUrl(transparency_url)) {
errors.transparency_url = `${transparency_url} is not a valid URL`;
if (transparencyUrl && !validUrl({ url: transparencyUrl })) {
errors.transparency_url = `${transparencyUrl} is not a valid URL`;
}
setFormErrors(errors);
@@ -55,7 +53,7 @@ const FleetDesktop = ({
const formDataForAPI: Pick<IConfig, "fleet_desktop"> = {
fleet_desktop: {
transparency_url: formData.transparency_url,
transparency_url: formData.transparencyUrl,
},
};
@@ -75,10 +73,11 @@ const FleetDesktop = ({
label="Custom transparency URL"
onChange={handleInputChange}
name="transparency_url"
value={formData.transparency_url}
value={formData.transparencyUrl}
parseTarget
onBlur={validateForm}
error={formErrors.transparency_url}
placeholder="https://fleetdm.com/transparency"
/>
<p className={`${baseClass}__component-details`}>
When an end user clicks Transparency in the Fleet Desktop menu, by
@@ -7,6 +7,8 @@ import Checkbox from "components/forms/fields/Checkbox";
import Dropdown from "components/forms/fields/Dropdown";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import validUrl from "components/forms/validators/valid_url";
import Modal from "components/Modal";
import {
IAppConfigFormProps,
@@ -32,7 +34,7 @@ const HostStatusWebhook = ({
enableHostStatusWebhook:
appConfig.webhook_settings.host_status_webhook
.enable_host_status_webhook || false,
hostStatusWebhookDestinationURL:
hostStatusWebhookDestinationUrl:
appConfig.webhook_settings.host_status_webhook.destination_url || "",
hostStatusWebhookHostPercentage:
appConfig.webhook_settings.host_status_webhook.host_percentage ||
@@ -43,7 +45,7 @@ const HostStatusWebhook = ({
const {
enableHostStatusWebhook,
hostStatusWebhookDestinationURL,
hostStatusWebhookDestinationUrl,
hostStatusWebhookHostPercentage,
hostStatusWebhookDaysCount,
} = formData;
@@ -59,8 +61,10 @@ const HostStatusWebhook = ({
const errors: IAppConfigFormErrors = {};
if (enableHostStatusWebhook) {
if (!hostStatusWebhookDestinationURL) {
if (!hostStatusWebhookDestinationUrl) {
errors.destination_url = "Destination URL must be present";
} else if (!validUrl({ url: hostStatusWebhookDestinationUrl })) {
errors.server_url = `${hostStatusWebhookDestinationUrl} is not a valid URL`;
}
if (!hostStatusWebhookDaysCount) {
@@ -92,7 +96,7 @@ const HostStatusWebhook = ({
webhook_settings: {
host_status_webhook: {
enable_host_status_webhook: enableHostStatusWebhook,
destination_url: hostStatusWebhookDestinationURL,
destination_url: hostStatusWebhookDestinationUrl,
host_percentage: hostStatusWebhookHostPercentage,
days_count: hostStatusWebhookDaysCount,
},
@@ -171,8 +175,8 @@ const HostStatusWebhook = ({
placeholder="https://server.com/example"
label="Destination URL"
onChange={handleInputChange}
name="hostStatusWebhookDestinationURL"
value={hostStatusWebhookDestinationURL}
name="hostStatusWebhookDestinationUrl"
value={hostStatusWebhookDestinationUrl}
parseTarget
onBlur={validateForm}
error={formErrors.destination_url}
@@ -41,7 +41,7 @@ const Info = ({
errors.org_name = "Organization name must be present";
}
if (orgLogoURL && !validUrl(orgLogoURL)) {
if (orgLogoURL && !validUrl({ url: orgLogoURL, protocol: "http" })) {
errors.org_logo_url = `${orgLogoURL} is not a valid URL`;
}
@@ -16,15 +16,15 @@ import {
const baseClass = "app-config-form";
interface ISsoFormData {
enableSSO?: boolean;
enableSso?: boolean;
idpName?: string;
entityID?: string;
issuerURI?: string;
idpImageURL?: string;
entityId?: string;
issuerUri?: string;
idpImageUrl?: string;
metadata?: string;
metadataURL?: string;
enableSSOIDPLogin?: boolean;
enableJITProvisioning?: boolean;
metadataUrl?: string;
enableSsoIdpLogin?: boolean;
enableJitProvisioning?: boolean;
}
const Sso = ({
@@ -34,28 +34,28 @@ const Sso = ({
isUpdatingSettings,
}: IAppConfigFormProps): JSX.Element => {
const [formData, setFormData] = useState<ISsoFormData>({
enableSSO: appConfig.sso_settings.enable_sso ?? false,
enableSso: appConfig.sso_settings.enable_sso ?? false,
idpName: appConfig.sso_settings.idp_name ?? "",
entityID: appConfig.sso_settings.entity_id ?? "",
issuerURI: appConfig.sso_settings.issuer_uri ?? "",
idpImageURL: appConfig.sso_settings.idp_image_url ?? "",
entityId: appConfig.sso_settings.entity_id ?? "",
issuerUri: appConfig.sso_settings.issuer_uri ?? "",
idpImageUrl: appConfig.sso_settings.idp_image_url ?? "",
metadata: appConfig.sso_settings.metadata ?? "",
metadataURL: appConfig.sso_settings.metadata_url ?? "",
enableSSOIDPLogin: appConfig.sso_settings.enable_sso_idp_login ?? false,
enableJITProvisioning:
metadataUrl: appConfig.sso_settings.metadata_url ?? "",
enableSsoIdpLogin: appConfig.sso_settings.enable_sso_idp_login ?? false,
enableJitProvisioning:
appConfig.sso_settings.enable_jit_provisioning ?? false,
});
const {
enableSSO,
enableSso,
idpName,
entityID,
issuerURI,
idpImageURL,
entityId,
issuerUri,
idpImageUrl,
metadata,
metadataURL,
enableSSOIDPLogin,
enableJITProvisioning,
metadataUrl,
enableSsoIdpLogin,
enableJitProvisioning,
} = formData;
const [formErrors, setFormErrors] = useState<IAppConfigFormErrors>({});
@@ -67,20 +67,25 @@ const Sso = ({
const validateForm = () => {
const errors: IAppConfigFormErrors = {};
if (enableSSO) {
if (idpImageURL && !validUrl(idpImageURL)) {
errors.idp_image_url = `${idpImageURL} is not a valid URL`;
if (enableSso) {
if (idpImageUrl && !validUrl({ url: idpImageUrl })) {
errors.idp_image_url = `${idpImageUrl} is not a valid URL`;
}
if (metadata === "" && metadataURL === "") {
errors.metadata_url = "Metadata URL must be present";
if (!metadata) {
if (!metadataUrl) {
errors.metadata_url = "Metadata or Metadata URL must be present";
errors.metadata = "Metadata or Metadata URL must be present";
} else if (!validUrl({ url: metadataUrl })) {
errors.metadata_url = `${metadataUrl} is not a valid URL`;
}
}
if (!entityID) {
if (!entityId) {
errors.entity_id = "Entity ID must be present";
}
if (typeof entityID === "string" && entityID.length < 5) {
if (typeof entityId === "string" && entityId.length < 5) {
errors.entity_id = "Entity ID must be 5 or more characters";
}
@@ -94,7 +99,7 @@ const Sso = ({
useEffect(() => {
validateForm();
}, [enableSSO]);
}, [idpImageUrl, metadata, metadataUrl, entityId, idpName]);
const onFormSubmit = (evt: React.MouseEvent<HTMLFormElement>) => {
evt.preventDefault();
@@ -102,15 +107,15 @@ const Sso = ({
// Formatting of API not UI
const formDataToSubmit = {
sso_settings: {
entity_id: entityID?.trim(),
issuer_uri: issuerURI?.trim(),
idp_image_url: idpImageURL?.trim(),
entity_id: entityId?.trim(),
issuer_uri: issuerUri?.trim(),
idp_image_url: idpImageUrl?.trim(),
metadata: metadata?.trim(),
metadata_url: metadataURL?.trim(),
metadata_url: metadataUrl?.trim(),
idp_name: idpName?.trim(),
enable_sso: enableSSO,
enable_sso_idp_login: enableSSOIDPLogin,
enable_jit_provisioning: enableJITProvisioning,
enable_sso: enableSso,
enable_sso_idp_login: enableSsoIdpLogin,
enable_jit_provisioning: enableJitProvisioning,
},
};
@@ -124,8 +129,8 @@ const Sso = ({
<div className={`${baseClass}__inputs`}>
<Checkbox
onChange={handleInputChange}
name="enableSSO"
value={enableSSO}
name="enableSso"
value={enableSso}
parseTarget
>
Enable single sign-on
@@ -153,8 +158,8 @@ const Sso = ({
</span>
}
onChange={handleInputChange}
name="entityID"
value={entityID}
name="entityId"
value={entityId}
parseTarget
onBlur={validateForm}
error={formErrors.entity_id}
@@ -165,8 +170,8 @@ const Sso = ({
<InputField
label="Issuer URI"
onChange={handleInputChange}
name="issuerURI"
value={issuerURI}
name="issuerUri"
value={issuerUri}
parseTarget
tooltip="The issuer URI supplied by the identity provider."
/>
@@ -175,8 +180,8 @@ const Sso = ({
<InputField
label="IDP image URL"
onChange={handleInputChange}
name="idpImageURL"
value={idpImageURL}
name="idpImageUrl"
value={idpImageUrl}
parseTarget
onBlur={validateForm}
error={formErrors.idp_image_url}
@@ -192,6 +197,7 @@ const Sso = ({
value={metadata}
parseTarget
onBlur={validateForm}
error={formErrors.metadata}
tooltip="Metadata provided by the identity provider. Either<br/> metadata or a metadata url must be provided."
/>
</div>
@@ -205,8 +211,8 @@ const Sso = ({
</span>
}
onChange={handleInputChange}
name="metadataURL"
value={metadataURL}
name="metadataUrl"
value={metadataUrl}
parseTarget
onBlur={validateForm}
error={formErrors.metadata_url}
@@ -216,8 +222,8 @@ const Sso = ({
<div className={`${baseClass}__inputs`}>
<Checkbox
onChange={handleInputChange}
name="enableSSOIDPLogin"
value={enableSSOIDPLogin}
name="enableSsoIdpLogin"
value={enableSsoIdpLogin}
parseTarget
>
Allow SSO login initiated by identity provider
@@ -227,8 +233,8 @@ const Sso = ({
<div className={`${baseClass}__inputs`}>
<Checkbox
onChange={handleInputChange}
name="enableJITProvisioning"
value={enableJITProvisioning}
name="enableJitProvisioning"
value={enableJitProvisioning}
parseTarget
>
<>
@@ -3,6 +3,8 @@ import React, { useState } from "react";
import Button from "components/buttons/Button";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import validUrl from "components/forms/validators/valid_url";
import {
IAppConfigFormProps,
IFormField,
@@ -31,9 +33,10 @@ const WebAddress = ({
const validateForm = () => {
const errors: IAppConfigFormErrors = {};
if (!serverURL) {
errors.server_url = "Fleet server URL must be present";
} else if (!validUrl({ url: serverURL, protocol: "http" })) {
errors.server_url = `${serverURL} is not a valid URL`;
}
setFormErrors(errors);
@@ -15,6 +15,7 @@ export interface IFormField {
}
export interface IAppConfigFormErrors {
metadata?: string | null;
metadata_url?: string | null;
entity_id?: string | null;
idp_name?: string | null;
@@ -18,6 +18,7 @@ import Dropdown from "components/forms/fields/Dropdown";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import Radio from "components/forms/fields/Radio";
import validUrl from "components/forms/validators/valid_url";
import PreviewPayloadModal from "../PreviewPayloadModal";
import PreviewTicketModal from "../PreviewTicketModal";
@@ -192,6 +193,8 @@ const ManageAutomationsModal = ({
if (isWebhookEnabled && !destinationUrl) {
newErrors.url = "Please add a destination URL";
} else if (!validUrl({ url: destinationUrl })) {
newErrors.url = `${destinationUrl} is not a valid URL`;
} else {
delete newErrors.url;
}
@@ -26,6 +26,7 @@ import Slider from "components/forms/fields/Slider";
import Radio from "components/forms/fields/Radio";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import validUrl from "components/forms/validators/valid_url";
import { IWebhookSoftwareVulnerabilities } from "interfaces/webhook";
import useDeepEffect from "hooks/useDeepEffect";
@@ -60,8 +61,12 @@ interface IManageAutomationsModalProps {
const validateWebhookURL = (url: string) => {
const errors: { [key: string]: string } = {};
if (url === "") {
if (!url) {
errors.url = "Please add a destination URL";
} else if (!validUrl({ url })) {
errors.url = `${url} is not a valid URL`;
} else {
delete errors.url;
}
const valid = !size(errors);
@@ -186,7 +191,7 @@ const ManageAutomationsModal = ({
const handleSaveAutomation = (evt: React.MouseEvent<HTMLFormElement>) => {
evt.preventDefault();
const { valid: validUrl, errors: newErrors } = validateWebhookURL(
const { valid: newValidUrl, errors: newErrors } = validateWebhookURL(
destinationUrl
);
setErrors({
@@ -232,7 +237,7 @@ const ManageAutomationsModal = ({
return;
}
if (!integrationEnabled) {
if (!validUrl) {
if (!newValidUrl) {
return;
}
// set enable_vulnerabilities_webhook to true