Add NFC normalization to all FormData inputs across 41 server actions
Local Build and Deploy / deploy (push) Successful in 59s
Local Build and Deploy / deploy (push) Successful in 59s
All user-supplied string values from FormData now go through
String.prototype.normalize('NFC') to prevent Unicode homoglyph
attacks and canonicalization bypasses. NFC is idempotent for
already-normalized strings, so this is a pure security improvement
with zero behavioral change for legitimate users.
This commit is contained in:
1 parent
e2fc7ea1a4
commit
e5ae51bff7
41 files changed
+152
-152
No files matched your search
@@ -12,7 +12,7 @@ import { formPositiveBigInt } from "@/lib/form-data";
|
|||||||
|
|
||||||
export async function createAd(formData: FormData): Promise<void> {
|
export async function createAd(formData: FormData): Promise<void> {
|
||||||
const staff = await requireStaff();
|
const staff = await requireStaff();
|
||||||
const image = String(formData.get("image") ?? "")
|
const image = String(formData.get("image") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
if (!image) return;
|
if (!image) return;
|
||||||
@@ -39,10 +39,10 @@ export async function createAd(formData: FormData): Promise<void> {
|
|||||||
|
|
||||||
export async function updateAd(formData: FormData): Promise<void> {
|
export async function updateAd(formData: FormData): Promise<void> {
|
||||||
const staff = await requireStaff();
|
const staff = await requireStaff();
|
||||||
const raw = String(formData.get("id") ?? "");
|
const raw = String(formData.get("id") ?? "").normalize("NFC");
|
||||||
if (!/^\d+$/.test(raw)) return;
|
if (!/^\d+$/.test(raw)) return;
|
||||||
const id = BigInt(raw);
|
const id = BigInt(raw);
|
||||||
const image = String(formData.get("image") ?? "")
|
const image = String(formData.get("image") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
if (!image) return;
|
if (!image) return;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { rcon } from "@/lib/services/rcon";
|
|||||||
export async function sendHotelAlert(formData: FormData): Promise<void> {
|
export async function sendHotelAlert(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requireStaff();
|
||||||
|
|
||||||
const message = String(formData.get("message") ?? "")
|
const message = String(formData.get("message") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 1000);
|
.slice(0, 1000);
|
||||||
if (!message) return;
|
if (!message) return;
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ async function uniqueSlug(title: string): Promise<string> {
|
|||||||
|
|
||||||
export async function createArticle(formData: FormData): Promise<void> {
|
export async function createArticle(formData: FormData): Promise<void> {
|
||||||
const staff = await requireStaff();
|
const staff = await requireStaff();
|
||||||
const title = String(formData.get("title") ?? "").trim();
|
const title = String(formData.get("title") ?? "").normalize("NFC").trim();
|
||||||
const shortStory = String(formData.get("shortStory") ?? "").trim();
|
const shortStory = String(formData.get("shortStory") ?? "").normalize("NFC").trim();
|
||||||
const fullStory = String(formData.get("fullStory") ?? "").trim();
|
const fullStory = String(formData.get("fullStory") ?? "").normalize("NFC").trim();
|
||||||
const image = String(formData.get("image") ?? "").trim();
|
const image = String(formData.get("image") ?? "").normalize("NFC").trim();
|
||||||
if (!title) return;
|
if (!title) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -53,14 +53,14 @@ export async function updateArticle(formData: FormData): Promise<void> {
|
|||||||
await prisma.websiteArticles.update({
|
await prisma.websiteArticles.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
title: String(formData.get("title") ?? "")
|
title: String(formData.get("title") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255),
|
.slice(0, 255),
|
||||||
shortStory: String(formData.get("shortStory") ?? "")
|
shortStory: String(formData.get("shortStory") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255),
|
.slice(0, 255),
|
||||||
fullStory: String(formData.get("fullStory") ?? "").trim(),
|
fullStory: String(formData.get("fullStory") ?? "").normalize("NFC").trim(),
|
||||||
image: String(formData.get("image") ?? "")
|
image: String(formData.get("image") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255),
|
.slice(0, 255),
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export async function uploadBadge(formData: FormData): Promise<void> {
|
|||||||
back("error", "Badge upload directory not configured");
|
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)) {
|
if (!CODE_RE.test(code)) {
|
||||||
back("error", "Invalid badge code (use A-Z, 0-9, _ or -, max 64 chars)");
|
back("error", "Invalid badge code (use A-Z, 0-9, _ or -, max 64 chars)");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export async function giveBadge(formData: FormData): Promise<void> {
|
|||||||
await requireStaff();
|
await requireStaff();
|
||||||
|
|
||||||
const userId = Number(formData.get("userId"));
|
const userId = Number(formData.get("userId"));
|
||||||
const code = String(formData.get("code") ?? "")
|
const code = String(formData.get("code") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 32);
|
.slice(0, 32);
|
||||||
if (!(userId > 0) || code.length === 0) return;
|
if (!(userId > 0) || code.length === 0) return;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export async function createBan(formData: FormData): Promise<void> {
|
|||||||
const staff = await requireStaff();
|
const staff = await requireStaff();
|
||||||
const userId = Number(formData.get("userId"));
|
const userId = Number(formData.get("userId"));
|
||||||
const reason =
|
const reason =
|
||||||
String(formData.get("reason") ?? "")
|
String(formData.get("reason") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 200) || "Banned";
|
.slice(0, 200) || "Banned";
|
||||||
const hours = Number(formData.get("hours"));
|
const hours = Number(formData.get("hours"));
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ import { formPositiveBigInt } from "@/lib/form-data";
|
|||||||
|
|
||||||
export async function createEmailTemplate(formData: FormData): Promise<void> {
|
export async function createEmailTemplate(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requireStaff();
|
||||||
const name = String(formData.get("name") ?? "")
|
const name = String(formData.get("name") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
const subject = String(formData.get("subject") ?? "")
|
const subject = String(formData.get("subject") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
const body = String(formData.get("body") ?? "");
|
const body = String(formData.get("body") ?? "").normalize("NFC");
|
||||||
const variablesRaw = String(formData.get("variables") ?? "").trim();
|
const variablesRaw = String(formData.get("variables") ?? "").normalize("NFC").trim();
|
||||||
const isActive = formData.get("isActive") != null;
|
const isActive = formData.get("isActive") != null;
|
||||||
if (!name || !subject || !body) return;
|
if (!name || !subject || !body) return;
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ export async function createEmailTemplate(formData: FormData): Promise<void> {
|
|||||||
|
|
||||||
export async function updateEmailTemplate(formData: FormData): Promise<void> {
|
export async function updateEmailTemplate(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requireStaff();
|
||||||
const raw = String(formData.get("id") ?? "");
|
const raw = String(formData.get("id") ?? "").normalize("NFC");
|
||||||
if (!raw) return;
|
if (!raw) return;
|
||||||
let id: bigint;
|
let id: bigint;
|
||||||
try {
|
try {
|
||||||
@@ -40,11 +40,11 @@ export async function updateEmailTemplate(formData: FormData): Promise<void> {
|
|||||||
} catch {
|
} catch {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const subject = String(formData.get("subject") ?? "")
|
const subject = String(formData.get("subject") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
const body = String(formData.get("body") ?? "");
|
const body = String(formData.get("body") ?? "").normalize("NFC");
|
||||||
const variablesRaw = String(formData.get("variables") ?? "").trim();
|
const variablesRaw = String(formData.get("variables") ?? "").normalize("NFC").trim();
|
||||||
const isActive = formData.get("isActive") != null;
|
const isActive = formData.get("isActive") != null;
|
||||||
if (!subject || !body) return;
|
if (!subject || !body) return;
|
||||||
|
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import { prisma } from "@/lib/prisma";
|
|||||||
|
|
||||||
export async function updateEmulatorSetting(formData: FormData): Promise<void> {
|
export async function updateEmulatorSetting(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requireStaff();
|
||||||
const key = String(formData.get("key") ?? "")
|
const key = String(formData.get("key") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 100);
|
.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;
|
if (!key) return;
|
||||||
await prisma.emulatorSettings.upsert({
|
await prisma.emulatorSettings.upsert({
|
||||||
where: { key },
|
where: { key },
|
||||||
@@ -26,10 +26,10 @@ export async function updateEmulatorSetting(formData: FormData): Promise<void> {
|
|||||||
|
|
||||||
export async function updateEmulatorText(formData: FormData): Promise<void> {
|
export async function updateEmulatorText(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requireStaff();
|
||||||
const key = String(formData.get("key") ?? "")
|
const key = String(formData.get("key") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 100);
|
.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;
|
if (!key) return;
|
||||||
await prisma.emulatorTexts.upsert({
|
await prisma.emulatorTexts.upsert({
|
||||||
where: { key },
|
where: { key },
|
||||||
|
|||||||
+14
-14
@@ -17,27 +17,27 @@ function parsePosition(value: FormDataEntryValue | null): number {
|
|||||||
|
|
||||||
export async function createHelpQuestion(formData: FormData): Promise<void> {
|
export async function createHelpQuestion(formData: FormData): Promise<void> {
|
||||||
const staff = await requireStaff();
|
const staff = await requireStaff();
|
||||||
const name = String(formData.get("name") ?? "")
|
const name = String(formData.get("name") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
const content = String(formData.get("content") ?? "").trim();
|
const content = String(formData.get("content") ?? "").normalize("NFC").trim();
|
||||||
if (!name || !content) return;
|
if (!name || !content) return;
|
||||||
|
|
||||||
const imageUrl = String(formData.get("imageUrl") ?? "")
|
const imageUrl = String(formData.get("imageUrl") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
const buttonText = String(formData.get("buttonText") ?? "")
|
const buttonText = String(formData.get("buttonText") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
const buttonUrl = String(formData.get("buttonUrl") ?? "")
|
const buttonUrl = String(formData.get("buttonUrl") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
const buttonColor =
|
const buttonColor =
|
||||||
String(formData.get("buttonColor") ?? "")
|
String(formData.get("buttonColor") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 16) || "#eeb425";
|
.slice(0, 16) || "#eeb425";
|
||||||
const buttonBorderColor =
|
const buttonBorderColor =
|
||||||
String(formData.get("buttonBorderColor") ?? "")
|
String(formData.get("buttonBorderColor") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 16) || "#facc15";
|
.slice(0, 16) || "#facc15";
|
||||||
|
|
||||||
@@ -76,27 +76,27 @@ export async function updateHelpQuestion(formData: FormData): Promise<void> {
|
|||||||
const id = formPositiveBigInt(formData, "id");
|
const id = formPositiveBigInt(formData, "id");
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|
||||||
const name = String(formData.get("name") ?? "")
|
const name = String(formData.get("name") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
const content = String(formData.get("content") ?? "").trim();
|
const content = String(formData.get("content") ?? "").normalize("NFC").trim();
|
||||||
if (!name || !content) return;
|
if (!name || !content) return;
|
||||||
|
|
||||||
const imageUrl = String(formData.get("imageUrl") ?? "")
|
const imageUrl = String(formData.get("imageUrl") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
const buttonText = String(formData.get("buttonText") ?? "")
|
const buttonText = String(formData.get("buttonText") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
const buttonUrl = String(formData.get("buttonUrl") ?? "")
|
const buttonUrl = String(formData.get("buttonUrl") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
const buttonColor =
|
const buttonColor =
|
||||||
String(formData.get("buttonColor") ?? "")
|
String(formData.get("buttonColor") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 16) || "#eeb425";
|
.slice(0, 16) || "#eeb425";
|
||||||
const buttonBorderColor =
|
const buttonBorderColor =
|
||||||
String(formData.get("buttonBorderColor") ?? "")
|
String(formData.get("buttonBorderColor") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 16) || "#facc15";
|
.slice(0, 16) || "#facc15";
|
||||||
|
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ import { prisma } from "@/lib/prisma";
|
|||||||
export async function upsertPermission(formData: FormData): Promise<void> {
|
export async function upsertPermission(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requireStaff();
|
||||||
|
|
||||||
const permission = String(formData.get("permission") ?? "")
|
const permission = String(formData.get("permission") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
const minRank = Number(formData.get("minRank"));
|
const minRank = Number(formData.get("minRank"));
|
||||||
const descriptionRaw = String(formData.get("description") ?? "")
|
const descriptionRaw = String(formData.get("description") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
const description = descriptionRaw.length > 0 ? descriptionRaw : null;
|
const description = descriptionRaw.length > 0 ? descriptionRaw : null;
|
||||||
@@ -38,7 +38,7 @@ export async function upsertPermission(formData: FormData): Promise<void> {
|
|||||||
export async function deletePermission(formData: FormData): Promise<void> {
|
export async function deletePermission(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requireStaff();
|
||||||
|
|
||||||
const raw = String(formData.get("id") ?? "");
|
const raw = String(formData.get("id") ?? "").normalize("NFC");
|
||||||
if (!raw) return;
|
if (!raw) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ import { requireStaff } from "@/lib/admin/guard";
|
|||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
|
||||||
function parseIp(formData: FormData): string {
|
function parseIp(formData: FormData): string {
|
||||||
return String(formData.get("ipAddress") ?? "")
|
return String(formData.get("ipAddress") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseAsn(formData: FormData): string | null {
|
function parseAsn(formData: FormData): string | null {
|
||||||
const asn = String(formData.get("asn") ?? "")
|
const asn = String(formData.get("asn") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
return asn || null;
|
return asn || null;
|
||||||
@@ -30,7 +30,7 @@ export async function addWhitelist(formData: FormData): Promise<void> {
|
|||||||
|
|
||||||
export async function deleteWhitelist(formData: FormData): Promise<void> {
|
export async function deleteWhitelist(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requireStaff();
|
||||||
const raw = String(formData.get("id") ?? "").trim();
|
const raw = String(formData.get("id") ?? "").normalize("NFC").trim();
|
||||||
if (!raw) return;
|
if (!raw) return;
|
||||||
await prisma.websiteIpWhitelist.delete({ where: { id: BigInt(raw) } });
|
await prisma.websiteIpWhitelist.delete({ where: { id: BigInt(raw) } });
|
||||||
revalidatePath("/admin/ip");
|
revalidatePath("/admin/ip");
|
||||||
@@ -49,7 +49,7 @@ export async function addBlacklist(formData: FormData): Promise<void> {
|
|||||||
|
|
||||||
export async function deleteBlacklist(formData: FormData): Promise<void> {
|
export async function deleteBlacklist(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requireStaff();
|
||||||
const raw = String(formData.get("id") ?? "").trim();
|
const raw = String(formData.get("id") ?? "").normalize("NFC").trim();
|
||||||
if (!raw) return;
|
if (!raw) return;
|
||||||
await prisma.websiteIpBlacklist.delete({ where: { id: BigInt(raw) } });
|
await prisma.websiteIpBlacklist.delete({ where: { id: BigInt(raw) } });
|
||||||
revalidatePath("/admin/ip");
|
revalidatePath("/admin/ip");
|
||||||
|
|||||||
@@ -39,11 +39,11 @@ export async function saveMaintenance(formData: FormData): Promise<void> {
|
|||||||
// emulator/Laravel side expects.
|
// emulator/Laravel side expects.
|
||||||
const enabled = formData.get("enabled") != null ? "1" : "0";
|
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
|
// Coerce the rank to a non-negative integer; fall back to AtomCMS's default
|
||||||
// of 5 when the field is blank or garbage.
|
// 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 parsedRank = Number.parseInt(rawRank, 10);
|
||||||
const minRank = Number.isFinite(parsedRank) && parsedRank >= 0 ? parsedRank : 5;
|
const minRank = Number.isFinite(parsedRank) && parsedRank >= 0 ? parsedRank : 5;
|
||||||
|
|
||||||
|
|||||||
@@ -14,17 +14,17 @@ import { logServerError } from "@/lib/server-log";
|
|||||||
// created_at/updated_at are managed here.
|
// created_at/updated_at are managed here.
|
||||||
|
|
||||||
function parseMinRank(formData: FormData): number {
|
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;
|
return Number.isInteger(n) && n >= 0 ? n : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createPermission(formData: FormData): Promise<void> {
|
export async function createPermission(formData: FormData): Promise<void> {
|
||||||
const staff = await requireStaff();
|
const staff = await requireStaff();
|
||||||
const permission = String(formData.get("permission") ?? "")
|
const permission = String(formData.get("permission") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
const minRank = parseMinRank(formData);
|
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;
|
if (!permission) return;
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
@@ -51,14 +51,14 @@ export async function createPermission(formData: FormData): Promise<void> {
|
|||||||
|
|
||||||
export async function updatePermission(formData: FormData): Promise<void> {
|
export async function updatePermission(formData: FormData): Promise<void> {
|
||||||
const staff = await requireStaff();
|
const staff = await requireStaff();
|
||||||
const raw = String(formData.get("id") ?? "");
|
const raw = String(formData.get("id") ?? "").normalize("NFC");
|
||||||
if (!raw) return;
|
if (!raw) return;
|
||||||
const id = BigInt(raw);
|
const id = BigInt(raw);
|
||||||
const permission = String(formData.get("permission") ?? "")
|
const permission = String(formData.get("permission") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
const minRank = parseMinRank(formData);
|
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;
|
if (!permission) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ import { formPositiveBigInt } from "@/lib/form-data";
|
|||||||
|
|
||||||
export async function createCategory(formData: FormData): Promise<void> {
|
export async function createCategory(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requireStaff();
|
||||||
const name = String(formData.get("name") ?? "")
|
const name = String(formData.get("name") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
const badge = String(formData.get("badge") ?? "")
|
const badge = String(formData.get("badge") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
const priorityRaw = Number(formData.get("priority"));
|
const priorityRaw = Number(formData.get("priority"));
|
||||||
@@ -47,10 +47,10 @@ export async function createValue(formData: FormData): Promise<void> {
|
|||||||
const categoryId = formPositiveBigInt(formData, "categoryId");
|
const categoryId = formPositiveBigInt(formData, "categoryId");
|
||||||
if (!categoryId) return;
|
if (!categoryId) return;
|
||||||
|
|
||||||
const name = String(formData.get("name") ?? "")
|
const name = String(formData.get("name") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
const furnitureIcon = String(formData.get("furnitureIcon") ?? "")
|
const furnitureIcon = String(formData.get("furnitureIcon") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
if (!name || !furnitureIcon) return;
|
if (!name || !furnitureIcon) return;
|
||||||
@@ -58,10 +58,10 @@ export async function createValue(formData: FormData): Promise<void> {
|
|||||||
const itemIdRaw = Number(formData.get("itemId"));
|
const itemIdRaw = Number(formData.get("itemId"));
|
||||||
const itemId = Number.isFinite(itemIdRaw) && itemIdRaw > 0 ? Math.floor(itemIdRaw) : null;
|
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()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
const currencyValueRaw = String(formData.get("currencyValue") ?? "")
|
const currencyValueRaw = String(formData.get("currencyValue") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
const currencyType =
|
const currencyType =
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import { siteSettings } from "@/lib/services/site-settings";
|
|||||||
|
|
||||||
export async function updateSetting(formData: FormData): Promise<void> {
|
export async function updateSetting(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requireStaff();
|
||||||
const key = String(formData.get("key") ?? "").trim();
|
const key = String(formData.get("key") ?? "").normalize("NFC").trim();
|
||||||
const value = String(formData.get("value") ?? "");
|
const value = String(formData.get("value") ?? "").normalize("NFC");
|
||||||
if (!key) return;
|
if (!key) return;
|
||||||
await prisma.websiteSetting.update({ where: { key }, data: { value } });
|
await prisma.websiteSetting.update({ where: { key }, data: { value } });
|
||||||
siteSettings.reload();
|
siteSettings.reload();
|
||||||
@@ -17,11 +17,11 @@ export async function updateSetting(formData: FormData): Promise<void> {
|
|||||||
|
|
||||||
export async function createSetting(formData: FormData): Promise<void> {
|
export async function createSetting(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requireStaff();
|
||||||
const key = String(formData.get("key") ?? "")
|
const key = String(formData.get("key") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
const value = String(formData.get("value") ?? "");
|
const value = String(formData.get("value") ?? "").normalize("NFC");
|
||||||
const comment = String(formData.get("comment") ?? "")
|
const comment = String(formData.get("comment") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
if (!key) return;
|
if (!key) return;
|
||||||
@@ -36,7 +36,7 @@ export async function createSetting(formData: FormData): Promise<void> {
|
|||||||
|
|
||||||
export async function deleteSetting(formData: FormData): Promise<void> {
|
export async function deleteSetting(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requireStaff();
|
||||||
const key = String(formData.get("key") ?? "").trim();
|
const key = String(formData.get("key") ?? "").normalize("NFC").trim();
|
||||||
if (!key) return;
|
if (!key) return;
|
||||||
await prisma.websiteSetting.delete({ where: { key } });
|
await prisma.websiteSetting.delete({ where: { key } });
|
||||||
siteSettings.reload();
|
siteSettings.reload();
|
||||||
|
|||||||
+11
-11
@@ -14,7 +14,7 @@ import { logServerError } from "@/lib/server-log";
|
|||||||
|
|
||||||
/** Parse an UnsignedInt form value, returning null when blank/invalid/negative. */
|
/** Parse an UnsignedInt form value, returning null when blank/invalid/negative. */
|
||||||
function optUInt(formData: FormData, key: string): number | null {
|
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;
|
if (raw === "") return null;
|
||||||
const n = Number(raw);
|
const n = Number(raw);
|
||||||
if (!Number.isFinite(n) || n < 0) return null;
|
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<void> {
|
export async function createShopArticle(formData: FormData): Promise<void> {
|
||||||
const staff = await requireStaff();
|
const staff = await requireStaff();
|
||||||
|
|
||||||
const name = String(formData.get("name") ?? "")
|
const name = String(formData.get("name") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
if (!name) return;
|
if (!name) return;
|
||||||
@@ -40,13 +40,13 @@ export async function createShopArticle(formData: FormData): Promise<void> {
|
|||||||
const created = await prisma.websiteShopArticles.create({
|
const created = await prisma.websiteShopArticles.create({
|
||||||
data: {
|
data: {
|
||||||
name,
|
name,
|
||||||
info: String(formData.get("info") ?? "")
|
info: String(formData.get("info") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255),
|
.slice(0, 255),
|
||||||
iconUrl: String(formData.get("icon") ?? "")
|
iconUrl: String(formData.get("icon") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255),
|
.slice(0, 255),
|
||||||
color: String(formData.get("color") ?? "")
|
color: String(formData.get("color") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255),
|
.slice(0, 255),
|
||||||
costs: reqUInt(formData, "costs"),
|
costs: reqUInt(formData, "costs"),
|
||||||
@@ -55,7 +55,7 @@ export async function createShopArticle(formData: FormData): Promise<void> {
|
|||||||
duckets: optUInt(formData, "duckets"),
|
duckets: optUInt(formData, "duckets"),
|
||||||
diamonds: optUInt(formData, "diamonds"),
|
diamonds: optUInt(formData, "diamonds"),
|
||||||
badges:
|
badges:
|
||||||
String(formData.get("badges") ?? "")
|
String(formData.get("badges") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255) || null,
|
.slice(0, 255) || null,
|
||||||
position: reqUInt(formData, "position"),
|
position: reqUInt(formData, "position"),
|
||||||
@@ -85,7 +85,7 @@ export async function updateShopArticle(formData: FormData): Promise<void> {
|
|||||||
const id = formPositiveBigInt(formData, "id");
|
const id = formPositiveBigInt(formData, "id");
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|
||||||
const name = String(formData.get("name") ?? "")
|
const name = String(formData.get("name") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
if (!name) return;
|
if (!name) return;
|
||||||
@@ -95,13 +95,13 @@ export async function updateShopArticle(formData: FormData): Promise<void> {
|
|||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
name,
|
name,
|
||||||
info: String(formData.get("info") ?? "")
|
info: String(formData.get("info") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255),
|
.slice(0, 255),
|
||||||
iconUrl: String(formData.get("icon") ?? "")
|
iconUrl: String(formData.get("icon") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255),
|
.slice(0, 255),
|
||||||
color: String(formData.get("color") ?? "")
|
color: String(formData.get("color") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255),
|
.slice(0, 255),
|
||||||
costs: reqUInt(formData, "costs"),
|
costs: reqUInt(formData, "costs"),
|
||||||
@@ -110,7 +110,7 @@ export async function updateShopArticle(formData: FormData): Promise<void> {
|
|||||||
duckets: optUInt(formData, "duckets"),
|
duckets: optUInt(formData, "duckets"),
|
||||||
diamonds: optUInt(formData, "diamonds"),
|
diamonds: optUInt(formData, "diamonds"),
|
||||||
badges:
|
badges:
|
||||||
String(formData.get("badges") ?? "")
|
String(formData.get("badges") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255) || null,
|
.slice(0, 255) || null,
|
||||||
position: reqUInt(formData, "position"),
|
position: reqUInt(formData, "position"),
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ import { prisma } from "@/lib/prisma";
|
|||||||
export async function createTeam(formData: FormData): Promise<void> {
|
export async function createTeam(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requireStaff();
|
||||||
|
|
||||||
const rankName = String(formData.get("rankName") ?? "").trim();
|
const rankName = String(formData.get("rankName") ?? "").normalize("NFC").trim();
|
||||||
if (!rankName) return;
|
if (!rankName) return;
|
||||||
|
|
||||||
const badge = String(formData.get("badge") ?? "").trim();
|
const badge = String(formData.get("badge") ?? "").normalize("NFC").trim();
|
||||||
const jobDescription = String(formData.get("jobDescription") ?? "").trim();
|
const jobDescription = String(formData.get("jobDescription") ?? "").normalize("NFC").trim();
|
||||||
const staffColor = String(formData.get("staffColor") ?? "").trim() || "#327fa8";
|
const staffColor = String(formData.get("staffColor") ?? "").normalize("NFC").trim() || "#327fa8";
|
||||||
const hiddenRank = formData.get("hiddenRank") === "on";
|
const hiddenRank = formData.get("hiddenRank") === "on";
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|||||||
@@ -30,24 +30,24 @@ export async function saveTheme(formData: FormData): Promise<void> {
|
|||||||
for (const mode of ["light", "dark"] as const) {
|
for (const mode of ["light", "dark"] as const) {
|
||||||
for (const key of THEME_COLOR_KEYS) {
|
for (const key of THEME_COLOR_KEYS) {
|
||||||
const dbKey = settingKey(key, mode);
|
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);
|
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);
|
if (/^\d{1,3}$/.test(radius)) await writeSetting("border_radius", radius);
|
||||||
|
|
||||||
// Typography
|
// 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);
|
if (font in FONTS) await writeSetting("font_family", font);
|
||||||
for (const key of HEADING_KEYS) {
|
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);
|
if (/^\d{1,3}$/.test(v)) await writeSetting(key, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Raw custom CSS (staff-trusted; length-capped, ThemeVars injects it as-is).
|
// Raw custom CSS (staff-trusted; length-capped, ThemeVars injects it as-is).
|
||||||
if (formData.has("custom_css")) {
|
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);
|
await writeSetting("custom_css", cssRaw);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ export async function saveTheme(formData: FormData): Promise<void> {
|
|||||||
|
|
||||||
export async function applyPreset(formData: FormData): Promise<void> {
|
export async function applyPreset(formData: FormData): Promise<void> {
|
||||||
const staff = await requireStaff();
|
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
|
// eslint-disable-next-line security/detect-object-injection -- guarded by null check below
|
||||||
const preset = PRESETS[name];
|
const preset = PRESETS[name];
|
||||||
if (!preset) redirect("/admin/theme");
|
if (!preset) redirect("/admin/theme");
|
||||||
|
|||||||
@@ -41,9 +41,9 @@ export async function updateUser(formData: FormData): Promise<void> {
|
|||||||
if (!existing) return;
|
if (!existing) return;
|
||||||
|
|
||||||
// users row — only existing, safe columns.
|
// users row — only existing, safe columns.
|
||||||
const mailRaw = String(formData.get("mail") ?? "").trim();
|
const mailRaw = String(formData.get("mail") ?? "").normalize("NFC").trim();
|
||||||
const motto = String(formData.get("motto") ?? "").slice(0, 127);
|
const motto = String(formData.get("motto") ?? "").normalize("NFC").slice(0, 127);
|
||||||
const look = String(formData.get("look") ?? "").slice(0, 256);
|
const look = String(formData.get("look") ?? "").normalize("NFC").slice(0, 256);
|
||||||
const rank = toInt(formData.get("rank"), 1);
|
const rank = toInt(formData.get("rank"), 1);
|
||||||
const credits = toInt(formData.get("credits"), 0);
|
const credits = toInt(formData.get("credits"), 0);
|
||||||
const pixels = toInt(formData.get("pixels"), 0);
|
const pixels = toInt(formData.get("pixels"), 0);
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export async function giveCurrency(formData: FormData): Promise<void> {
|
|||||||
export async function setMotto(formData: FormData): Promise<void> {
|
export async function setMotto(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requireStaff();
|
||||||
const userId = Number(formData.get("userId"));
|
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) {
|
if (userId > 0) {
|
||||||
await prisma.user.update({ where: { id: userId }, data: { motto } });
|
await prisma.user.update({ where: { id: userId }, data: { motto } });
|
||||||
await rcon.setMotto(userId, motto);
|
await rcon.setMotto(userId, motto);
|
||||||
@@ -59,13 +59,13 @@ export async function setRank(formData: FormData): Promise<void> {
|
|||||||
export async function alertUser(formData: FormData): Promise<void> {
|
export async function alertUser(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requireStaff();
|
||||||
const userId = Number(formData.get("userId"));
|
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);
|
if (userId > 0 && message) await rcon.alertUser(userId, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function disconnectUser(formData: FormData): Promise<void> {
|
export async function disconnectUser(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requireStaff();
|
||||||
const userId = Number(formData.get("userId"));
|
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);
|
if (userId > 0) await rcon.disconnectUser(userId, username);
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,7 @@ import { logServerError } from "@/lib/server-log";
|
|||||||
export async function createVoucher(formData: FormData): Promise<void> {
|
export async function createVoucher(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requireStaff();
|
||||||
|
|
||||||
const code = String(formData.get("code") ?? "")
|
const code = String(formData.get("code") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
const amount = Number(formData.get("amount"));
|
const amount = Number(formData.get("amount"));
|
||||||
@@ -18,7 +18,7 @@ export async function createVoucher(formData: FormData): Promise<void> {
|
|||||||
|
|
||||||
if (!code || !(amount > 0)) return;
|
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;
|
let expiresAt: Date | null = null;
|
||||||
if (expiresRaw) {
|
if (expiresRaw) {
|
||||||
const parsed = new Date(expiresRaw);
|
const parsed = new Date(expiresRaw);
|
||||||
|
|||||||
@@ -27,17 +27,17 @@ export async function saveVpn(formData: FormData): Promise<void> {
|
|||||||
const staff = await requireStaff();
|
const staff = await requireStaff();
|
||||||
|
|
||||||
// Toggle: an unchecked checkbox submits nothing, so absence === disabled.
|
// 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()
|
.trim()
|
||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
const provider = ALLOWED_PROVIDERS.has(providerRaw) ? providerRaw : "none";
|
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()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
const blockMessage = String(formData.get("vpn_block_message") ?? "")
|
const blockMessage = String(formData.get("vpn_block_message") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { rcon } from "@/lib/services/rcon";
|
|||||||
|
|
||||||
export async function addWord(formData: FormData): Promise<void> {
|
export async function addWord(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requireStaff();
|
||||||
const word = String(formData.get("word") ?? "")
|
const word = String(formData.get("word") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
if (!word) return;
|
if (!word) return;
|
||||||
@@ -23,7 +23,7 @@ export async function addWord(formData: FormData): Promise<void> {
|
|||||||
|
|
||||||
export async function deleteWord(formData: FormData): Promise<void> {
|
export async function deleteWord(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requireStaff();
|
||||||
const raw = String(formData.get("id") ?? "");
|
const raw = String(formData.get("id") ?? "").normalize("NFC");
|
||||||
if (!raw) return;
|
if (!raw) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { logStaffActivity } from "@/lib/services/staff-activity";
|
|||||||
|
|
||||||
/** Parse a non-negative Int form value, falling back to 0. */
|
/** Parse a non-negative Int form value, falling back to 0. */
|
||||||
function reqInt(formData: FormData, key: string): number {
|
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;
|
if (raw === "") return 0;
|
||||||
const n = Number(raw);
|
const n = Number(raw);
|
||||||
if (!Number.isFinite(n) || n < 0) return 0;
|
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. */
|
/** Parse the BigInt `id` form value, returning null when blank/invalid. */
|
||||||
function parseId(formData: FormData): bigint | null {
|
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;
|
if (!raw) return null;
|
||||||
try {
|
try {
|
||||||
return BigInt(raw);
|
return BigInt(raw);
|
||||||
@@ -38,7 +38,7 @@ function revalidate(): void {
|
|||||||
export async function createBox(formData: FormData): Promise<void> {
|
export async function createBox(formData: FormData): Promise<void> {
|
||||||
const staff = await requireStaff();
|
const staff = await requireStaff();
|
||||||
|
|
||||||
const title = String(formData.get("title") ?? "")
|
const title = String(formData.get("title") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
if (!title) return;
|
if (!title) return;
|
||||||
@@ -49,12 +49,12 @@ export async function createBox(formData: FormData): Promise<void> {
|
|||||||
data: {
|
data: {
|
||||||
title,
|
title,
|
||||||
icon:
|
icon:
|
||||||
String(formData.get("icon") ?? "")
|
String(formData.get("icon") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255) || null,
|
.slice(0, 255) || null,
|
||||||
content: String(formData.get("content") ?? ""),
|
content: String(formData.get("content") ?? "").normalize("NFC"),
|
||||||
position: reqInt(formData, "position"),
|
position: reqInt(formData, "position"),
|
||||||
isActive: String(formData.get("isActive") ?? "") === "1",
|
isActive: String(formData.get("isActive") ?? "").normalize("NFC") === "1",
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
updatedAt: now,
|
updatedAt: now,
|
||||||
},
|
},
|
||||||
@@ -80,7 +80,7 @@ export async function updateBox(formData: FormData): Promise<void> {
|
|||||||
const id = parseId(formData);
|
const id = parseId(formData);
|
||||||
if (id == null) return;
|
if (id == null) return;
|
||||||
|
|
||||||
const title = String(formData.get("title") ?? "")
|
const title = String(formData.get("title") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255);
|
.slice(0, 255);
|
||||||
if (!title) return;
|
if (!title) return;
|
||||||
@@ -91,12 +91,12 @@ export async function updateBox(formData: FormData): Promise<void> {
|
|||||||
data: {
|
data: {
|
||||||
title,
|
title,
|
||||||
icon:
|
icon:
|
||||||
String(formData.get("icon") ?? "")
|
String(formData.get("icon") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255) || null,
|
.slice(0, 255) || null,
|
||||||
content: String(formData.get("content") ?? ""),
|
content: String(formData.get("content") ?? "").normalize("NFC"),
|
||||||
position: reqInt(formData, "position"),
|
position: reqInt(formData, "position"),
|
||||||
isActive: String(formData.get("isActive") ?? "") === "1",
|
isActive: String(formData.get("isActive") ?? "").normalize("NFC") === "1",
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -143,7 +143,7 @@ export async function toggleBox(formData: FormData): Promise<void> {
|
|||||||
if (id == null) return;
|
if (id == null) return;
|
||||||
|
|
||||||
// `next` carries the desired state ("1" to activate, anything else to hide).
|
// `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 {
|
try {
|
||||||
await prisma.websiteWriteableBoxes.update({
|
await prisma.websiteWriteableBoxes.update({
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export async function applyStaff(formData: FormData): Promise<void> {
|
|||||||
const rankId = Number(formData.get("rankId"));
|
const rankId = Number(formData.get("rankId"));
|
||||||
if (!Number.isInteger(rankId) || rankId <= 0) return;
|
if (!Number.isInteger(rankId) || rankId <= 0) return;
|
||||||
|
|
||||||
const content = String(formData.get("content") ?? "")
|
const content = String(formData.get("content") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, CONTENT_MAX);
|
.slice(0, CONTENT_MAX);
|
||||||
if (content.length < CONTENT_MIN) return;
|
if (content.length < CONTENT_MIN) return;
|
||||||
@@ -72,7 +72,7 @@ export async function applyTeam(formData: FormData): Promise<void> {
|
|||||||
const rankId = Number(formData.get("teamId"));
|
const rankId = Number(formData.get("teamId"));
|
||||||
if (!Number.isInteger(rankId) || rankId <= 0) return;
|
if (!Number.isInteger(rankId) || rankId <= 0) return;
|
||||||
|
|
||||||
const content = String(formData.get("content") ?? "")
|
const content = String(formData.get("content") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, CONTENT_MAX);
|
.slice(0, CONTENT_MAX);
|
||||||
if (content.length < CONTENT_MIN) return;
|
if (content.length < CONTENT_MIN) return;
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export async function postComment(formData: FormData): Promise<void> {
|
|||||||
const userId = Number(session.user.id);
|
const userId = Number(session.user.id);
|
||||||
if (!Number.isFinite(userId)) return;
|
if (!Number.isFinite(userId)) return;
|
||||||
|
|
||||||
const comment = String(formData.get("comment") ?? "")
|
const comment = String(formData.get("comment") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, COMMENT_MAX);
|
.slice(0, COMMENT_MAX);
|
||||||
if (!comment) return;
|
if (!comment) return;
|
||||||
@@ -29,7 +29,7 @@ export async function postComment(formData: FormData): Promise<void> {
|
|||||||
// Block filtered/AI-flagged content before it touches the DB (fail-open).
|
// Block filtered/AI-flagged content before it touches the DB (fail-open).
|
||||||
if (!(await isAllowed(comment)).ok) return;
|
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;
|
if (!/^\d+$/.test(articleIdRaw)) return;
|
||||||
|
|
||||||
let articleId: bigint;
|
let articleId: bigint;
|
||||||
|
|||||||
@@ -31,12 +31,12 @@ export async function toggleReaction(formData: FormData): Promise<void> {
|
|||||||
const userId = Number(session.user.id);
|
const userId = Number(session.user.id);
|
||||||
if (!Number.isFinite(userId)) return;
|
if (!Number.isFinite(userId)) return;
|
||||||
|
|
||||||
const reaction = String(formData.get("reaction") ?? "")
|
const reaction = String(formData.get("reaction") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
if (!ALLOWED_REACTIONS.has(reaction)) return;
|
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;
|
if (!/^\d+$/.test(articleIdRaw)) return;
|
||||||
|
|
||||||
let articleId: bigint;
|
let articleId: bigint;
|
||||||
|
|||||||
@@ -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.
|
* TOTP code is still required. Lets the login form do the two-step 2FA flow.
|
||||||
*/
|
*/
|
||||||
export async function precheckLogin(username: string, password: string): Promise<PrecheckResult> {
|
export async function precheckLogin(username: string, password: string): Promise<PrecheckResult> {
|
||||||
const u = String(username ?? "").trim();
|
const u = String(username ?? "").normalize("NFC").trim();
|
||||||
const p = String(password ?? "");
|
const p = String(password ?? "");
|
||||||
if (!u || !p) return "invalid";
|
if (!u || !p) return "invalid";
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export async function updateNavigator(): Promise<void> {
|
|||||||
/** Broadcast a hotel-wide alert to every connected user (rcon: hotelalert). */
|
/** Broadcast a hotel-wide alert to every connected user (rcon: hotelalert). */
|
||||||
export async function hotelAlert(formData: FormData): Promise<void> {
|
export async function hotelAlert(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requireStaff();
|
||||||
const message = String(formData.get("message") ?? "")
|
const message = String(formData.get("message") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 512);
|
.slice(0, 512);
|
||||||
if (!message) return;
|
if (!message) return;
|
||||||
@@ -58,7 +58,7 @@ export async function hotelAlert(formData: FormData): Promise<void> {
|
|||||||
export async function disconnectUser(formData: FormData): Promise<void> {
|
export async function disconnectUser(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requireStaff();
|
||||||
const userId = Number(formData.get("userId"));
|
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;
|
if (!userId || !username) return;
|
||||||
try {
|
try {
|
||||||
await rcon.disconnectUser(userId, username);
|
await rcon.disconnectUser(userId, username);
|
||||||
@@ -72,7 +72,7 @@ export async function disconnectUser(formData: FormData): Promise<void> {
|
|||||||
export async function alertUser(formData: FormData): Promise<void> {
|
export async function alertUser(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requireStaff();
|
||||||
const userId = Number(formData.get("userId"));
|
const userId = Number(formData.get("userId"));
|
||||||
const message = String(formData.get("message") ?? "")
|
const message = String(formData.get("message") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 512);
|
.slice(0, 512);
|
||||||
if (!userId || !message) return;
|
if (!userId || !message) return;
|
||||||
@@ -144,7 +144,7 @@ export async function giveDiamonds(formData: FormData): Promise<void> {
|
|||||||
export async function giveBadge(formData: FormData): Promise<void> {
|
export async function giveBadge(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requireStaff();
|
||||||
const userId = Number(formData.get("userId"));
|
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;
|
if (!userId || !badge) return;
|
||||||
try {
|
try {
|
||||||
await rcon.giveBadge(userId, badge);
|
await rcon.giveBadge(userId, badge);
|
||||||
@@ -158,7 +158,7 @@ export async function giveBadge(formData: FormData): Promise<void> {
|
|||||||
export async function setMotto(formData: FormData): Promise<void> {
|
export async function setMotto(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requireStaff();
|
||||||
const userId = Number(formData.get("userId"));
|
const userId = Number(formData.get("userId"));
|
||||||
const motto = String(formData.get("motto") ?? "")
|
const motto = String(formData.get("motto") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 127);
|
.slice(0, 127);
|
||||||
if (!userId || !motto) return;
|
if (!userId || !motto) return;
|
||||||
@@ -188,7 +188,7 @@ export async function setRank(formData: FormData): Promise<void> {
|
|||||||
export async function executeCommand(formData: FormData): Promise<void> {
|
export async function executeCommand(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requireStaff();
|
||||||
const userId = Number(formData.get("userId"));
|
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;
|
if (!userId || !command) return;
|
||||||
try {
|
try {
|
||||||
await rcon.executeCommand(userId, command);
|
await rcon.executeCommand(userId, command);
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export async function buyBadge(formData: FormData): Promise<void> {
|
|||||||
|
|
||||||
// The form posts the badge row id; everything else (price, code) is resolved
|
// The form posts the badge row id; everything else (price, code) is resolved
|
||||||
// server-side from trusted data — never from the client.
|
// 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");
|
if (!/^\d+$/.test(rawId)) redirect("/draw-badge?error=invalid");
|
||||||
|
|
||||||
let outcome: "bought" | "invalid" | "credits" | "fail";
|
let outcome: "bought" | "invalid" | "credits" | "fail";
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export async function postGuestbook(formData: FormData): Promise<void> {
|
|||||||
const profileId = Number(formData.get("profileId"));
|
const profileId = Number(formData.get("profileId"));
|
||||||
if (!Number.isInteger(profileId) || profileId <= 0) return;
|
if (!Number.isInteger(profileId) || profileId <= 0) return;
|
||||||
|
|
||||||
const message = String(formData.get("message") ?? "")
|
const message = String(formData.get("message") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, MESSAGE_MAX);
|
.slice(0, MESSAGE_MAX);
|
||||||
if (!message) return;
|
if (!message) return;
|
||||||
@@ -34,7 +34,7 @@ export async function postGuestbook(formData: FormData): Promise<void> {
|
|||||||
if (!(await isAllowed(message)).ok) return;
|
if (!(await isAllowed(message)).ok) return;
|
||||||
|
|
||||||
// Optional: used only to revalidate the correct profile route.
|
// 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();
|
const now = new Date();
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ export async function createTicket(formData: FormData): Promise<void> {
|
|||||||
if (!(await rateLimit(`ticket:${userId}`, 3, 60_000)).ok) return;
|
if (!(await rateLimit(`ticket:${userId}`, 3, 60_000)).ok) return;
|
||||||
|
|
||||||
const raw = {
|
const raw = {
|
||||||
title: String(formData.get("title") ?? "")
|
title: String(formData.get("title") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255),
|
.slice(0, 255),
|
||||||
content: String(formData.get("content") ?? "")
|
content: String(formData.get("content") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 5000),
|
.slice(0, 5000),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ function sha256(s: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function requestReset(formData: FormData): Promise<void> {
|
export async function requestReset(formData: FormData): Promise<void> {
|
||||||
const email = String(formData.get("email") ?? "")
|
const email = String(formData.get("email") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
|
|
||||||
@@ -49,11 +49,11 @@ export async function requestReset(formData: FormData): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function resetPassword(formData: FormData): Promise<void> {
|
export async function resetPassword(formData: FormData): Promise<void> {
|
||||||
const email = String(formData.get("email") ?? "")
|
const email = String(formData.get("email") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
const token = String(formData.get("token") ?? "").trim();
|
const token = String(formData.get("token") ?? "").normalize("NFC").trim();
|
||||||
const password = String(formData.get("password") ?? "");
|
const password = String(formData.get("password") ?? "").normalize("NFC");
|
||||||
|
|
||||||
// Throttle reset attempts per IP (5 per 15 min) to prevent token brute-force.
|
// 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) {
|
if (!(await rateLimit(`resetpwd:${await clientIp()}`, 5, 15 * 60_000)).ok) {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const TEXT_MAX = 5000;
|
|||||||
const STYLE_MAX = 5000;
|
const STYLE_MAX = 5000;
|
||||||
|
|
||||||
function str(form: FormData, key: string, max: number): string {
|
function str(form: FormData, key: string, max: number): string {
|
||||||
return String(form.get(key) ?? "")
|
return String(form.get(key) ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, max);
|
.slice(0, max);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ export async function submitRequest(formData: FormData): Promise<void> {
|
|||||||
const userId = Number(session?.user?.id);
|
const userId = Number(session?.user?.id);
|
||||||
if (!Number.isInteger(userId) || userId <= 0) return;
|
if (!Number.isInteger(userId) || userId <= 0) return;
|
||||||
|
|
||||||
const songTitle = String(formData.get("songTitle") ?? "")
|
const songTitle = String(formData.get("songTitle") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, SONG_MAX);
|
.slice(0, SONG_MAX);
|
||||||
const artist = String(formData.get("artist") ?? "")
|
const artist = String(formData.get("artist") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, ARTIST_MAX);
|
.slice(0, ARTIST_MAX);
|
||||||
if (!songTitle && !artist) return;
|
if (!songTitle && !artist) return;
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export async function postShout(formData: FormData): Promise<void> {
|
|||||||
if (!(await rateLimit(`shout:${userId}`, 5, 30_000)).ok) return;
|
if (!(await rateLimit(`shout:${userId}`, 5, 30_000)).ok) return;
|
||||||
|
|
||||||
const raw = {
|
const raw = {
|
||||||
message: String(formData.get("message") ?? "")
|
message: String(formData.get("message") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 255),
|
.slice(0, 255),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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<string | null> {
|
export async function register(prevState: string | null, formData: FormData): Promise<string | null> {
|
||||||
const raw = {
|
const raw = {
|
||||||
username: String(formData.get("username") ?? "").trim(),
|
username: String(formData.get("username") ?? "").normalize("NFC").trim(),
|
||||||
mail: String(formData.get("mail") ?? "")
|
mail: String(formData.get("mail") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.toLowerCase(),
|
.toLowerCase(),
|
||||||
password: String(formData.get("password") ?? ""),
|
password: String(formData.get("password") ?? "").normalize("NFC"),
|
||||||
look: String(formData.get("look") ?? "").trim() || DEFAULT_LOOK,
|
look: String(formData.get("look") ?? "").normalize("NFC").trim() || DEFAULT_LOOK,
|
||||||
};
|
};
|
||||||
|
|
||||||
const parsed = registerSchema.safeParse(raw);
|
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.
|
// CAPTCHA (Turnstile / reCAPTCHA) — only enforced when configured in settings.
|
||||||
const cfg = await captchaConfig();
|
const cfg = await captchaConfig();
|
||||||
if (cfg.provider !== "none") {
|
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.";
|
if (!(await verifyCaptcha(token, ip))) return "Captcha verification failed. Please try again.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export async function sendFriendRequest(formData: FormData): Promise<void> {
|
|||||||
|
|
||||||
// Optional: revalidate the target profile if a username was supplied, purely
|
// Optional: revalidate the target profile if a username was supplied, purely
|
||||||
// to refresh any request-state UI rendered there.
|
// 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}`);
|
if (username) revalidatePath(`/u/${username}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,10 +85,10 @@ export async function postThread(formData: FormData): Promise<void> {
|
|||||||
const guildId = Number(formData.get("guildId"));
|
const guildId = Number(formData.get("guildId"));
|
||||||
if (!Number.isInteger(guildId) || guildId <= 0) return;
|
if (!Number.isInteger(guildId) || guildId <= 0) return;
|
||||||
|
|
||||||
const subject = String(formData.get("subject") ?? "")
|
const subject = String(formData.get("subject") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, SUBJECT_MAX);
|
.slice(0, SUBJECT_MAX);
|
||||||
const message = String(formData.get("message") ?? "")
|
const message = String(formData.get("message") ?? "").normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, MESSAGE_MAX);
|
.slice(0, MESSAGE_MAX);
|
||||||
if (!subject || !message) return;
|
if (!subject || !message) return;
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export async function confirmTwoFactor(formData: FormData): Promise<void> {
|
|||||||
|
|
||||||
if (!(await rateLimit(`2fa-confirm:${id}`, 5, 30_000)).ok) redirect("/settings/2fa?error=ratelimit");
|
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);
|
const { ok } = await verifyTwoFactorCode(id, code);
|
||||||
if (!ok) redirect("/settings/2fa?error=badcode");
|
if (!ok) redirect("/settings/2fa?error=badcode");
|
||||||
@@ -107,7 +107,7 @@ export async function disableTwoFactor(formData: FormData): Promise<void> {
|
|||||||
|
|
||||||
if (!(await rateLimit(`2fa-disable:${id}`, 5, 30_000)).ok) redirect("/settings/2fa?error=ratelimit");
|
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);
|
const { ok } = await verifyTwoFactorCode(id, code);
|
||||||
if (!ok) redirect("/settings/2fa?error=badcode");
|
if (!ok) redirect("/settings/2fa?error=badcode");
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ const updateMottoAction = authAction(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export async function updateMotto(formData: FormData): Promise<void> {
|
export async function updateMotto(formData: FormData): Promise<void> {
|
||||||
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 });
|
await updateMottoAction({ motto });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export async function redeem(_prev: RedeemState, formData: FormData): Promise<Re
|
|||||||
return { ok: false, message: "Your session is invalid. Please sign in again." };
|
return { ok: false, message: "Your session is invalid. Please sign in again." };
|
||||||
}
|
}
|
||||||
|
|
||||||
const code = String(formData.get("code") ?? "").trim();
|
const code = String(formData.get("code") ?? "").normalize("NFC").trim();
|
||||||
if (!code) {
|
if (!code) {
|
||||||
return { ok: false, message: "Please enter a voucher code." };
|
return { ok: false, message: "Please enter a voucher code." };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user