From a9025b5d97af7ab1115b65b2c7d4474ae8ba5e0d Mon Sep 17 00:00:00 2001 From: aeris <51246+aeris@users.noreply.github.com> Date: Wed, 31 Jul 2024 07:26:05 +0200 Subject: [PATCH] fix: use native URL parser instead of wrong regex (#1206) Updates the current regex based approach for validating redirect urls to instead use the native URL constructor which is available in browsers and Node.js and handles several valid cases that were previously not working. --- packages/lib/constants/url-regex.ts | 2 -- packages/lib/schemas/common.ts | 6 +++--- packages/lib/utils/is-valid-redirect-url.ts | 16 ++++++++++++++++ packages/trpc/server/document-router/schema.ts | 12 +++++++----- packages/trpc/server/template-router/schema.ts | 7 ++++--- .../document-flow/add-settings.types.ts | 7 ++++--- .../add-template-settings.types.tsx | 7 ++++--- 7 files changed, 38 insertions(+), 19 deletions(-) delete mode 100644 packages/lib/constants/url-regex.ts create mode 100644 packages/lib/utils/is-valid-redirect-url.ts diff --git a/packages/lib/constants/url-regex.ts b/packages/lib/constants/url-regex.ts deleted file mode 100644 index 1dfb70ad3..000000000 --- a/packages/lib/constants/url-regex.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const URL_REGEX = - /^(https?):\/\/(?:www\.)?(?:[a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.[a-zA-Z0-9()]{2,}(?:\/[a-zA-Z0-9-._?&=/]*)?$/i; diff --git a/packages/lib/schemas/common.ts b/packages/lib/schemas/common.ts index 101aeeff5..c63b9b399 100644 --- a/packages/lib/schemas/common.ts +++ b/packages/lib/schemas/common.ts @@ -1,12 +1,12 @@ import { z } from 'zod'; -import { URL_REGEX } from '../constants/url-regex'; +import { isValidRedirectUrl } from '../utils/is-valid-redirect-url'; /** * Note this allows empty strings. */ export const ZUrlSchema = z .string() - .refine((value) => value === undefined || value === '' || URL_REGEX.test(value), { - message: 'Please enter a valid URL', + .refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), { + message: 'Please enter a valid URL, make sure you include http:// or https:// part of the url.', }); diff --git a/packages/lib/utils/is-valid-redirect-url.ts b/packages/lib/utils/is-valid-redirect-url.ts new file mode 100644 index 000000000..e89818ac2 --- /dev/null +++ b/packages/lib/utils/is-valid-redirect-url.ts @@ -0,0 +1,16 @@ +const ALLOWED_PROTOCOLS = ['http', 'https']; + +export const isValidRedirectUrl = (value: string) => { + try { + const url = new URL(value); + + console.log({ protocol: url.protocol }); + if (!ALLOWED_PROTOCOLS.includes(url.protocol.slice(0, -1).toLowerCase())) { + return false; + } + + return true; + } catch { + return false; + } +}; diff --git a/packages/trpc/server/document-router/schema.ts b/packages/trpc/server/document-router/schema.ts index ac278b1d8..80efc7c15 100644 --- a/packages/trpc/server/document-router/schema.ts +++ b/packages/trpc/server/document-router/schema.ts @@ -1,11 +1,11 @@ import { z } from 'zod'; -import { URL_REGEX } from '@documenso/lib/constants/url-regex'; import { ZDocumentAccessAuthTypesSchema, ZDocumentActionAuthTypesSchema, } from '@documenso/lib/types/document-auth'; import { ZBaseTableSearchParamsSchema } from '@documenso/lib/types/search-params'; +import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url'; import { FieldType, RecipientRole } from '@documenso/prisma/client'; export const ZFindDocumentAuditLogsQuerySchema = ZBaseTableSearchParamsSchema.extend({ @@ -65,8 +65,9 @@ export const ZSetSettingsForDocumentMutationSchema = z.object({ redirectUrl: z .string() .optional() - .refine((value) => value === undefined || value === '' || URL_REGEX.test(value), { - message: 'Please enter a valid URL', + .refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), { + message: + 'Please enter a valid URL, make sure you include http:// or https:// part of the url.', }), }), }); @@ -131,8 +132,9 @@ export const ZSendDocumentMutationSchema = z.object({ redirectUrl: z .string() .optional() - .refine((value) => value === undefined || value === '' || URL_REGEX.test(value), { - message: 'Please enter a valid URL', + .refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), { + message: + 'Please enter a valid URL, make sure you include http:// or https:// part of the url.', }), }), }); diff --git a/packages/trpc/server/template-router/schema.ts b/packages/trpc/server/template-router/schema.ts index 29f815f35..671cc50fd 100644 --- a/packages/trpc/server/template-router/schema.ts +++ b/packages/trpc/server/template-router/schema.ts @@ -1,11 +1,11 @@ import { z } from 'zod'; -import { URL_REGEX } from '@documenso/lib/constants/url-regex'; import { ZDocumentAccessAuthTypesSchema, ZDocumentActionAuthTypesSchema, } from '@documenso/lib/types/document-auth'; import { ZBaseTableSearchParamsSchema } from '@documenso/lib/types/search-params'; +import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url'; import { TemplateType } from '@documenso/prisma/client'; import { ZSignFieldWithTokenMutationSchema } from '../field-router/schema'; @@ -96,8 +96,9 @@ export const ZUpdateTemplateSettingsMutationSchema = z.object({ redirectUrl: z .string() .optional() - .refine((value) => value === undefined || value === '' || URL_REGEX.test(value), { - message: 'Please enter a valid URL', + .refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), { + message: + 'Please enter a valid URL, make sure you include http:// or https:// part of the url.', }), }) .optional(), diff --git a/packages/ui/primitives/document-flow/add-settings.types.ts b/packages/ui/primitives/document-flow/add-settings.types.ts index a31e4a9bc..df4c6efcf 100644 --- a/packages/ui/primitives/document-flow/add-settings.types.ts +++ b/packages/ui/primitives/document-flow/add-settings.types.ts @@ -2,11 +2,11 @@ import { z } from 'zod'; import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats'; import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones'; -import { URL_REGEX } from '@documenso/lib/constants/url-regex'; import { ZDocumentAccessAuthTypesSchema, ZDocumentActionAuthTypesSchema, } from '@documenso/lib/types/document-auth'; +import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url'; export const ZMapNegativeOneToUndefinedSchema = z .string() @@ -34,8 +34,9 @@ export const ZAddSettingsFormSchema = z.object({ redirectUrl: z .string() .optional() - .refine((value) => value === undefined || value === '' || URL_REGEX.test(value), { - message: 'Please enter a valid URL', + .refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), { + message: + 'Please enter a valid URL, make sure you include http:// or https:// part of the url.', }), }), }); diff --git a/packages/ui/primitives/template-flow/add-template-settings.types.tsx b/packages/ui/primitives/template-flow/add-template-settings.types.tsx index 7e54d97b8..4f0d3f61a 100644 --- a/packages/ui/primitives/template-flow/add-template-settings.types.tsx +++ b/packages/ui/primitives/template-flow/add-template-settings.types.tsx @@ -2,11 +2,11 @@ import { z } from 'zod'; import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats'; import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones'; -import { URL_REGEX } from '@documenso/lib/constants/url-regex'; import { ZDocumentAccessAuthTypesSchema, ZDocumentActionAuthTypesSchema, } from '@documenso/lib/types/document-auth'; +import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url'; import { ZMapNegativeOneToUndefinedSchema } from '../document-flow/add-settings.types'; @@ -27,8 +27,9 @@ export const ZAddTemplateSettingsFormSchema = z.object({ redirectUrl: z .string() .optional() - .refine((value) => value === undefined || value === '' || URL_REGEX.test(value), { - message: 'Please enter a valid URL', + .refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), { + message: + 'Please enter a valid URL, make sure you include http:// or https:// part of the url.', }), }), });