diff --git a/changes/17360-better-url-email-validators b/changes/17360-better-url-email-validators new file mode 100644 index 0000000000..079733c24e --- /dev/null +++ b/changes/17360-better-url-email-validators @@ -0,0 +1 @@ +- UI: Improve URL and email validation \ No newline at end of file diff --git a/frontend/components/forms/RegistrationForm/AdminDetails/helpers.js b/frontend/components/forms/RegistrationForm/AdminDetails/helpers.js index 5569a0b986..3fe4e84575 100644 --- a/frontend/components/forms/RegistrationForm/AdminDetails/helpers.js +++ b/frontend/components/forms/RegistrationForm/AdminDetails/helpers.js @@ -12,12 +12,10 @@ const validate = (formData) => { name, } = formData; - if (!validEmail(email)) { - errors.email = "Email must be a valid email"; - } - if (!email) { errors.email = "Email must be present"; + } else if (!validEmail(email)) { + errors.email = "Email must be a valid email"; } if (!name) { diff --git a/frontend/components/forms/validators/valid_email/valid_email.ts b/frontend/components/forms/validators/valid_email/valid_email.ts index 8658bd0529..090a2e1cf8 100644 --- a/frontend/components/forms/validators/valid_email/valid_email.ts +++ b/frontend/components/forms/validators/valid_email/valid_email.ts @@ -1,12 +1,7 @@ -// see https://stackoverflow.com/a/201378 +// https://github.com/validatorjs/validator.js/blob/master/README.md#validators -// eslint-disable-next-line no-control-regex -const EMAIL_REGEX = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/; +import isEmail from "validator/lib/isEmail"; export default (email: string): boolean => { - if (EMAIL_REGEX.test(email)) { - return true; - } - - return false; + return isEmail(email); }; diff --git a/frontend/components/forms/validators/valid_url/valid_url.ts b/frontend/components/forms/validators/valid_url/valid_url.ts index adbdf37f78..1707e991f1 100644 --- a/frontend/components/forms/validators/valid_url/valid_url.ts +++ b/frontend/components/forms/validators/valid_url/valid_url.ts @@ -1,20 +1,13 @@ +// https://github.com/validatorjs/validator.js/blob/master/README.md#validators + +import isURL from "validator/lib/isURL"; + interface IValidUrl { url: string; - /** Validate protocol specified; http validates both http and https */ - protocol?: "http" | "https"; + /** Validate protocols specified */ + protocols?: ("http" | "https")[]; } -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; - } +export default ({ url, protocols }: IValidUrl): boolean => { + return isURL(url, { protocols }); }; diff --git a/frontend/pages/admin/IntegrationsPage/cards/Integrations/components/IntegrationForm/IntegrationForm.tsx b/frontend/pages/admin/IntegrationsPage/cards/Integrations/components/IntegrationForm/IntegrationForm.tsx index 1d4bad9950..dee63d4826 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/Integrations/components/IntegrationForm/IntegrationForm.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/Integrations/components/IntegrationForm/IntegrationForm.tsx @@ -89,7 +89,7 @@ const IntegrationForm = ({ const validateForm = () => { let error = null; - if (url && !validUrl({ url, protocol: "https" })) { + if (url && !validUrl({ url, protocols: ["https"] })) { error = `${url} is not a valid HTTPS URL`; } diff --git a/frontend/pages/admin/OrgSettingsPage/cards/Info/Info.tsx b/frontend/pages/admin/OrgSettingsPage/cards/Info/Info.tsx index 3cba376e00..fb91398e9c 100644 --- a/frontend/pages/admin/OrgSettingsPage/cards/Info/Info.tsx +++ b/frontend/pages/admin/OrgSettingsPage/cards/Info/Info.tsx @@ -61,13 +61,18 @@ const Info = ({ errors.org_name = "Organization name must be present"; } - if (orgLogoURL && !validUrl({ url: orgLogoURL, protocol: "http" })) { + if ( + orgLogoURL && + !validUrl({ url: orgLogoURL, protocols: ["http", "https"] }) + ) { errors.org_logo_url = `${orgLogoURL} is not a valid URL`; } if (!orgSupportURL) { errors.org_support_url = `Organization support URL must be present`; - } else if (!validUrl({ url: orgSupportURL, protocol: "http" })) { + } else if ( + !validUrl({ url: orgSupportURL, protocols: ["http", "https"] }) + ) { errors.org_support_url = `${orgSupportURL} is not a valid URL`; } diff --git a/frontend/pages/admin/OrgSettingsPage/cards/Sso/Sso.tsx b/frontend/pages/admin/OrgSettingsPage/cards/Sso/Sso.tsx index 8df0cacf07..929f08011a 100644 --- a/frontend/pages/admin/OrgSettingsPage/cards/Sso/Sso.tsx +++ b/frontend/pages/admin/OrgSettingsPage/cards/Sso/Sso.tsx @@ -74,7 +74,9 @@ const Sso = ({ 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, protocol: "http" })) { + } else if ( + !validUrl({ url: metadataUrl, protocols: ["http", "https"] }) + ) { errors.metadata_url = `${metadataUrl} is not a valid URL`; } } diff --git a/frontend/pages/admin/OrgSettingsPage/cards/WebAddress/WebAddress.tsx b/frontend/pages/admin/OrgSettingsPage/cards/WebAddress/WebAddress.tsx index 2709f67d2a..cc2660f30b 100644 --- a/frontend/pages/admin/OrgSettingsPage/cards/WebAddress/WebAddress.tsx +++ b/frontend/pages/admin/OrgSettingsPage/cards/WebAddress/WebAddress.tsx @@ -36,7 +36,7 @@ const WebAddress = ({ const errors: IAppConfigFormErrors = {}; if (!serverURL) { errors.server_url = "Fleet server URL must be present"; - } else if (!validUrl({ url: serverURL, protocol: "http" })) { + } else if (!validUrl({ url: serverURL, protocols: ["http", "https"] })) { errors.server_url = `${serverURL} is not a valid URL`; } diff --git a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx index 7e3285d87d..9ffbd5fa55 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx @@ -75,7 +75,7 @@ const CalendarEventsModal = ({ const { url: newUrl } = newFormData; if ( formData.enabled && - !validURL({ url: newUrl || "", protocol: "http" }) + !validURL({ url: newUrl || "", protocols: ["http", "https"] }) ) { const errorPrefix = newUrl ? `${newUrl} is not` : "Please enter"; errors.url = `${errorPrefix} a valid resolution webhook URL`; diff --git a/package.json b/package.json index 01622139c3..29ac16831a 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "sqlite-parser": "1.0.1", "use-debounce": "9.0.4", "uuid": "8.3.2", + "validator": "13.11.0", "when": "3.7.8" }, "devDependencies": { @@ -116,6 +117,7 @@ "@types/react-tooltip": "4.2.4", "@types/sockjs-client": "1.5.1", "@types/uuid": "8.3.4", + "@types/validator": "13.11.9", "@typescript-eslint/eslint-plugin": "5.58.0", "@typescript-eslint/parser": "5.58.0", "autoprefixer": "10.4.19", diff --git a/yarn.lock b/yarn.lock index 4932b7c84a..117249d2cf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5363,6 +5363,11 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== +"@types/validator@13.11.9": + version "13.11.9" + resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.11.9.tgz#adfe96520b437a0eaa798a475877bf2f75ee402d" + integrity sha512-FCTsikRozryfayPuiI46QzH3fnrOoctTjvOYZkho9BTFLCOZ2rgZJHMOVgCOfttjPJcgOx52EpkY0CMfy87MIw== + "@types/wait-on@^5.2.0": version "5.3.3" resolved "https://registry.yarnpkg.com/@types/wait-on/-/wait-on-5.3.3.tgz#e0cb6ddc3f7fade127f0d8cb295499f75e65b849" @@ -15799,7 +15804,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -15813,6 +15818,13 @@ strip-ansi@^3.0.0: dependencies: ansi-regex "^2.0.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -16811,6 +16823,11 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" +validator@13.11.0: + version "13.11.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-13.11.0.tgz#23ab3fd59290c61248364eabf4067f04955fbb1b" + integrity sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ== + value-equal@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c"