Files
CubeAdmin/app/api/plugins/route.ts
2026-03-08 17:01:36 +01:00

89 lines
3.2 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { auth, getAuthSession } from "@/lib/auth";
import { db } from "@/lib/db";
import { plugins, auditLogs } from "@/lib/db/schema";
import { checkRateLimit, getClientIp } from "@/lib/security/rateLimit";
import { rconClient } from "@/lib/minecraft/rcon";
import { sanitizeRconCommand } from "@/lib/security/sanitize";
import { eq } from "drizzle-orm";
import { nanoid } from "nanoid";
import * as fs from "node:fs";
import * as path from "node:path";
export async function GET(req: NextRequest) {
const session = await getAuthSession(req.headers);
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
const ip = getClientIp(req);
const { allowed } = checkRateLimit(ip);
if (!allowed) return NextResponse.json({ error: "Too many requests" }, { status: 429 });
// Read plugins from DB + sync with filesystem
const mcPath = process.env.MC_SERVER_PATH ?? "/opt/minecraft/server";
const pluginsDir = path.join(mcPath, "plugins");
const dbPlugins = await db.select().from(plugins);
// Try to read actual plugin jars from filesystem
let jarFiles: string[] = [];
try {
jarFiles = fs
.readdirSync(pluginsDir)
.filter((f) => f.endsWith(".jar"));
} catch { /* plugins dir might not exist */ }
return NextResponse.json({ plugins: dbPlugins, jarFiles });
}
export async function POST(req: NextRequest) {
const session = await getAuthSession(req.headers);
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
if (!["superadmin", "admin"].includes(session.user.role ?? "")) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const ip = getClientIp(req);
const { allowed } = checkRateLimit(ip);
if (!allowed) return NextResponse.json({ error: "Too many requests" }, { status: 429 });
const { searchParams } = new URL(req.url);
const action = searchParams.get("action");
const pluginName = searchParams.get("name");
if (!pluginName || !/^[a-zA-Z0-9_\-]+$/.test(pluginName)) {
return NextResponse.json({ error: "Invalid plugin name" }, { status: 400 });
}
try {
if (action === "enable" || action === "disable") {
const cmd = sanitizeRconCommand(
`plugman ${action} ${pluginName}`,
);
const result = await rconClient.sendCommand(cmd);
await db
.update(plugins)
.set({ isEnabled: action === "enable" })
.where(eq(plugins.name, pluginName));
await db.insert(auditLogs).values({
id: nanoid(), userId: session.user.id,
action: `plugin.${action}`, target: "plugin", targetId: pluginName,
details: null, ipAddress: ip, createdAt: Date.now(),
});
return NextResponse.json({ success: true, result });
}
if (action === "reload") {
const cmd = sanitizeRconCommand(`plugman reload ${pluginName}`);
const result = await rconClient.sendCommand(cmd);
return NextResponse.json({ success: true, result });
}
return NextResponse.json({ error: "Unknown action" }, { status: 400 });
} catch (err) {
const message = err instanceof Error ? err.message : "Unknown error";
return NextResponse.json({ error: message }, { status: 503 });
}
}