diff --git a/apps/remix/app/components/dialogs/folder-create-dialog.tsx b/apps/remix/app/components/dialogs/folder-create-dialog.tsx index bbad765aa..395feaf37 100644 --- a/apps/remix/app/components/dialogs/folder-create-dialog.tsx +++ b/apps/remix/app/components/dialogs/folder-create-dialog.tsx @@ -1,4 +1,4 @@ -import { ZNameSchema } from '@documenso/lib/constants/auth'; +import { ZNameSchema } from '@documenso/lib/types/name'; import { trpc } from '@documenso/trpc/react'; import { Button } from '@documenso/ui/primitives/button'; import { diff --git a/apps/remix/app/components/dialogs/folder-update-dialog.tsx b/apps/remix/app/components/dialogs/folder-update-dialog.tsx index be50f6a80..db859431a 100644 --- a/apps/remix/app/components/dialogs/folder-update-dialog.tsx +++ b/apps/remix/app/components/dialogs/folder-update-dialog.tsx @@ -1,6 +1,6 @@ -import { ZNameSchema } from '@documenso/lib/constants/auth'; import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; import { DocumentVisibility } from '@documenso/lib/types/document-visibility'; +import { ZNameSchema } from '@documenso/lib/types/name'; import { trpc } from '@documenso/trpc/react'; import type { TFolderWithSubfolders } from '@documenso/trpc/server/folder-router/schema'; import { Button } from '@documenso/ui/primitives/button'; diff --git a/apps/remix/app/components/forms/profile.tsx b/apps/remix/app/components/forms/profile.tsx index a9b81bc02..3449a747f 100644 --- a/apps/remix/app/components/forms/profile.tsx +++ b/apps/remix/app/components/forms/profile.tsx @@ -1,5 +1,5 @@ import { useSession } from '@documenso/lib/client-only/providers/session'; -import { ZNameSchema } from '@documenso/lib/constants/auth'; +import { ZNameSchema } from '@documenso/lib/types/name'; import { trpc } from '@documenso/trpc/react'; import { cn } from '@documenso/ui/lib/utils'; import { Button } from '@documenso/ui/primitives/button'; diff --git a/apps/remix/app/components/forms/signup.tsx b/apps/remix/app/components/forms/signup.tsx index a53131f08..4c7a18aec 100644 --- a/apps/remix/app/components/forms/signup.tsx +++ b/apps/remix/app/components/forms/signup.tsx @@ -1,8 +1,8 @@ import communityCardsImage from '@documenso/assets/images/community-cards.png'; import { authClient } from '@documenso/auth/client'; import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics'; -import { ZNameSchema } from '@documenso/lib/constants/auth'; import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; +import { ZNameSchema } from '@documenso/lib/types/name'; import { env } from '@documenso/lib/utils/env'; import { zEmail } from '@documenso/lib/utils/zod'; import { ZPasswordSchema } from '@documenso/trpc/server/auth-router/schema'; diff --git a/packages/auth/server/types/email-password.ts b/packages/auth/server/types/email-password.ts index eafaca28c..5303d326c 100644 --- a/packages/auth/server/types/email-password.ts +++ b/packages/auth/server/types/email-password.ts @@ -1,4 +1,4 @@ -import { ZNameSchema } from '@documenso/lib/constants/auth'; +import { ZNameSchema } from '@documenso/lib/types/name'; import { zEmail } from '@documenso/lib/utils/zod'; import { z } from 'zod'; diff --git a/packages/lib/constants/auth.ts b/packages/lib/constants/auth.ts index bf8945e06..56cbd90a3 100644 --- a/packages/lib/constants/auth.ts +++ b/packages/lib/constants/auth.ts @@ -1,31 +1,10 @@ import MailChecker from 'mailchecker'; -import { z } from 'zod'; import { env } from '../utils/env'; -import { hasInvalidTextCharacters } from '../utils/zod'; import { NEXT_PUBLIC_WEBAPP_URL } from './app'; export const SALT_ROUNDS = 12; -export const URL_PATTERN = /https?:\/\/|www\./i; - -/** - * Shared name schema that disallows URLs to prevent phishing via email rendering, - * and invisible/control characters that render as empty or break the UI. - * Also disallows invalid resource name characters. - */ -export const ZNameSchema = z - .string() - .trim() - .min(2, { message: 'Please enter a valid name.' }) - .max(255, { message: 'Name cannot be more than 255 characters.' }) - .refine((value) => !URL_PATTERN.test(value), { - message: 'Name cannot contain URLs.', - }) - .refine((value) => !hasInvalidTextCharacters(value), { - message: 'Name contains invalid characters.', - }); - export const IDENTITY_PROVIDER_NAME: Record = { DOCUMENSO: 'Documenso', GOOGLE: 'Google', diff --git a/packages/lib/types/name.ts b/packages/lib/types/name.ts new file mode 100644 index 000000000..561445d55 --- /dev/null +++ b/packages/lib/types/name.ts @@ -0,0 +1,22 @@ +import { z } from 'zod'; +import { hasInvalidTextCharacters } from '../utils/zod'; + +export const URL_PATTERN = /https?:\/\/|www\./i; + +/** + * Shared name schema that disallows URLs to prevent phishing via email rendering, + * and invisible/control characters that render as empty or break the UI. + */ +export const ZNameSchema = z + .string() + .trim() + .min(2, { message: 'Please enter a valid name.' }) + .max(100, { message: 'Name cannot be more than 100 characters.' }) + .refine((value) => !URL_PATTERN.test(value), { + message: 'Name cannot contain URLs.', + }) + .refine((value) => !hasInvalidTextCharacters(value), { + message: 'Name contains invalid characters.', + }); + +export type TName = z.infer; diff --git a/packages/trpc/server/admin-router/create-admin-organisation.types.ts b/packages/trpc/server/admin-router/create-admin-organisation.types.ts index 7c142bba6..a86750ab3 100644 --- a/packages/trpc/server/admin-router/create-admin-organisation.types.ts +++ b/packages/trpc/server/admin-router/create-admin-organisation.types.ts @@ -1,11 +1,10 @@ +import { ZNameSchema } from '@documenso/lib/types/name'; import { z } from 'zod'; -import { ZOrganisationNameSchema } from '../organisation-router/create-organisation.types'; - export const ZCreateAdminOrganisationRequestSchema = z.object({ ownerUserId: z.number(), data: z.object({ - name: ZOrganisationNameSchema, + name: ZNameSchema, }), }); diff --git a/packages/trpc/server/admin-router/create-user.types.ts b/packages/trpc/server/admin-router/create-user.types.ts index 6e1b65438..6c629a291 100644 --- a/packages/trpc/server/admin-router/create-user.types.ts +++ b/packages/trpc/server/admin-router/create-user.types.ts @@ -1,4 +1,4 @@ -import { ZNameSchema } from '@documenso/lib/constants/auth'; +import { ZNameSchema } from '@documenso/lib/types/name'; import { z } from 'zod'; export const ZCreateUserRequestSchema = z.object({ diff --git a/packages/trpc/server/admin-router/update-admin-organisation.types.ts b/packages/trpc/server/admin-router/update-admin-organisation.types.ts index 5f5d4a17a..47e9927cc 100644 --- a/packages/trpc/server/admin-router/update-admin-organisation.types.ts +++ b/packages/trpc/server/admin-router/update-admin-organisation.types.ts @@ -1,13 +1,13 @@ +import { ZNameSchema } from '@documenso/lib/types/name'; import { z } from 'zod'; -import { ZOrganisationNameSchema } from '../organisation-router/create-organisation.types'; import { ZTeamUrlSchema } from '../team-router/schema'; import { ZCreateSubscriptionClaimRequestSchema } from './create-subscription-claim.types'; export const ZUpdateAdminOrganisationRequestSchema = z.object({ organisationId: z.string(), data: z.object({ - name: ZOrganisationNameSchema.optional(), + name: ZNameSchema.optional(), url: ZTeamUrlSchema.optional(), claims: ZCreateSubscriptionClaimRequestSchema.pick({ teamCount: true, diff --git a/packages/trpc/server/api-token-router/create-api-token.types.ts b/packages/trpc/server/api-token-router/create-api-token.types.ts index c73c65833..c362bdf50 100644 --- a/packages/trpc/server/api-token-router/create-api-token.types.ts +++ b/packages/trpc/server/api-token-router/create-api-token.types.ts @@ -1,8 +1,9 @@ +import { ZNameSchema } from '@documenso/lib/types/name'; import { z } from 'zod'; export const ZCreateApiTokenRequestSchema = z.object({ teamId: z.number(), - tokenName: z.string().min(3, { message: 'The token name should be 3 characters or longer' }), + tokenName: ZNameSchema, expirationDate: z.string().nullable(), }); diff --git a/packages/trpc/server/folder-router/schema.ts b/packages/trpc/server/folder-router/schema.ts index f16981836..396e121c1 100644 --- a/packages/trpc/server/folder-router/schema.ts +++ b/packages/trpc/server/folder-router/schema.ts @@ -1,5 +1,5 @@ -import { ZNameSchema } from '@documenso/lib/constants/auth'; import { ZFolderTypeSchema } from '@documenso/lib/types/folder-type'; +import { ZNameSchema } from '@documenso/lib/types/name'; import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params'; import { DocumentVisibility } from '@documenso/prisma/generated/types'; import FolderSchema from '@documenso/prisma/generated/zod/modelSchema/FolderSchema'; diff --git a/packages/trpc/server/organisation-router/create-organisation-group.types.ts b/packages/trpc/server/organisation-router/create-organisation-group.types.ts index af824d426..3588f8015 100644 --- a/packages/trpc/server/organisation-router/create-organisation-group.types.ts +++ b/packages/trpc/server/organisation-router/create-organisation-group.types.ts @@ -1,3 +1,4 @@ +import { ZNameSchema } from '@documenso/lib/types/name'; import { OrganisationMemberRole } from '@prisma/client'; import { z } from 'zod'; @@ -14,7 +15,7 @@ import { z } from 'zod'; export const ZCreateOrganisationGroupRequestSchema = z.object({ organisationId: z.string(), organisationRole: z.nativeEnum(OrganisationMemberRole), - name: z.string().max(100), + name: ZNameSchema, memberIds: z.array(z.string()), }); diff --git a/packages/trpc/server/organisation-router/create-organisation.types.ts b/packages/trpc/server/organisation-router/create-organisation.types.ts index e18846120..97bb6ee86 100644 --- a/packages/trpc/server/organisation-router/create-organisation.types.ts +++ b/packages/trpc/server/organisation-router/create-organisation.types.ts @@ -1,3 +1,4 @@ +import { ZNameSchema } from '@documenso/lib/types/name'; import { z } from 'zod'; // export const createOrganisationMeta: TrpcOpenApiMeta = { @@ -10,13 +11,8 @@ import { z } from 'zod'; // }, // }; -export const ZOrganisationNameSchema = z - .string() - .min(3, { message: 'Minimum 3 characters' }) - .max(50, { message: 'Maximum 50 characters' }); - export const ZCreateOrganisationRequestSchema = z.object({ - name: ZOrganisationNameSchema, + name: ZNameSchema, priceId: z.string().optional(), }); diff --git a/packages/trpc/server/profile-router/schema.ts b/packages/trpc/server/profile-router/schema.ts index 4f1873096..5bcf2fe4e 100644 --- a/packages/trpc/server/profile-router/schema.ts +++ b/packages/trpc/server/profile-router/schema.ts @@ -1,4 +1,4 @@ -import { ZNameSchema } from '@documenso/lib/constants/auth'; +import { ZNameSchema } from '@documenso/lib/types/name'; import { z } from 'zod'; export const ZFindUserSecurityAuditLogsSchema = z.object({ diff --git a/packages/trpc/server/team-router/create-team.types.ts b/packages/trpc/server/team-router/create-team.types.ts index efbedccfe..b4bc56679 100644 --- a/packages/trpc/server/team-router/create-team.types.ts +++ b/packages/trpc/server/team-router/create-team.types.ts @@ -1,5 +1,6 @@ +import { ZNameSchema } from '@documenso/lib/types/name'; import { z } from 'zod'; -import { ZTeamNameSchema, ZTeamUrlSchema } from './schema'; +import { ZTeamUrlSchema } from './schema'; // export const createTeamMeta: TrpcOpenApiMeta = { // openapi: { @@ -13,7 +14,7 @@ import { ZTeamNameSchema, ZTeamUrlSchema } from './schema'; export const ZCreateTeamRequestSchema = z.object({ organisationId: z.string(), - teamName: ZTeamNameSchema, + teamName: ZNameSchema, teamUrl: ZTeamUrlSchema, inheritMembers: z .boolean() diff --git a/packages/trpc/server/team-router/schema.ts b/packages/trpc/server/team-router/schema.ts index 35ee0b59a..620ad1cb4 100644 --- a/packages/trpc/server/team-router/schema.ts +++ b/packages/trpc/server/team-router/schema.ts @@ -1,5 +1,5 @@ -import { URL_PATTERN, ZNameSchema } from '@documenso/lib/constants/auth'; import { PROTECTED_TEAM_URLS } from '@documenso/lib/constants/teams'; +import { ZNameSchema } from '@documenso/lib/types/name'; import { zEmail } from '@documenso/lib/utils/zod'; import { TeamMemberRole } from '@prisma/client'; import { z } from 'zod'; @@ -32,15 +32,6 @@ export const ZTeamUrlSchema = z message: 'This URL is already in use.', }); -export const ZTeamNameSchema = z - .string() - .trim() - .min(3, { message: 'Team name must be at least 3 characters long.' }) - .max(30, { message: 'Team name must not exceed 30 characters.' }) - .refine((value) => !URL_PATTERN.test(value), { - message: 'Team name cannot contain URLs.', - }); - export const ZCreateTeamEmailVerificationMutationSchema = z.object({ teamId: z.number(), name: ZNameSchema, diff --git a/packages/trpc/server/team-router/update-team.types.ts b/packages/trpc/server/team-router/update-team.types.ts index c75abdf30..f28675b1b 100644 --- a/packages/trpc/server/team-router/update-team.types.ts +++ b/packages/trpc/server/team-router/update-team.types.ts @@ -1,6 +1,7 @@ +import { ZNameSchema } from '@documenso/lib/types/name'; import { z } from 'zod'; -import { ZTeamNameSchema, ZTeamUrlSchema } from './schema'; +import { ZTeamUrlSchema } from './schema'; export const MAX_PROFILE_BIO_LENGTH = 256; @@ -19,7 +20,7 @@ export const MAX_PROFILE_BIO_LENGTH = 256; export const ZUpdateTeamRequestSchema = z.object({ teamId: z.number(), data: z.object({ - name: ZTeamNameSchema.optional(), + name: ZNameSchema.optional(), url: ZTeamUrlSchema.optional(), profileBio: z .string() diff --git a/packages/trpc/server/webhook-router/schema.ts b/packages/trpc/server/webhook-router/schema.ts index 1abd126a4..87ebf53d2 100644 --- a/packages/trpc/server/webhook-router/schema.ts +++ b/packages/trpc/server/webhook-router/schema.ts @@ -1,4 +1,5 @@ import { isPrivateUrl } from '@documenso/lib/server-only/webhooks/is-private-url'; +import { URL_PATTERN } from '@documenso/lib/types/name'; import { WebhookTriggerEvents } from '@prisma/client'; import { z } from 'zod'; @@ -7,6 +8,13 @@ export const ZWebhookUrlSchema = z .url() .refine((url) => !isPrivateUrl(url), { message: 'Webhook URL cannot point to a private or loopback address', + }) + /* + * Without this, values like "foo: bar" would be valid URLs. + * Keep the same error message as the zod url() validator. + */ + .refine((value) => URL_PATTERN.test(value), { + message: 'Invalid url', }); export const ZCreateWebhookRequestSchema = z.object({