From d7109eff47d42ec3fdeeacc66fc55bcae98785e6 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Sat, 8 Mar 2025 10:21:42 +0200 Subject: [PATCH] Prevent some empty name sheganigans --- app/constants.ts | 6 ------ app/features/team/team-schemas.server.ts | 13 +++++++++++-- .../tournament/tournament-schemas.server.ts | 10 +++++----- .../user-page/routes/u.$identifier.edit.tsx | 10 +++------- app/utils/zod.ts | 14 ++++++++++++++ 5 files changed, 33 insertions(+), 20 deletions(-) diff --git a/app/constants.ts b/app/constants.ts index a14cee52b..4c0c4e830 100644 --- a/app/constants.ts +++ b/app/constants.ts @@ -3,16 +3,10 @@ import type { BuildAbilitiesTupleWithUnknown } from "./modules/in-game-lists"; export const TWEET_LENGTH_MAX_LENGTH = 280; export const DISCORD_MESSAGE_MAX_LENGTH = 2000; -const EMPTY_CHARACTERS = ["\u200B", "\u200C", "\u200D", "\u200E", "\u200F"]; -export const notAllEmptyCharactersRegExp = new RegExp( - `^(?!(${EMPTY_CHARACTERS.join("|")})+$).*$`, -); - export const USER = { BIO_MAX_LENGTH: DISCORD_MESSAGE_MAX_LENGTH, CUSTOM_URL_MAX_LENGTH: 32, CUSTOM_NAME_MAX_LENGTH: 32, - CUSTOM_NAME_REGEXP: notAllEmptyCharactersRegExp, BATTLEFY_MAX_LENGTH: 32, IN_GAME_NAME_TEXT_MAX_LENGTH: 20, IN_GAME_NAME_DISCRIMINATOR_MAX_LENGTH: 5, diff --git a/app/features/team/team-schemas.server.ts b/app/features/team/team-schemas.server.ts index c7825333d..adfc92a43 100644 --- a/app/features/team/team-schemas.server.ts +++ b/app/features/team/team-schemas.server.ts @@ -1,11 +1,20 @@ import { z } from "zod"; -import { _action, customCssVarObject, falsyToNull, id } from "~/utils/zod"; +import { + _action, + actuallyNonEmptyStringOrNull, + customCssVarObject, + falsyToNull, + id, +} from "~/utils/zod"; import { TEAM, TEAM_MEMBER_ROLES } from "./team-constants"; export const teamParamsSchema = z.object({ customUrl: z.string() }); export const createTeamSchema = z.object({ - name: z.string().min(TEAM.NAME_MIN_LENGTH).max(TEAM.NAME_MAX_LENGTH), + name: z.preprocess( + actuallyNonEmptyStringOrNull, + z.string().min(TEAM.NAME_MIN_LENGTH).max(TEAM.NAME_MAX_LENGTH), + ), }); export const teamProfilePageActionSchema = z.union([ diff --git a/app/features/tournament/tournament-schemas.server.ts b/app/features/tournament/tournament-schemas.server.ts index 81ae44984..a10fdbaa8 100644 --- a/app/features/tournament/tournament-schemas.server.ts +++ b/app/features/tournament/tournament-schemas.server.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { _action, + actuallyNonEmptyStringOrNull, checkboxValueToBoolean, id, modeShort, @@ -11,11 +12,10 @@ import { import { bracketIdx } from "../tournament-bracket/tournament-bracket-schemas.server"; import { TOURNAMENT } from "./tournament-constants"; -export const teamName = z - .string() - .trim() - .min(1) - .max(TOURNAMENT.TEAM_NAME_MAX_LENGTH); +export const teamName = z.preprocess( + actuallyNonEmptyStringOrNull, + z.string().max(TOURNAMENT.TEAM_NAME_MAX_LENGTH), +); export const registerSchema = z.union([ z.object({ diff --git a/app/features/user-page/routes/u.$identifier.edit.tsx b/app/features/user-page/routes/u.$identifier.edit.tsx index 0a9f38ba1..f2dbab952 100644 --- a/app/features/user-page/routes/u.$identifier.edit.tsx +++ b/app/features/user-page/routes/u.$identifier.edit.tsx @@ -41,6 +41,7 @@ import { rawSensToString } from "~/utils/strings"; import { FAQ_PAGE, isCustomUrl, userPage } from "~/utils/urls"; import { actualNumber, + actuallyNonEmptyStringOrNull, checkboxValueToDbBoolean, customCssVarObject, dbBoolean, @@ -87,13 +88,8 @@ export const userEditActionSchema = z .nullable(), ), customName: z.preprocess( - falsyToNull, - z - .string() - .trim() - .regex(USER.CUSTOM_NAME_REGEXP) - .max(USER.CUSTOM_NAME_MAX_LENGTH) - .nullable(), + actuallyNonEmptyStringOrNull, + z.string().max(USER.CUSTOM_NAME_MAX_LENGTH).nullable(), ), battlefy: z.preprocess( falsyToNull, diff --git a/app/utils/zod.ts b/app/utils/zod.ts index 2f9eae723..970651f0e 100644 --- a/app/utils/zod.ts +++ b/app/utils/zod.ts @@ -120,6 +120,20 @@ export function safeJSONParse(value: unknown): unknown { } } +const EMPTY_CHARACTERS = ["\u200B", "\u200C", "\u200D", "\u200E", "\u200F"]; +const EMPTY_CHARACTERS_REGEX = new RegExp(EMPTY_CHARACTERS.join("|"), "g"); + +/** + * Processes the input value and returns a non-empty string with invisible characters cleaned out or null. + */ +export function actuallyNonEmptyStringOrNull(value: unknown) { + if (typeof value !== "string") return value; + + const trimmed = value.replace(EMPTY_CHARACTERS_REGEX, "").trim(); + + return trimmed === "" ? null : trimmed; +} + /** * Safely splits a string by a specified delimiter as Zod preprocess function. *