Harden admin ACL on critical write paths.
Local Build and Deploy / deploy (push) Successful in 54s

Gate translations, RCON, and user mutations on SETTINGS_EDIT, RCON_EXECUTE, and USERS_EDIT instead of dashboard/rank checks; redirect the legacy user-edit URL to the guarded canonical page.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
SimoandCursor committed 2026-07-15 20:07:15 +02:00
1 parent bae543baf6
commit f2427b3483
8 files changed
+127 -284

No files matched your search

+51 -69
View File
@@ -1,41 +1,14 @@
"use server";
import { NextResponse } from "next/server";
import { requireStaff } from "@/lib/admin/guard";
import { logger } from "@/lib/logger";
import { withAdmin } from "@/lib/api-handler";
import { PERMS } from "@/lib/permissions";
import { prisma } from "@/lib/prisma";
import { rcon } from "@/lib/services/rcon";
import { logStaffActivity } from "@/lib/services/staff-activity";
const giveCurrency = async ({
rconClient: _rconClient,
db: _db,
userId,
type,
amount,
}: {
rconClient: typeof rcon;
db: typeof prisma;
userId: number;
type: string;
amount: number;
}) => {
await logStaffActivity({
staffId: 1,
action: "give_currency",
description: `Gave ${amount} ${type} to user #${userId}`,
targetType: "user",
targetId: userId,
});
return {
success: true,
message: `Gave ${amount} ${type} to user #${userId}`,
};
};
export async function POST(request: Request) {
try {
const staff = await requireStaff();
export const POST = withAdmin(
{ permission: PERMS.USERS_EDIT },
async (request, context) => {
const staffId = context.session.user.id;
const formData = await request.formData();
const userId = Number(formData.get("userId"));
const username = String(formData.get("username") || "");
@@ -60,7 +33,7 @@ export async function POST(request: Request) {
await prisma.user.update({ where: { id: userId }, data: { rank } });
await rcon.setRank(userId, rank);
await logStaffActivity({
staffId: staff.id,
staffId,
action: "rank_change",
description: `Set rank of user #${userId} to ${rank}`,
targetType: "user",
@@ -75,6 +48,13 @@ export async function POST(request: Request) {
if (action === "disconnect") {
await rcon.disconnectUser(userId, username);
await logStaffActivity({
staffId,
action: "disconnect",
description: `Disconnected user #${userId} (${username})`,
targetType: "user",
targetId: userId,
});
return NextResponse.json(
{
success: true,
@@ -95,6 +75,13 @@ export async function POST(request: Request) {
);
}
await rcon.alertUser(userId, message);
await logStaffActivity({
staffId,
action: "alert_user",
description: `Sent alert to user #${userId}`,
targetType: "user",
targetId: userId,
});
return NextResponse.json(
{ success: true, message: `Sent alert to user #${userId}` },
{ status: 200 },
@@ -109,12 +96,13 @@ export async function POST(request: Request) {
{ status: 400 },
);
}
await giveCurrency({
rconClient: rcon,
db: prisma,
userId,
type: "credits",
amount: credits,
await rcon.giveCredits(userId, credits);
await logStaffActivity({
staffId,
action: "give_currency",
description: `Gave ${credits} credits to user #${userId}`,
targetType: "user",
targetId: userId,
});
return NextResponse.json(
{
@@ -133,12 +121,13 @@ export async function POST(request: Request) {
{ status: 400 },
);
}
await giveCurrency({
rconClient: rcon,
db: prisma,
userId,
type: "duckets",
amount,
await rcon.giveDuckets(userId, amount);
await logStaffActivity({
staffId,
action: "give_currency",
description: `Gave ${amount} duckets to user #${userId}`,
targetType: "user",
targetId: userId,
});
return NextResponse.json(
{ success: true, message: `Gave ${amount} duckets to user #${userId}` },
@@ -154,12 +143,13 @@ export async function POST(request: Request) {
{ status: 400 },
);
}
await giveCurrency({
rconClient: rcon,
db: prisma,
userId,
type: "diamonds",
amount,
await rcon.giveDiamonds(userId, amount);
await logStaffActivity({
staffId,
action: "give_currency",
description: `Gave ${amount} diamonds to user #${userId}`,
targetType: "user",
targetId: userId,
});
return NextResponse.json(
{
@@ -178,12 +168,13 @@ export async function POST(request: Request) {
{ status: 400 },
);
}
await giveCurrency({
rconClient: rcon,
db: prisma,
userId,
type: "points",
amount,
await rcon.givePointsGotw(userId, amount);
await logStaffActivity({
staffId,
action: "give_currency",
description: `Gave ${amount} points to user #${userId}`,
targetType: "user",
targetId: userId,
});
return NextResponse.json(
{ success: true, message: `Gave ${amount} points to user #${userId}` },
@@ -195,14 +186,5 @@ export async function POST(request: Request) {
{ success: false, message: `Unknown action: ${action}` },
{ status: 400 },
);
} catch (error) {
logger.error("Admin users actions error", {
module: "admin/users/actions",
error: String(error),
});
return NextResponse.json(
{ success: false, message: "Internal server error" },
{ status: 500 },
);
}
}
},
);