From 3f9bfeb01a67b2b587c7548b02ee826722e65c0f Mon Sep 17 00:00:00 2001 From: TheCatLady <52870424+TheCatLady@users.noreply.github.com> Date: Thu, 4 Feb 2021 20:12:51 -0500 Subject: [PATCH] fix(ui): Add additional URL & email input validation (#843) --- .../Notifications/NotificationsDiscord.tsx | 8 +++---- .../Notifications/NotificationsEmail.tsx | 8 +++---- .../NotificationsSlack/index.tsx | 8 +++---- .../NotificationsWebhook/index.tsx | 12 ++++++---- src/components/Settings/RadarrModal/index.tsx | 23 +++++++++++++++++++ src/components/Settings/SonarrModal/index.tsx | 23 +++++++++++++++++++ src/components/UserEdit/index.tsx | 14 ++++++++++- src/components/UserList/index.tsx | 6 ++--- src/i18n/locale/en.json | 15 ++++++++---- 9 files changed, 92 insertions(+), 25 deletions(-) diff --git a/src/components/Settings/Notifications/NotificationsDiscord.tsx b/src/components/Settings/Notifications/NotificationsDiscord.tsx index 85948645..8d879acf 100644 --- a/src/components/Settings/Notifications/NotificationsDiscord.tsx +++ b/src/components/Settings/Notifications/NotificationsDiscord.tsx @@ -14,13 +14,13 @@ const messages = defineMessages({ saving: 'Saving…', agentenabled: 'Enable Agent', webhookUrl: 'Webhook URL', - validationWebhookUrlRequired: 'You must provide a webhook URL', webhookUrlPlaceholder: 'Server Settings → Integrations → Webhooks', discordsettingssaved: 'Discord notification settings saved!', discordsettingsfailed: 'Discord notification settings failed to save.', testsent: 'Test notification sent!', test: 'Test', notificationtypes: 'Notification Types', + validationWebhookUrl: 'You must provide a valid URL', }); const NotificationsDiscord: React.FC = () => { @@ -31,9 +31,9 @@ const NotificationsDiscord: React.FC = () => { ); const NotificationsDiscordSchema = Yup.object().shape({ - webhookUrl: Yup.string().required( - intl.formatMessage(messages.validationWebhookUrlRequired) - ), + webhookUrl: Yup.string() + .required(intl.formatMessage(messages.validationWebhookUrl)) + .url(intl.formatMessage(messages.validationWebhookUrl)), }); if (!data && !error) { diff --git a/src/components/Settings/Notifications/NotificationsEmail.tsx b/src/components/Settings/Notifications/NotificationsEmail.tsx index 3d2f9bbb..36926c55 100644 --- a/src/components/Settings/Notifications/NotificationsEmail.tsx +++ b/src/components/Settings/Notifications/NotificationsEmail.tsx @@ -12,7 +12,6 @@ import NotificationTypeSelector from '../../NotificationTypeSelector'; const messages = defineMessages({ save: 'Save Changes', saving: 'Saving…', - validationFromRequired: 'You must provide a sender address', validationSmtpHostRequired: 'You must provide an SMTP host', validationSmtpPortRequired: 'You must provide an SMTP port', agentenabled: 'Enable Agent', @@ -31,6 +30,7 @@ const messages = defineMessages({ 'SSL should be disabled on standard TLS connections (port 587)', senderName: 'Sender Name', notificationtypes: 'Notification Types', + validationEmail: 'You must provide a valid email address', }); const NotificationsEmail: React.FC = () => { @@ -41,9 +41,9 @@ const NotificationsEmail: React.FC = () => { ); const NotificationsEmailSchema = Yup.object().shape({ - emailFrom: Yup.string().required( - intl.formatMessage(messages.validationFromRequired) - ), + emailFrom: Yup.string() + .required(intl.formatMessage(messages.validationEmail)) + .email(intl.formatMessage(messages.validationEmail)), smtpHost: Yup.string().required( intl.formatMessage(messages.validationSmtpHostRequired) ), diff --git a/src/components/Settings/Notifications/NotificationsSlack/index.tsx b/src/components/Settings/Notifications/NotificationsSlack/index.tsx index 65b18c17..b4297bd4 100644 --- a/src/components/Settings/Notifications/NotificationsSlack/index.tsx +++ b/src/components/Settings/Notifications/NotificationsSlack/index.tsx @@ -15,7 +15,6 @@ const messages = defineMessages({ saving: 'Saving…', agentenabled: 'Enable Agent', webhookUrl: 'Webhook URL', - validationWebhookUrlRequired: 'You must provide a webhook URL', webhookUrlPlaceholder: 'Webhook URL', slacksettingssaved: 'Slack notification settings saved!', slacksettingsfailed: 'Slack notification settings failed to save.', @@ -25,6 +24,7 @@ const messages = defineMessages({ settingupslackDescription: 'To use Slack notifications, you will need to create an Incoming Webhook integration and use the provided webhook URL below.', notificationtypes: 'Notification Types', + validationWebhookUrl: 'You must provide a valid URL', }); const NotificationsSlack: React.FC = () => { @@ -35,9 +35,9 @@ const NotificationsSlack: React.FC = () => { ); const NotificationsSlackSchema = Yup.object().shape({ - webhookUrl: Yup.string().required( - intl.formatMessage(messages.validationWebhookUrlRequired) - ), + webhookUrl: Yup.string() + .required(intl.formatMessage(messages.validationWebhookUrl)) + .url(intl.formatMessage(messages.validationWebhookUrl)), }); if (!data && !error) { diff --git a/src/components/Settings/Notifications/NotificationsWebhook/index.tsx b/src/components/Settings/Notifications/NotificationsWebhook/index.tsx index 01992690..927a7639 100644 --- a/src/components/Settings/Notifications/NotificationsWebhook/index.tsx +++ b/src/components/Settings/Notifications/NotificationsWebhook/index.tsx @@ -37,7 +37,6 @@ const messages = defineMessages({ agentenabled: 'Enable Agent', webhookUrl: 'Webhook URL', authheader: 'Authorization Header', - validationWebhookUrlRequired: 'You must provide a webhook URL', validationJsonPayloadRequired: 'You must provide a JSON Payload', webhookUrlPlaceholder: 'Remote webhook URL', webhooksettingssaved: 'Webhook notification settings saved!', @@ -49,6 +48,7 @@ const messages = defineMessages({ resetPayloadSuccess: 'JSON reset to default payload.', customJson: 'Custom JSON Payload', templatevariablehelp: 'Template Variable Help', + validationWebhookUrl: 'You must provide a valid URL', }); const NotificationsWebhook: React.FC = () => { @@ -59,9 +59,13 @@ const NotificationsWebhook: React.FC = () => { ); const NotificationsWebhookSchema = Yup.object().shape({ - webhookUrl: Yup.string().required( - intl.formatMessage(messages.validationWebhookUrlRequired) - ), + webhookUrl: Yup.string() + .required(intl.formatMessage(messages.validationWebhookUrl)) + .matches( + // eslint-disable-next-line + /^(https?:)?\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*)?([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/, + intl.formatMessage(messages.validationWebhookUrl) + ), jsonPayload: Yup.string() .required(intl.formatMessage(messages.validationJsonPayloadRequired)) .test('validate-json', 'Invalid JSON', (value) => { diff --git a/src/components/Settings/RadarrModal/index.tsx b/src/components/Settings/RadarrModal/index.tsx index 2ba6b61a..eba9d198 100644 --- a/src/components/Settings/RadarrModal/index.tsx +++ b/src/components/Settings/RadarrModal/index.tsx @@ -52,6 +52,8 @@ const messages = defineMessages({ preventSearch: 'Disable Auto-Search', validationApplicationUrl: 'You must provide a valid URL', validationApplicationUrlTrailingSlash: 'URL must not end in a trailing slash', + validationBaseUrlLeadingSlash: 'Base URL must have a leading slash', + validationBaseUrlTrailingSlash: 'Base URL must not end in a trailing slash', }); interface TestResponse { @@ -119,6 +121,27 @@ const RadarrModal: React.FC = ({ return true; } ), + baseUrl: Yup.string() + .test( + 'leading-slash', + intl.formatMessage(messages.validationBaseUrlLeadingSlash), + (value) => { + if (value && value?.substr(0, 1) !== '/') { + return false; + } + return true; + } + ) + .test( + 'no-trailing-slash', + intl.formatMessage(messages.validationBaseUrlTrailingSlash), + (value) => { + if (value?.substr(value.length - 1) === '/') { + return false; + } + return true; + } + ), }); const testConnection = useCallback( diff --git a/src/components/Settings/SonarrModal/index.tsx b/src/components/Settings/SonarrModal/index.tsx index 36816ed6..2f6d5a66 100644 --- a/src/components/Settings/SonarrModal/index.tsx +++ b/src/components/Settings/SonarrModal/index.tsx @@ -52,6 +52,8 @@ const messages = defineMessages({ preventSearch: 'Disable Auto-Search', validationApplicationUrl: 'You must provide a valid URL', validationApplicationUrlTrailingSlash: 'URL must not end in a trailing slash', + validationBaseUrlLeadingSlash: 'Base URL must have a leading slash', + validationBaseUrlTrailingSlash: 'Base URL must not end in a trailing slash', }); interface TestResponse { @@ -116,6 +118,27 @@ const SonarrModal: React.FC = ({ return true; } ), + baseUrl: Yup.string() + .test( + 'leading-slash', + intl.formatMessage(messages.validationBaseUrlLeadingSlash), + (value) => { + if (value && value?.substr(0, 1) !== '/') { + return false; + } + return true; + } + ) + .test( + 'no-trailing-slash', + intl.formatMessage(messages.validationBaseUrlTrailingSlash), + (value) => { + if (value?.substr(value.length - 1) === '/') { + return false; + } + return true; + } + ), }); const testConnection = useCallback( diff --git a/src/components/UserEdit/index.tsx b/src/components/UserEdit/index.tsx index 188cf99f..2beb3e9f 100644 --- a/src/components/UserEdit/index.tsx +++ b/src/components/UserEdit/index.tsx @@ -11,6 +11,7 @@ import PermissionEdit from '../PermissionEdit'; import { Field, Form, Formik } from 'formik'; import { UserType } from '../../../server/constants/user'; import PageTitle from '../Common/PageTitle'; +import * as Yup from 'yup'; export const messages = defineMessages({ edituser: 'Edit User', @@ -23,6 +24,7 @@ export const messages = defineMessages({ saving: 'Saving…', usersaved: 'User saved!', userfail: 'Something went wrong while saving the user.', + validationEmail: 'You must provide a valid email address', }); const UserEdit: React.FC = () => { @@ -43,6 +45,12 @@ const UserEdit: React.FC = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [user]); + const UserEditSchema = Yup.object().shape({ + email: Yup.string() + .required(intl.formatMessage(messages.validationEmail)) + .email(intl.formatMessage(messages.validationEmail)), + }); + if (!user && !error) { return ; } @@ -54,6 +62,7 @@ const UserEdit: React.FC = () => { username: user?.username, email: user?.email, }} + validationSchema={UserEditSchema} onSubmit={async (values) => { try { await axios.put(`/api/v1/user/${user?.id}`, { @@ -78,7 +87,7 @@ const UserEdit: React.FC = () => { } }} > - {({ isSubmitting, handleSubmit }) => ( + {({ errors, touched, isSubmitting, handleSubmit }) => (
@@ -122,6 +131,9 @@ const UserEdit: React.FC = () => {
+ {errors.email && touched.email && ( +
{errors.email}
+ )}
diff --git a/src/components/UserList/index.tsx b/src/components/UserList/index.tsx index aa98134d..5e5b3939 100644 --- a/src/components/UserList/index.tsx +++ b/src/components/UserList/index.tsx @@ -51,7 +51,6 @@ const messages = defineMessages({ createuser: 'Create User', creating: 'Creating…', create: 'Create', - validationemailrequired: 'Must enter a valid email address', validationpasswordminchars: 'Password is too short; should be a minimum of 8 characters', usercreatedfailed: 'Something went wrong while creating the user.', @@ -62,6 +61,7 @@ const messages = defineMessages({ passwordinfodescription: 'Email notifications need to be configured and enabled in order to automatically generate passwords.', autogeneratepassword: 'Automatically generate password', + validationEmail: 'You must provide a valid email address', }); const UserList: React.FC = () => { @@ -171,8 +171,8 @@ const UserList: React.FC = () => { const CreateUserSchema = Yup.object().shape({ email: Yup.string() - .email() - .required(intl.formatMessage(messages.validationemailrequired)), + .required(intl.formatMessage(messages.validationEmail)) + .email(intl.formatMessage(messages.validationEmail)), password: Yup.lazy((value) => !value ? Yup.string() : Yup.string().min(8) ), diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 997061d9..85092fac 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -237,7 +237,7 @@ "components.Settings.Notifications.NotificationsSlack.slacksettingssaved": "Slack notification settings saved!", "components.Settings.Notifications.NotificationsSlack.test": "Test", "components.Settings.Notifications.NotificationsSlack.testsent": "Test notification sent!", - "components.Settings.Notifications.NotificationsSlack.validationWebhookUrlRequired": "You must provide a webhook URL", + "components.Settings.Notifications.NotificationsSlack.validationWebhookUrl": "You must provide a valid URL", "components.Settings.Notifications.NotificationsSlack.webhookUrl": "Webhook URL", "components.Settings.Notifications.NotificationsSlack.webhookUrlPlaceholder": "Webhook URL", "components.Settings.Notifications.NotificationsWebhook.agentenabled": "Enable Agent", @@ -252,7 +252,7 @@ "components.Settings.Notifications.NotificationsWebhook.test": "Test", "components.Settings.Notifications.NotificationsWebhook.testsent": "Test notification sent!", "components.Settings.Notifications.NotificationsWebhook.validationJsonPayloadRequired": "You must provide a JSON payload", - "components.Settings.Notifications.NotificationsWebhook.validationWebhookUrlRequired": "You must provide a webhook URL", + "components.Settings.Notifications.NotificationsWebhook.validationWebhookUrl": "You must provide a valid URL", "components.Settings.Notifications.NotificationsWebhook.webhookUrl": "Webhook URL", "components.Settings.Notifications.NotificationsWebhook.webhookUrlPlaceholder": "Remote webhook URL", "components.Settings.Notifications.NotificationsWebhook.webhooksettingsfailed": "Webhook notification settings failed to save.", @@ -284,10 +284,10 @@ "components.Settings.Notifications.testsent": "Test notification sent!", "components.Settings.Notifications.validationBotAPIRequired": "You must provide a Bot API key", "components.Settings.Notifications.validationChatIdRequired": "You must provide a Chat ID", - "components.Settings.Notifications.validationFromRequired": "You must provide a sender address", + "components.Settings.Notifications.validationEmail": "You must provide a valid email address", "components.Settings.Notifications.validationSmtpHostRequired": "You must provide an SMTP host", "components.Settings.Notifications.validationSmtpPortRequired": "You must provide an SMTP port", - "components.Settings.Notifications.validationWebhookUrlRequired": "You must provide a webhook URL", + "components.Settings.Notifications.validationWebhookUrl": "You must provide a valid URL", "components.Settings.Notifications.webhookUrl": "Webhook URL", "components.Settings.Notifications.webhookUrlPlaceholder": "Server Settings → Integrations → Webhooks", "components.Settings.RadarrModal.add": "Add Server", @@ -327,6 +327,8 @@ "components.Settings.RadarrModal.validationApiKeyRequired": "You must provide an API key", "components.Settings.RadarrModal.validationApplicationUrl": "You must provide a valid URL", "components.Settings.RadarrModal.validationApplicationUrlTrailingSlash": "URL must not end in a trailing slash", + "components.Settings.RadarrModal.validationBaseUrlLeadingSlash": "Base URL must have a leading slash", + "components.Settings.RadarrModal.validationBaseUrlTrailingSlash": "Base URL must not end in a trailing slash", "components.Settings.RadarrModal.validationHostnameRequired": "You must provide a hostname/IP", "components.Settings.RadarrModal.validationMinimumAvailabilityRequired": "You must select a minimum availability", "components.Settings.RadarrModal.validationNameRequired": "You must provide a server name", @@ -413,6 +415,8 @@ "components.Settings.SonarrModal.validationApiKeyRequired": "You must provide an API key", "components.Settings.SonarrModal.validationApplicationUrl": "You must provide a valid URL", "components.Settings.SonarrModal.validationApplicationUrlTrailingSlash": "URL must not end in a trailing slash", + "components.Settings.SonarrModal.validationBaseUrlLeadingSlash": "Base URL must have a leading slash", + "components.Settings.SonarrModal.validationBaseUrlTrailingSlash": "Base URL must not end in a trailing slash", "components.Settings.SonarrModal.validationHostnameRequired": "You must provide a hostname/IP", "components.Settings.SonarrModal.validationNameRequired": "You must provide a server name", "components.Settings.SonarrModal.validationPortRequired": "You must provide a port", @@ -576,6 +580,7 @@ "components.UserEdit.userfail": "Something went wrong while saving the user.", "components.UserEdit.username": "Display Name", "components.UserEdit.usersaved": "User saved!", + "components.UserEdit.validationEmail": "You must provide a valid email address", "components.UserList.admin": "Admin", "components.UserList.autogeneratepassword": "Automatically generate password", "components.UserList.bulkedit": "Bulk Edit", @@ -610,7 +615,7 @@ "components.UserList.users": "Users", "components.UserList.userssaved": "Users saved!", "components.UserList.usertype": "User Type", - "components.UserList.validationemailrequired": "Must enter a valid email address", + "components.UserList.validationEmail": "You must provide a valid email address", "components.UserList.validationpasswordminchars": "Password is too short; should be a minimum of 8 characters", "i18n.advanced": "Advanced", "i18n.approve": "Approve",