diff --git a/src/actions/admin-ads.ts b/src/actions/admin-ads.ts index 80869dca..a4703cc1 100644 --- a/src/actions/admin-ads.ts +++ b/src/actions/admin-ads.ts @@ -12,7 +12,7 @@ import { formPositiveBigInt } from "@/lib/form-data"; export async function createAd(formData: FormData): Promise { const staff = await requireStaff(); - const image = String(formData.get("image") ?? "") + const image = String(formData.get("image") ?? "").normalize("NFC") .trim() .slice(0, 255); if (!image) return; @@ -39,10 +39,10 @@ export async function createAd(formData: FormData): Promise { export async function updateAd(formData: FormData): Promise { const staff = await requireStaff(); - const raw = String(formData.get("id") ?? ""); + const raw = String(formData.get("id") ?? "").normalize("NFC"); if (!/^\d+$/.test(raw)) return; const id = BigInt(raw); - const image = String(formData.get("image") ?? "") + const image = String(formData.get("image") ?? "").normalize("NFC") .trim() .slice(0, 255); if (!image) return; diff --git a/src/actions/admin-alerts.ts b/src/actions/admin-alerts.ts index 13a64555..24d1a90c 100644 --- a/src/actions/admin-alerts.ts +++ b/src/actions/admin-alerts.ts @@ -13,7 +13,7 @@ import { rcon } from "@/lib/services/rcon"; export async function sendHotelAlert(formData: FormData): Promise { await requireStaff(); - const message = String(formData.get("message") ?? "") + const message = String(formData.get("message") ?? "").normalize("NFC") .trim() .slice(0, 1000); if (!message) return; diff --git a/src/actions/admin-articles.ts b/src/actions/admin-articles.ts index f3e062b8..67ec7b88 100644 --- a/src/actions/admin-articles.ts +++ b/src/actions/admin-articles.ts @@ -19,10 +19,10 @@ async function uniqueSlug(title: string): Promise { export async function createArticle(formData: FormData): Promise { const staff = await requireStaff(); - const title = String(formData.get("title") ?? "").trim(); - const shortStory = String(formData.get("shortStory") ?? "").trim(); - const fullStory = String(formData.get("fullStory") ?? "").trim(); - const image = String(formData.get("image") ?? "").trim(); + const title = String(formData.get("title") ?? "").normalize("NFC").trim(); + const shortStory = String(formData.get("shortStory") ?? "").normalize("NFC").trim(); + const fullStory = String(formData.get("fullStory") ?? "").normalize("NFC").trim(); + const image = String(formData.get("image") ?? "").normalize("NFC").trim(); if (!title) return; try { @@ -53,14 +53,14 @@ export async function updateArticle(formData: FormData): Promise { await prisma.websiteArticles.update({ where: { id }, data: { - title: String(formData.get("title") ?? "") + title: String(formData.get("title") ?? "").normalize("NFC") .trim() .slice(0, 255), - shortStory: String(formData.get("shortStory") ?? "") + shortStory: String(formData.get("shortStory") ?? "").normalize("NFC") .trim() .slice(0, 255), - fullStory: String(formData.get("fullStory") ?? "").trim(), - image: String(formData.get("image") ?? "") + fullStory: String(formData.get("fullStory") ?? "").normalize("NFC").trim(), + image: String(formData.get("image") ?? "").normalize("NFC") .trim() .slice(0, 255), updatedAt: new Date(), diff --git a/src/actions/admin-badge-upload.ts b/src/actions/admin-badge-upload.ts index 9949a92b..d6e4f223 100644 --- a/src/actions/admin-badge-upload.ts +++ b/src/actions/admin-badge-upload.ts @@ -27,7 +27,7 @@ export async function uploadBadge(formData: FormData): Promise { back("error", "Badge upload directory not configured"); } - const code = String(formData.get("code") ?? "").trim(); + const code = String(formData.get("code") ?? "").normalize("NFC").trim(); if (!CODE_RE.test(code)) { back("error", "Invalid badge code (use A-Z, 0-9, _ or -, max 64 chars)"); } diff --git a/src/actions/admin-badges.ts b/src/actions/admin-badges.ts index 3740c790..a2d5adfc 100644 --- a/src/actions/admin-badges.ts +++ b/src/actions/admin-badges.ts @@ -9,7 +9,7 @@ export async function giveBadge(formData: FormData): Promise { await requireStaff(); const userId = Number(formData.get("userId")); - const code = String(formData.get("code") ?? "") + const code = String(formData.get("code") ?? "").normalize("NFC") .trim() .slice(0, 32); if (!(userId > 0) || code.length === 0) return; diff --git a/src/actions/admin-bans.ts b/src/actions/admin-bans.ts index 8673c023..34c0c177 100644 --- a/src/actions/admin-bans.ts +++ b/src/actions/admin-bans.ts @@ -15,7 +15,7 @@ export async function createBan(formData: FormData): Promise { const staff = await requireStaff(); const userId = Number(formData.get("userId")); const reason = - String(formData.get("reason") ?? "") + String(formData.get("reason") ?? "").normalize("NFC") .trim() .slice(0, 200) || "Banned"; const hours = Number(formData.get("hours")); diff --git a/src/actions/admin-email-templates.ts b/src/actions/admin-email-templates.ts index 5036ab5e..9563adb0 100644 --- a/src/actions/admin-email-templates.ts +++ b/src/actions/admin-email-templates.ts @@ -7,14 +7,14 @@ import { formPositiveBigInt } from "@/lib/form-data"; export async function createEmailTemplate(formData: FormData): Promise { await requireStaff(); - const name = String(formData.get("name") ?? "") + const name = String(formData.get("name") ?? "").normalize("NFC") .trim() .slice(0, 255); - const subject = String(formData.get("subject") ?? "") + const subject = String(formData.get("subject") ?? "").normalize("NFC") .trim() .slice(0, 255); - const body = String(formData.get("body") ?? ""); - const variablesRaw = String(formData.get("variables") ?? "").trim(); + const body = String(formData.get("body") ?? "").normalize("NFC"); + const variablesRaw = String(formData.get("variables") ?? "").normalize("NFC").trim(); const isActive = formData.get("isActive") != null; if (!name || !subject || !body) return; @@ -32,7 +32,7 @@ export async function createEmailTemplate(formData: FormData): Promise { export async function updateEmailTemplate(formData: FormData): Promise { await requireStaff(); - const raw = String(formData.get("id") ?? ""); + const raw = String(formData.get("id") ?? "").normalize("NFC"); if (!raw) return; let id: bigint; try { @@ -40,11 +40,11 @@ export async function updateEmailTemplate(formData: FormData): Promise { } catch { return; } - const subject = String(formData.get("subject") ?? "") + const subject = String(formData.get("subject") ?? "").normalize("NFC") .trim() .slice(0, 255); - const body = String(formData.get("body") ?? ""); - const variablesRaw = String(formData.get("variables") ?? "").trim(); + const body = String(formData.get("body") ?? "").normalize("NFC"); + const variablesRaw = String(formData.get("variables") ?? "").normalize("NFC").trim(); const isActive = formData.get("isActive") != null; if (!subject || !body) return; diff --git a/src/actions/admin-emulator.ts b/src/actions/admin-emulator.ts index 3c193246..af7d2525 100644 --- a/src/actions/admin-emulator.ts +++ b/src/actions/admin-emulator.ts @@ -11,10 +11,10 @@ import { prisma } from "@/lib/prisma"; export async function updateEmulatorSetting(formData: FormData): Promise { await requireStaff(); - const key = String(formData.get("key") ?? "") + const key = String(formData.get("key") ?? "").normalize("NFC") .trim() .slice(0, 100); - const value = String(formData.get("value") ?? "").slice(0, 512); + const value = String(formData.get("value") ?? "").normalize("NFC").slice(0, 512); if (!key) return; await prisma.emulatorSettings.upsert({ where: { key }, @@ -26,10 +26,10 @@ export async function updateEmulatorSetting(formData: FormData): Promise { export async function updateEmulatorText(formData: FormData): Promise { await requireStaff(); - const key = String(formData.get("key") ?? "") + const key = String(formData.get("key") ?? "").normalize("NFC") .trim() .slice(0, 100); - const value = String(formData.get("value") ?? "").slice(0, 4096); + const value = String(formData.get("value") ?? "").normalize("NFC").slice(0, 4096); if (!key) return; await prisma.emulatorTexts.upsert({ where: { key }, diff --git a/src/actions/admin-help.ts b/src/actions/admin-help.ts index 0a5c72c0..96ceab28 100644 --- a/src/actions/admin-help.ts +++ b/src/actions/admin-help.ts @@ -17,27 +17,27 @@ function parsePosition(value: FormDataEntryValue | null): number { export async function createHelpQuestion(formData: FormData): Promise { const staff = await requireStaff(); - const name = String(formData.get("name") ?? "") + const name = String(formData.get("name") ?? "").normalize("NFC") .trim() .slice(0, 255); - const content = String(formData.get("content") ?? "").trim(); + const content = String(formData.get("content") ?? "").normalize("NFC").trim(); if (!name || !content) return; - const imageUrl = String(formData.get("imageUrl") ?? "") + const imageUrl = String(formData.get("imageUrl") ?? "").normalize("NFC") .trim() .slice(0, 255); - const buttonText = String(formData.get("buttonText") ?? "") + const buttonText = String(formData.get("buttonText") ?? "").normalize("NFC") .trim() .slice(0, 255); - const buttonUrl = String(formData.get("buttonUrl") ?? "") + const buttonUrl = String(formData.get("buttonUrl") ?? "").normalize("NFC") .trim() .slice(0, 255); const buttonColor = - String(formData.get("buttonColor") ?? "") + String(formData.get("buttonColor") ?? "").normalize("NFC") .trim() .slice(0, 16) || "#eeb425"; const buttonBorderColor = - String(formData.get("buttonBorderColor") ?? "") + String(formData.get("buttonBorderColor") ?? "").normalize("NFC") .trim() .slice(0, 16) || "#facc15"; @@ -76,27 +76,27 @@ export async function updateHelpQuestion(formData: FormData): Promise { const id = formPositiveBigInt(formData, "id"); if (!id) return; - const name = String(formData.get("name") ?? "") + const name = String(formData.get("name") ?? "").normalize("NFC") .trim() .slice(0, 255); - const content = String(formData.get("content") ?? "").trim(); + const content = String(formData.get("content") ?? "").normalize("NFC").trim(); if (!name || !content) return; - const imageUrl = String(formData.get("imageUrl") ?? "") + const imageUrl = String(formData.get("imageUrl") ?? "").normalize("NFC") .trim() .slice(0, 255); - const buttonText = String(formData.get("buttonText") ?? "") + const buttonText = String(formData.get("buttonText") ?? "").normalize("NFC") .trim() .slice(0, 255); - const buttonUrl = String(formData.get("buttonUrl") ?? "") + const buttonUrl = String(formData.get("buttonUrl") ?? "").normalize("NFC") .trim() .slice(0, 255); const buttonColor = - String(formData.get("buttonColor") ?? "") + String(formData.get("buttonColor") ?? "").normalize("NFC") .trim() .slice(0, 16) || "#eeb425"; const buttonBorderColor = - String(formData.get("buttonBorderColor") ?? "") + String(formData.get("buttonBorderColor") ?? "").normalize("NFC") .trim() .slice(0, 16) || "#facc15"; diff --git a/src/actions/admin-housekeeping.ts b/src/actions/admin-housekeeping.ts index d0802fca..e3774daf 100644 --- a/src/actions/admin-housekeeping.ts +++ b/src/actions/admin-housekeeping.ts @@ -11,11 +11,11 @@ import { prisma } from "@/lib/prisma"; export async function upsertPermission(formData: FormData): Promise { await requireStaff(); - const permission = String(formData.get("permission") ?? "") + const permission = String(formData.get("permission") ?? "").normalize("NFC") .trim() .slice(0, 255); const minRank = Number(formData.get("minRank")); - const descriptionRaw = String(formData.get("description") ?? "") + const descriptionRaw = String(formData.get("description") ?? "").normalize("NFC") .trim() .slice(0, 255); const description = descriptionRaw.length > 0 ? descriptionRaw : null; @@ -38,7 +38,7 @@ export async function upsertPermission(formData: FormData): Promise { export async function deletePermission(formData: FormData): Promise { await requireStaff(); - const raw = String(formData.get("id") ?? ""); + const raw = String(formData.get("id") ?? "").normalize("NFC"); if (!raw) return; try { diff --git a/src/actions/admin-ip.ts b/src/actions/admin-ip.ts index 151a2785..a8769022 100644 --- a/src/actions/admin-ip.ts +++ b/src/actions/admin-ip.ts @@ -5,13 +5,13 @@ import { requireStaff } from "@/lib/admin/guard"; import { prisma } from "@/lib/prisma"; function parseIp(formData: FormData): string { - return String(formData.get("ipAddress") ?? "") + return String(formData.get("ipAddress") ?? "").normalize("NFC") .trim() .slice(0, 255); } function parseAsn(formData: FormData): string | null { - const asn = String(formData.get("asn") ?? "") + const asn = String(formData.get("asn") ?? "").normalize("NFC") .trim() .slice(0, 255); return asn || null; @@ -30,7 +30,7 @@ export async function addWhitelist(formData: FormData): Promise { export async function deleteWhitelist(formData: FormData): Promise { await requireStaff(); - const raw = String(formData.get("id") ?? "").trim(); + const raw = String(formData.get("id") ?? "").normalize("NFC").trim(); if (!raw) return; await prisma.websiteIpWhitelist.delete({ where: { id: BigInt(raw) } }); revalidatePath("/admin/ip"); @@ -49,7 +49,7 @@ export async function addBlacklist(formData: FormData): Promise { export async function deleteBlacklist(formData: FormData): Promise { await requireStaff(); - const raw = String(formData.get("id") ?? "").trim(); + const raw = String(formData.get("id") ?? "").normalize("NFC").trim(); if (!raw) return; await prisma.websiteIpBlacklist.delete({ where: { id: BigInt(raw) } }); revalidatePath("/admin/ip"); diff --git a/src/actions/admin-maintenance.ts b/src/actions/admin-maintenance.ts index 37b893ec..aed85517 100644 --- a/src/actions/admin-maintenance.ts +++ b/src/actions/admin-maintenance.ts @@ -39,11 +39,11 @@ export async function saveMaintenance(formData: FormData): Promise { // emulator/Laravel side expects. const enabled = formData.get("enabled") != null ? "1" : "0"; - const message = String(formData.get("message") ?? ""); + const message = String(formData.get("message") ?? "").normalize("NFC"); // Coerce the rank to a non-negative integer; fall back to AtomCMS's default // of 5 when the field is blank or garbage. - const rawRank = String(formData.get("min_rank") ?? "").trim(); + const rawRank = String(formData.get("min_rank") ?? "").normalize("NFC").trim(); const parsedRank = Number.parseInt(rawRank, 10); const minRank = Number.isFinite(parsedRank) && parsedRank >= 0 ? parsedRank : 5; diff --git a/src/actions/admin-permissions.ts b/src/actions/admin-permissions.ts index 0fcdd06d..c2bb3206 100644 --- a/src/actions/admin-permissions.ts +++ b/src/actions/admin-permissions.ts @@ -14,17 +14,17 @@ import { logServerError } from "@/lib/server-log"; // created_at/updated_at are managed here. function parseMinRank(formData: FormData): number { - const n = Number(String(formData.get("minRank") ?? "").trim()); + const n = Number(String(formData.get("minRank") ?? "").normalize("NFC").trim()); return Number.isInteger(n) && n >= 0 ? n : 1; } export async function createPermission(formData: FormData): Promise { const staff = await requireStaff(); - const permission = String(formData.get("permission") ?? "") + const permission = String(formData.get("permission") ?? "").normalize("NFC") .trim() .slice(0, 255); const minRank = parseMinRank(formData); - const description = String(formData.get("description") ?? "").trim() || null; + const description = String(formData.get("description") ?? "").normalize("NFC").trim() || null; if (!permission) return; const now = new Date(); @@ -51,14 +51,14 @@ export async function createPermission(formData: FormData): Promise { export async function updatePermission(formData: FormData): Promise { const staff = await requireStaff(); - const raw = String(formData.get("id") ?? ""); + const raw = String(formData.get("id") ?? "").normalize("NFC"); if (!raw) return; const id = BigInt(raw); - const permission = String(formData.get("permission") ?? "") + const permission = String(formData.get("permission") ?? "").normalize("NFC") .trim() .slice(0, 255); const minRank = parseMinRank(formData); - const description = String(formData.get("description") ?? "").trim() || null; + const description = String(formData.get("description") ?? "").normalize("NFC").trim() || null; if (!permission) return; try { diff --git a/src/actions/admin-rare-values.ts b/src/actions/admin-rare-values.ts index 5cdc0bde..d456cc13 100644 --- a/src/actions/admin-rare-values.ts +++ b/src/actions/admin-rare-values.ts @@ -7,10 +7,10 @@ import { formPositiveBigInt } from "@/lib/form-data"; export async function createCategory(formData: FormData): Promise { await requireStaff(); - const name = String(formData.get("name") ?? "") + const name = String(formData.get("name") ?? "").normalize("NFC") .trim() .slice(0, 255); - const badge = String(formData.get("badge") ?? "") + const badge = String(formData.get("badge") ?? "").normalize("NFC") .trim() .slice(0, 255); const priorityRaw = Number(formData.get("priority")); @@ -47,10 +47,10 @@ export async function createValue(formData: FormData): Promise { const categoryId = formPositiveBigInt(formData, "categoryId"); if (!categoryId) return; - const name = String(formData.get("name") ?? "") + const name = String(formData.get("name") ?? "").normalize("NFC") .trim() .slice(0, 255); - const furnitureIcon = String(formData.get("furnitureIcon") ?? "") + const furnitureIcon = String(formData.get("furnitureIcon") ?? "").normalize("NFC") .trim() .slice(0, 255); if (!name || !furnitureIcon) return; @@ -58,10 +58,10 @@ export async function createValue(formData: FormData): Promise { const itemIdRaw = Number(formData.get("itemId")); const itemId = Number.isFinite(itemIdRaw) && itemIdRaw > 0 ? Math.floor(itemIdRaw) : null; - const creditValueRaw = String(formData.get("creditValue") ?? "") + const creditValueRaw = String(formData.get("creditValue") ?? "").normalize("NFC") .trim() .slice(0, 255); - const currencyValueRaw = String(formData.get("currencyValue") ?? "") + const currencyValueRaw = String(formData.get("currencyValue") ?? "").normalize("NFC") .trim() .slice(0, 255); const currencyType = diff --git a/src/actions/admin-settings.ts b/src/actions/admin-settings.ts index a45d90ba..55ef4dd9 100644 --- a/src/actions/admin-settings.ts +++ b/src/actions/admin-settings.ts @@ -7,8 +7,8 @@ import { siteSettings } from "@/lib/services/site-settings"; export async function updateSetting(formData: FormData): Promise { await requireStaff(); - const key = String(formData.get("key") ?? "").trim(); - const value = String(formData.get("value") ?? ""); + const key = String(formData.get("key") ?? "").normalize("NFC").trim(); + const value = String(formData.get("value") ?? "").normalize("NFC"); if (!key) return; await prisma.websiteSetting.update({ where: { key }, data: { value } }); siteSettings.reload(); @@ -17,11 +17,11 @@ export async function updateSetting(formData: FormData): Promise { export async function createSetting(formData: FormData): Promise { await requireStaff(); - const key = String(formData.get("key") ?? "") + const key = String(formData.get("key") ?? "").normalize("NFC") .trim() .slice(0, 255); - const value = String(formData.get("value") ?? ""); - const comment = String(formData.get("comment") ?? "") + const value = String(formData.get("value") ?? "").normalize("NFC"); + const comment = String(formData.get("comment") ?? "").normalize("NFC") .trim() .slice(0, 255); if (!key) return; @@ -36,7 +36,7 @@ export async function createSetting(formData: FormData): Promise { export async function deleteSetting(formData: FormData): Promise { await requireStaff(); - const key = String(formData.get("key") ?? "").trim(); + const key = String(formData.get("key") ?? "").normalize("NFC").trim(); if (!key) return; await prisma.websiteSetting.delete({ where: { key } }); siteSettings.reload(); diff --git a/src/actions/admin-shop.ts b/src/actions/admin-shop.ts index dfe1ff90..2885cf26 100644 --- a/src/actions/admin-shop.ts +++ b/src/actions/admin-shop.ts @@ -14,7 +14,7 @@ import { logServerError } from "@/lib/server-log"; /** Parse an UnsignedInt form value, returning null when blank/invalid/negative. */ function optUInt(formData: FormData, key: string): number | null { - const raw = String(formData.get(key) ?? "").trim(); + const raw = String(formData.get(key) ?? "").normalize("NFC").trim(); if (raw === "") return null; const n = Number(raw); if (!Number.isFinite(n) || n < 0) return null; @@ -30,7 +30,7 @@ function reqUInt(formData: FormData, key: string): number { export async function createShopArticle(formData: FormData): Promise { const staff = await requireStaff(); - const name = String(formData.get("name") ?? "") + const name = String(formData.get("name") ?? "").normalize("NFC") .trim() .slice(0, 255); if (!name) return; @@ -40,13 +40,13 @@ export async function createShopArticle(formData: FormData): Promise { const created = await prisma.websiteShopArticles.create({ data: { name, - info: String(formData.get("info") ?? "") + info: String(formData.get("info") ?? "").normalize("NFC") .trim() .slice(0, 255), - iconUrl: String(formData.get("icon") ?? "") + iconUrl: String(formData.get("icon") ?? "").normalize("NFC") .trim() .slice(0, 255), - color: String(formData.get("color") ?? "") + color: String(formData.get("color") ?? "").normalize("NFC") .trim() .slice(0, 255), costs: reqUInt(formData, "costs"), @@ -55,7 +55,7 @@ export async function createShopArticle(formData: FormData): Promise { duckets: optUInt(formData, "duckets"), diamonds: optUInt(formData, "diamonds"), badges: - String(formData.get("badges") ?? "") + String(formData.get("badges") ?? "").normalize("NFC") .trim() .slice(0, 255) || null, position: reqUInt(formData, "position"), @@ -85,7 +85,7 @@ export async function updateShopArticle(formData: FormData): Promise { const id = formPositiveBigInt(formData, "id"); if (!id) return; - const name = String(formData.get("name") ?? "") + const name = String(formData.get("name") ?? "").normalize("NFC") .trim() .slice(0, 255); if (!name) return; @@ -95,13 +95,13 @@ export async function updateShopArticle(formData: FormData): Promise { where: { id }, data: { name, - info: String(formData.get("info") ?? "") + info: String(formData.get("info") ?? "").normalize("NFC") .trim() .slice(0, 255), - iconUrl: String(formData.get("icon") ?? "") + iconUrl: String(formData.get("icon") ?? "").normalize("NFC") .trim() .slice(0, 255), - color: String(formData.get("color") ?? "") + color: String(formData.get("color") ?? "").normalize("NFC") .trim() .slice(0, 255), costs: reqUInt(formData, "costs"), @@ -110,7 +110,7 @@ export async function updateShopArticle(formData: FormData): Promise { duckets: optUInt(formData, "duckets"), diamonds: optUInt(formData, "diamonds"), badges: - String(formData.get("badges") ?? "") + String(formData.get("badges") ?? "").normalize("NFC") .trim() .slice(0, 255) || null, position: reqUInt(formData, "position"), diff --git a/src/actions/admin-teams.ts b/src/actions/admin-teams.ts index d7e1be18..360a7641 100644 --- a/src/actions/admin-teams.ts +++ b/src/actions/admin-teams.ts @@ -7,12 +7,12 @@ import { prisma } from "@/lib/prisma"; export async function createTeam(formData: FormData): Promise { await requireStaff(); - const rankName = String(formData.get("rankName") ?? "").trim(); + const rankName = String(formData.get("rankName") ?? "").normalize("NFC").trim(); if (!rankName) return; - const badge = String(formData.get("badge") ?? "").trim(); - const jobDescription = String(formData.get("jobDescription") ?? "").trim(); - const staffColor = String(formData.get("staffColor") ?? "").trim() || "#327fa8"; + const badge = String(formData.get("badge") ?? "").normalize("NFC").trim(); + const jobDescription = String(formData.get("jobDescription") ?? "").normalize("NFC").trim(); + const staffColor = String(formData.get("staffColor") ?? "").normalize("NFC").trim() || "#327fa8"; const hiddenRank = formData.get("hiddenRank") === "on"; const now = new Date(); diff --git a/src/actions/admin-theme.ts b/src/actions/admin-theme.ts index 3997bb8a..d2b56ffe 100644 --- a/src/actions/admin-theme.ts +++ b/src/actions/admin-theme.ts @@ -30,24 +30,24 @@ export async function saveTheme(formData: FormData): Promise { for (const mode of ["light", "dark"] as const) { for (const key of THEME_COLOR_KEYS) { const dbKey = settingKey(key, mode); - const raw = String(formData.get(dbKey) ?? "").trim(); + const raw = String(formData.get(dbKey) ?? "").normalize("NFC").trim(); if (raw && COLOR_RE.test(raw)) await writeSetting(dbKey, raw); } } - const radius = String(formData.get("border_radius") ?? "").trim(); + const radius = String(formData.get("border_radius") ?? "").normalize("NFC").trim(); if (/^\d{1,3}$/.test(radius)) await writeSetting("border_radius", radius); // Typography - const font = String(formData.get("font_family") ?? "").trim(); + const font = String(formData.get("font_family") ?? "").normalize("NFC").trim(); if (font in FONTS) await writeSetting("font_family", font); for (const key of HEADING_KEYS) { - const v = String(formData.get(key) ?? "").trim(); + const v = String(formData.get(key) ?? "").normalize("NFC").trim(); if (/^\d{1,3}$/.test(v)) await writeSetting(key, v); } // Raw custom CSS (staff-trusted; length-capped, ThemeVars injects it as-is). if (formData.has("custom_css")) { - const cssRaw = String(formData.get("custom_css") ?? "").slice(0, CUSTOM_CSS_MAX); + const cssRaw = String(formData.get("custom_css") ?? "").normalize("NFC").slice(0, CUSTOM_CSS_MAX); await writeSetting("custom_css", cssRaw); } @@ -66,7 +66,7 @@ export async function saveTheme(formData: FormData): Promise { export async function applyPreset(formData: FormData): Promise { const staff = await requireStaff(); - const name = String(formData.get("preset") ?? ""); + const name = String(formData.get("preset") ?? "").normalize("NFC"); // eslint-disable-next-line security/detect-object-injection -- guarded by null check below const preset = PRESETS[name]; if (!preset) redirect("/admin/theme"); diff --git a/src/actions/admin-user-edit.ts b/src/actions/admin-user-edit.ts index 465516a6..ec86eb5c 100644 --- a/src/actions/admin-user-edit.ts +++ b/src/actions/admin-user-edit.ts @@ -41,9 +41,9 @@ export async function updateUser(formData: FormData): Promise { if (!existing) return; // users row — only existing, safe columns. - const mailRaw = String(formData.get("mail") ?? "").trim(); - const motto = String(formData.get("motto") ?? "").slice(0, 127); - const look = String(formData.get("look") ?? "").slice(0, 256); + const mailRaw = String(formData.get("mail") ?? "").normalize("NFC").trim(); + const motto = String(formData.get("motto") ?? "").normalize("NFC").slice(0, 127); + const look = String(formData.get("look") ?? "").normalize("NFC").slice(0, 256); const rank = toInt(formData.get("rank"), 1); const credits = toInt(formData.get("credits"), 0); const pixels = toInt(formData.get("pixels"), 0); diff --git a/src/actions/admin-users.ts b/src/actions/admin-users.ts index 1762dcd8..1583b0b6 100644 --- a/src/actions/admin-users.ts +++ b/src/actions/admin-users.ts @@ -30,7 +30,7 @@ export async function giveCurrency(formData: FormData): Promise { export async function setMotto(formData: FormData): Promise { await requireStaff(); const userId = Number(formData.get("userId")); - const motto = String(formData.get("motto") ?? "").slice(0, 127); + const motto = String(formData.get("motto") ?? "").normalize("NFC").slice(0, 127); if (userId > 0) { await prisma.user.update({ where: { id: userId }, data: { motto } }); await rcon.setMotto(userId, motto); @@ -59,13 +59,13 @@ export async function setRank(formData: FormData): Promise { export async function alertUser(formData: FormData): Promise { await requireStaff(); const userId = Number(formData.get("userId")); - const message = String(formData.get("message") ?? "").trim(); + const message = String(formData.get("message") ?? "").normalize("NFC").trim(); if (userId > 0 && message) await rcon.alertUser(userId, message); } export async function disconnectUser(formData: FormData): Promise { await requireStaff(); const userId = Number(formData.get("userId")); - const username = String(formData.get("username") ?? ""); + const username = String(formData.get("username") ?? "").normalize("NFC"); if (userId > 0) await rcon.disconnectUser(userId, username); } diff --git a/src/actions/admin-vouchers.ts b/src/actions/admin-vouchers.ts index fa818b6b..2c1ac0e7 100644 --- a/src/actions/admin-vouchers.ts +++ b/src/actions/admin-vouchers.ts @@ -9,7 +9,7 @@ import { logServerError } from "@/lib/server-log"; export async function createVoucher(formData: FormData): Promise { await requireStaff(); - const code = String(formData.get("code") ?? "") + const code = String(formData.get("code") ?? "").normalize("NFC") .trim() .slice(0, 255); const amount = Number(formData.get("amount")); @@ -18,7 +18,7 @@ export async function createVoucher(formData: FormData): Promise { if (!code || !(amount > 0)) return; - const expiresRaw = String(formData.get("expiresAt") ?? "").trim(); + const expiresRaw = String(formData.get("expiresAt") ?? "").normalize("NFC").trim(); let expiresAt: Date | null = null; if (expiresRaw) { const parsed = new Date(expiresRaw); diff --git a/src/actions/admin-vpn.ts b/src/actions/admin-vpn.ts index 6a9db60e..2396ba95 100644 --- a/src/actions/admin-vpn.ts +++ b/src/actions/admin-vpn.ts @@ -27,17 +27,17 @@ export async function saveVpn(formData: FormData): Promise { const staff = await requireStaff(); // Toggle: an unchecked checkbox submits nothing, so absence === disabled. - const enabled = String(formData.get("vpn_block_enabled") ?? "").trim() !== ""; + const enabled = String(formData.get("vpn_block_enabled") ?? "").normalize("NFC").trim() !== ""; - const providerRaw = String(formData.get("vpn_provider") ?? "") + const providerRaw = String(formData.get("vpn_provider") ?? "").normalize("NFC") .trim() .toLowerCase(); const provider = ALLOWED_PROVIDERS.has(providerRaw) ? providerRaw : "none"; - const apiKey = String(formData.get("vpn_api_key") ?? "") + const apiKey = String(formData.get("vpn_api_key") ?? "").normalize("NFC") .trim() .slice(0, 255); - const blockMessage = String(formData.get("vpn_block_message") ?? "") + const blockMessage = String(formData.get("vpn_block_message") ?? "").normalize("NFC") .trim() .slice(0, 255); diff --git a/src/actions/admin-wordfilter.ts b/src/actions/admin-wordfilter.ts index a3265835..f927d895 100644 --- a/src/actions/admin-wordfilter.ts +++ b/src/actions/admin-wordfilter.ts @@ -7,7 +7,7 @@ import { rcon } from "@/lib/services/rcon"; export async function addWord(formData: FormData): Promise { await requireStaff(); - const word = String(formData.get("word") ?? "") + const word = String(formData.get("word") ?? "").normalize("NFC") .trim() .slice(0, 255); if (!word) return; @@ -23,7 +23,7 @@ export async function addWord(formData: FormData): Promise { export async function deleteWord(formData: FormData): Promise { await requireStaff(); - const raw = String(formData.get("id") ?? ""); + const raw = String(formData.get("id") ?? "").normalize("NFC"); if (!raw) return; try { diff --git a/src/actions/admin-writeable-boxes.ts b/src/actions/admin-writeable-boxes.ts index 627f3ce2..d19120e7 100644 --- a/src/actions/admin-writeable-boxes.ts +++ b/src/actions/admin-writeable-boxes.ts @@ -11,7 +11,7 @@ import { logStaffActivity } from "@/lib/services/staff-activity"; /** Parse a non-negative Int form value, falling back to 0. */ function reqInt(formData: FormData, key: string): number { - const raw = String(formData.get(key) ?? "").trim(); + const raw = String(formData.get(key) ?? "").normalize("NFC").trim(); if (raw === "") return 0; const n = Number(raw); if (!Number.isFinite(n) || n < 0) return 0; @@ -20,7 +20,7 @@ function reqInt(formData: FormData, key: string): number { /** Parse the BigInt `id` form value, returning null when blank/invalid. */ function parseId(formData: FormData): bigint | null { - const raw = String(formData.get("id") ?? "").trim(); + const raw = String(formData.get("id") ?? "").normalize("NFC").trim(); if (!raw) return null; try { return BigInt(raw); @@ -38,7 +38,7 @@ function revalidate(): void { export async function createBox(formData: FormData): Promise { const staff = await requireStaff(); - const title = String(formData.get("title") ?? "") + const title = String(formData.get("title") ?? "").normalize("NFC") .trim() .slice(0, 255); if (!title) return; @@ -49,12 +49,12 @@ export async function createBox(formData: FormData): Promise { data: { title, icon: - String(formData.get("icon") ?? "") + String(formData.get("icon") ?? "").normalize("NFC") .trim() .slice(0, 255) || null, - content: String(formData.get("content") ?? ""), + content: String(formData.get("content") ?? "").normalize("NFC"), position: reqInt(formData, "position"), - isActive: String(formData.get("isActive") ?? "") === "1", + isActive: String(formData.get("isActive") ?? "").normalize("NFC") === "1", createdAt: now, updatedAt: now, }, @@ -80,7 +80,7 @@ export async function updateBox(formData: FormData): Promise { const id = parseId(formData); if (id == null) return; - const title = String(formData.get("title") ?? "") + const title = String(formData.get("title") ?? "").normalize("NFC") .trim() .slice(0, 255); if (!title) return; @@ -91,12 +91,12 @@ export async function updateBox(formData: FormData): Promise { data: { title, icon: - String(formData.get("icon") ?? "") + String(formData.get("icon") ?? "").normalize("NFC") .trim() .slice(0, 255) || null, - content: String(formData.get("content") ?? ""), + content: String(formData.get("content") ?? "").normalize("NFC"), position: reqInt(formData, "position"), - isActive: String(formData.get("isActive") ?? "") === "1", + isActive: String(formData.get("isActive") ?? "").normalize("NFC") === "1", updatedAt: new Date(), }, }); @@ -143,7 +143,7 @@ export async function toggleBox(formData: FormData): Promise { if (id == null) return; // `next` carries the desired state ("1" to activate, anything else to hide). - const next = String(formData.get("next") ?? "") === "1"; + const next = String(formData.get("next") ?? "").normalize("NFC") === "1"; try { await prisma.websiteWriteableBoxes.update({ diff --git a/src/actions/applications.ts b/src/actions/applications.ts index 1c2160ed..1d6fd917 100644 --- a/src/actions/applications.ts +++ b/src/actions/applications.ts @@ -28,7 +28,7 @@ export async function applyStaff(formData: FormData): Promise { const rankId = Number(formData.get("rankId")); if (!Number.isInteger(rankId) || rankId <= 0) return; - const content = String(formData.get("content") ?? "") + const content = String(formData.get("content") ?? "").normalize("NFC") .trim() .slice(0, CONTENT_MAX); if (content.length < CONTENT_MIN) return; @@ -72,7 +72,7 @@ export async function applyTeam(formData: FormData): Promise { const rankId = Number(formData.get("teamId")); if (!Number.isInteger(rankId) || rankId <= 0) return; - const content = String(formData.get("content") ?? "") + const content = String(formData.get("content") ?? "").normalize("NFC") .trim() .slice(0, CONTENT_MAX); if (content.length < CONTENT_MIN) return; diff --git a/src/actions/article-comments.ts b/src/actions/article-comments.ts index 732ea4d8..c0a647be 100644 --- a/src/actions/article-comments.ts +++ b/src/actions/article-comments.ts @@ -21,7 +21,7 @@ export async function postComment(formData: FormData): Promise { const userId = Number(session.user.id); if (!Number.isFinite(userId)) return; - const comment = String(formData.get("comment") ?? "") + const comment = String(formData.get("comment") ?? "").normalize("NFC") .trim() .slice(0, COMMENT_MAX); if (!comment) return; @@ -29,7 +29,7 @@ export async function postComment(formData: FormData): Promise { // Block filtered/AI-flagged content before it touches the DB (fail-open). if (!(await isAllowed(comment)).ok) return; - const articleIdRaw = String(formData.get("articleId") ?? "").trim(); + const articleIdRaw = String(formData.get("articleId") ?? "").normalize("NFC").trim(); if (!/^\d+$/.test(articleIdRaw)) return; let articleId: bigint; diff --git a/src/actions/article-reactions.ts b/src/actions/article-reactions.ts index bb681816..791fffbb 100644 --- a/src/actions/article-reactions.ts +++ b/src/actions/article-reactions.ts @@ -31,12 +31,12 @@ export async function toggleReaction(formData: FormData): Promise { const userId = Number(session.user.id); if (!Number.isFinite(userId)) return; - const reaction = String(formData.get("reaction") ?? "") + const reaction = String(formData.get("reaction") ?? "").normalize("NFC") .trim() .toLowerCase(); if (!ALLOWED_REACTIONS.has(reaction)) return; - const articleIdRaw = String(formData.get("articleId") ?? "").trim(); + const articleIdRaw = String(formData.get("articleId") ?? "").normalize("NFC").trim(); if (!/^\d+$/.test(articleIdRaw)) return; let articleId: bigint; diff --git a/src/actions/auth-precheck.ts b/src/actions/auth-precheck.ts index 98699068..9027edd7 100644 --- a/src/actions/auth-precheck.ts +++ b/src/actions/auth-precheck.ts @@ -12,7 +12,7 @@ export type PrecheckResult = "ok" | "invalid" | "twofactor"; * TOTP code is still required. Lets the login form do the two-step 2FA flow. */ export async function precheckLogin(username: string, password: string): Promise { - const u = String(username ?? "").trim(); + const u = String(username ?? "").normalize("NFC").trim(); const p = String(password ?? ""); if (!u || !p) return "invalid"; diff --git a/src/actions/commandocentrum.ts b/src/actions/commandocentrum.ts index 064fa159..a3656f14 100644 --- a/src/actions/commandocentrum.ts +++ b/src/actions/commandocentrum.ts @@ -42,7 +42,7 @@ export async function updateNavigator(): Promise { /** Broadcast a hotel-wide alert to every connected user (rcon: hotelalert). */ export async function hotelAlert(formData: FormData): Promise { await requireStaff(); - const message = String(formData.get("message") ?? "") + const message = String(formData.get("message") ?? "").normalize("NFC") .trim() .slice(0, 512); if (!message) return; @@ -58,7 +58,7 @@ export async function hotelAlert(formData: FormData): Promise { export async function disconnectUser(formData: FormData): Promise { await requireStaff(); const userId = Number(formData.get("userId")); - const username = String(formData.get("username") ?? "").trim(); + const username = String(formData.get("username") ?? "").normalize("NFC").trim(); if (!userId || !username) return; try { await rcon.disconnectUser(userId, username); @@ -72,7 +72,7 @@ export async function disconnectUser(formData: FormData): Promise { export async function alertUser(formData: FormData): Promise { await requireStaff(); const userId = Number(formData.get("userId")); - const message = String(formData.get("message") ?? "") + const message = String(formData.get("message") ?? "").normalize("NFC") .trim() .slice(0, 512); if (!userId || !message) return; @@ -144,7 +144,7 @@ export async function giveDiamonds(formData: FormData): Promise { export async function giveBadge(formData: FormData): Promise { await requireStaff(); const userId = Number(formData.get("userId")); - const badge = String(formData.get("badge") ?? "").trim(); + const badge = String(formData.get("badge") ?? "").normalize("NFC").trim(); if (!userId || !badge) return; try { await rcon.giveBadge(userId, badge); @@ -158,7 +158,7 @@ export async function giveBadge(formData: FormData): Promise { export async function setMotto(formData: FormData): Promise { await requireStaff(); const userId = Number(formData.get("userId")); - const motto = String(formData.get("motto") ?? "") + const motto = String(formData.get("motto") ?? "").normalize("NFC") .trim() .slice(0, 127); if (!userId || !motto) return; @@ -188,7 +188,7 @@ export async function setRank(formData: FormData): Promise { export async function executeCommand(formData: FormData): Promise { await requireStaff(); const userId = Number(formData.get("userId")); - const command = String(formData.get("command") ?? "").trim(); + const command = String(formData.get("command") ?? "").normalize("NFC").trim(); if (!userId || !command) return; try { await rcon.executeCommand(userId, command); diff --git a/src/actions/draw-badge.ts b/src/actions/draw-badge.ts index 831e6ef7..bd0b969a 100644 --- a/src/actions/draw-badge.ts +++ b/src/actions/draw-badge.ts @@ -50,7 +50,7 @@ export async function buyBadge(formData: FormData): Promise { // The form posts the badge row id; everything else (price, code) is resolved // server-side from trusted data — never from the client. - const rawId = String(formData.get("id") ?? "").trim(); + const rawId = String(formData.get("id") ?? "").normalize("NFC").trim(); if (!/^\d+$/.test(rawId)) redirect("/draw-badge?error=invalid"); let outcome: "bought" | "invalid" | "credits" | "fail"; diff --git a/src/actions/guestbook.ts b/src/actions/guestbook.ts index 70412953..2526062a 100644 --- a/src/actions/guestbook.ts +++ b/src/actions/guestbook.ts @@ -25,7 +25,7 @@ export async function postGuestbook(formData: FormData): Promise { const profileId = Number(formData.get("profileId")); if (!Number.isInteger(profileId) || profileId <= 0) return; - const message = String(formData.get("message") ?? "") + const message = String(formData.get("message") ?? "").normalize("NFC") .trim() .slice(0, MESSAGE_MAX); if (!message) return; @@ -34,7 +34,7 @@ export async function postGuestbook(formData: FormData): Promise { if (!(await isAllowed(message)).ok) return; // Optional: used only to revalidate the correct profile route. - const username = String(formData.get("username") ?? "").trim(); + const username = String(formData.get("username") ?? "").normalize("NFC").trim(); const now = new Date(); try { diff --git a/src/actions/help-tickets.ts b/src/actions/help-tickets.ts index 101e644c..57c5bff2 100644 --- a/src/actions/help-tickets.ts +++ b/src/actions/help-tickets.ts @@ -22,10 +22,10 @@ export async function createTicket(formData: FormData): Promise { if (!(await rateLimit(`ticket:${userId}`, 3, 60_000)).ok) return; const raw = { - title: String(formData.get("title") ?? "") + title: String(formData.get("title") ?? "").normalize("NFC") .trim() .slice(0, 255), - content: String(formData.get("content") ?? "") + content: String(formData.get("content") ?? "").normalize("NFC") .trim() .slice(0, 5000), }; diff --git a/src/actions/password-reset.ts b/src/actions/password-reset.ts index 76eab732..f4ca6f8a 100644 --- a/src/actions/password-reset.ts +++ b/src/actions/password-reset.ts @@ -15,7 +15,7 @@ function sha256(s: string): string { } export async function requestReset(formData: FormData): Promise { - const email = String(formData.get("email") ?? "") + const email = String(formData.get("email") ?? "").normalize("NFC") .trim() .toLowerCase(); @@ -49,11 +49,11 @@ export async function requestReset(formData: FormData): Promise { } export async function resetPassword(formData: FormData): Promise { - const email = String(formData.get("email") ?? "") + const email = String(formData.get("email") ?? "").normalize("NFC") .trim() .toLowerCase(); - const token = String(formData.get("token") ?? "").trim(); - const password = String(formData.get("password") ?? ""); + const token = String(formData.get("token") ?? "").normalize("NFC").trim(); + const password = String(formData.get("password") ?? "").normalize("NFC"); // Throttle reset attempts per IP (5 per 15 min) to prevent token brute-force. if (!(await rateLimit(`resetpwd:${await clientIp()}`, 5, 15 * 60_000)).ok) { diff --git a/src/actions/radio-apply.ts b/src/actions/radio-apply.ts index 48e83464..6a1bcef1 100644 --- a/src/actions/radio-apply.ts +++ b/src/actions/radio-apply.ts @@ -11,7 +11,7 @@ const TEXT_MAX = 5000; const STYLE_MAX = 5000; function str(form: FormData, key: string, max: number): string { - return String(form.get(key) ?? "") + return String(form.get(key) ?? "").normalize("NFC") .trim() .slice(0, max); } diff --git a/src/actions/radio-requests.ts b/src/actions/radio-requests.ts index da61f35a..f8f138ed 100644 --- a/src/actions/radio-requests.ts +++ b/src/actions/radio-requests.ts @@ -12,10 +12,10 @@ export async function submitRequest(formData: FormData): Promise { const userId = Number(session?.user?.id); if (!Number.isInteger(userId) || userId <= 0) return; - const songTitle = String(formData.get("songTitle") ?? "") + const songTitle = String(formData.get("songTitle") ?? "").normalize("NFC") .trim() .slice(0, SONG_MAX); - const artist = String(formData.get("artist") ?? "") + const artist = String(formData.get("artist") ?? "").normalize("NFC") .trim() .slice(0, ARTIST_MAX); if (!songTitle && !artist) return; diff --git a/src/actions/radio-shouts.ts b/src/actions/radio-shouts.ts index 19a6e88e..af62dfaa 100644 --- a/src/actions/radio-shouts.ts +++ b/src/actions/radio-shouts.ts @@ -28,7 +28,7 @@ export async function postShout(formData: FormData): Promise { if (!(await rateLimit(`shout:${userId}`, 5, 30_000)).ok) return; const raw = { - message: String(formData.get("message") ?? "") + message: String(formData.get("message") ?? "").normalize("NFC") .trim() .slice(0, 255), }; diff --git a/src/actions/register.ts b/src/actions/register.ts index d07cd21e..c9cbf718 100644 --- a/src/actions/register.ts +++ b/src/actions/register.ts @@ -31,12 +31,12 @@ const DEFAULT_LOOK = "hr-100-.hd-180-1.ch-255-66.lg-280-110.sh-305-62"; export async function register(prevState: string | null, formData: FormData): Promise { const raw = { - username: String(formData.get("username") ?? "").trim(), - mail: String(formData.get("mail") ?? "") + username: String(formData.get("username") ?? "").normalize("NFC").trim(), + mail: String(formData.get("mail") ?? "").normalize("NFC") .trim() .toLowerCase(), - password: String(formData.get("password") ?? ""), - look: String(formData.get("look") ?? "").trim() || DEFAULT_LOOK, + password: String(formData.get("password") ?? "").normalize("NFC"), + look: String(formData.get("look") ?? "").normalize("NFC").trim() || DEFAULT_LOOK, }; const parsed = registerSchema.safeParse(raw); @@ -56,7 +56,7 @@ export async function register(prevState: string | null, formData: FormData): Pr // CAPTCHA (Turnstile / reCAPTCHA) — only enforced when configured in settings. const cfg = await captchaConfig(); if (cfg.provider !== "none") { - const token = String(formData.get(cfg.field) ?? ""); + const token = String(formData.get(cfg.field) ?? "").normalize("NFC"); if (!(await verifyCaptcha(token, ip))) return "Captcha verification failed. Please try again."; } diff --git a/src/actions/social.ts b/src/actions/social.ts index 68fc4e98..510378c0 100644 --- a/src/actions/social.ts +++ b/src/actions/social.ts @@ -61,7 +61,7 @@ export async function sendFriendRequest(formData: FormData): Promise { // Optional: revalidate the target profile if a username was supplied, purely // to refresh any request-state UI rendered there. - const username = String(formData.get("username") ?? "").trim(); + const username = String(formData.get("username") ?? "").normalize("NFC").trim(); if (username) revalidatePath(`/u/${username}`); } @@ -85,10 +85,10 @@ export async function postThread(formData: FormData): Promise { const guildId = Number(formData.get("guildId")); if (!Number.isInteger(guildId) || guildId <= 0) return; - const subject = String(formData.get("subject") ?? "") + const subject = String(formData.get("subject") ?? "").normalize("NFC") .trim() .slice(0, SUBJECT_MAX); - const message = String(formData.get("message") ?? "") + const message = String(formData.get("message") ?? "").normalize("NFC") .trim() .slice(0, MESSAGE_MAX); if (!subject || !message) return; diff --git a/src/actions/twofactor.ts b/src/actions/twofactor.ts index 57d8adea..cd494756 100644 --- a/src/actions/twofactor.ts +++ b/src/actions/twofactor.ts @@ -92,7 +92,7 @@ export async function confirmTwoFactor(formData: FormData): Promise { if (!(await rateLimit(`2fa-confirm:${id}`, 5, 30_000)).ok) redirect("/settings/2fa?error=ratelimit"); - const code = String(formData.get("code") ?? "").trim(); + const code = String(formData.get("code") ?? "").normalize("NFC").trim(); const { ok } = await verifyTwoFactorCode(id, code); if (!ok) redirect("/settings/2fa?error=badcode"); @@ -107,7 +107,7 @@ export async function disableTwoFactor(formData: FormData): Promise { if (!(await rateLimit(`2fa-disable:${id}`, 5, 30_000)).ok) redirect("/settings/2fa?error=ratelimit"); - const code = String(formData.get("code") ?? "").trim(); + const code = String(formData.get("code") ?? "").normalize("NFC").trim(); const { ok } = await verifyTwoFactorCode(id, code); if (!ok) redirect("/settings/2fa?error=badcode"); diff --git a/src/actions/user-settings.ts b/src/actions/user-settings.ts index 37693139..61ff8d63 100644 --- a/src/actions/user-settings.ts +++ b/src/actions/user-settings.ts @@ -34,7 +34,7 @@ const updateMottoAction = authAction( ); export async function updateMotto(formData: FormData): Promise { - const motto = String(formData.get("motto") ?? "").slice(0, MOTTO_MAX); + const motto = String(formData.get("motto") ?? "").normalize("NFC").slice(0, MOTTO_MAX); await updateMottoAction({ motto }); } diff --git a/src/actions/voucher.ts b/src/actions/voucher.ts index d842f920..3af634f6 100644 --- a/src/actions/voucher.ts +++ b/src/actions/voucher.ts @@ -34,7 +34,7 @@ export async function redeem(_prev: RedeemState, formData: FormData): Promise