Protect launcher admin tools

This commit is contained in:
Andraxion 2026-06-27 00:51:20 -04:00
parent fad065c429
commit ab1dfbf029
4 changed files with 384 additions and 151 deletions

View file

@ -10,6 +10,38 @@ const __dirname = path.dirname(__filename);
const app = express();
const port = Number(process.env.PORT) || 5180;
const host = process.env.HOST || "0.0.0.0";
const launcherAdminPassword = String(process.env.LAUNCHER_ADMIN_PASSWORD || "").trim();
function isLauncherAdminProtectionEnabled() {
return Boolean(launcherAdminPassword);
}
function readLauncherAdminPasswordCandidate(req) {
const headerValue = req.get("x-worldshaper-admin-password");
if (String(headerValue || "").trim()) {
return String(headerValue || "").trim();
}
return String(req.body?.password || "").trim();
}
function requireLauncherAdminAccess(req, res) {
if (!isLauncherAdminProtectionEnabled()) {
res.status(503).json({
error: "Launcher admin access is not configured on the server.",
adminConfigured: false,
});
return false;
}
const submittedPassword = readLauncherAdminPasswordCandidate(req);
if (!submittedPassword || submittedPassword !== launcherAdminPassword) {
res.status(401).json({
error: "Admin access denied.",
adminConfigured: true,
});
return false;
}
return true;
}
function resolveContentRoot() {
const envPath = String(process.env.CONTENT_ROOT || "").trim();
@ -2610,6 +2642,9 @@ app.get("/api/types", (_req, res) => {
});
app.get("/api/debug/paths", (_req, res) => {
if (!requireLauncherAdminAccess(_req, res)) {
return;
}
const contentFiles = Object.fromEntries(
Object.entries(contentMap).map(([type, entry]) => {
const fullPath = path.join(contentRoot, entry.file);
@ -2637,7 +2672,37 @@ app.get("/api/debug/paths", (_req, res) => {
});
});
app.get("/api/debug/recent-saves", (_req, res) => {
app.post("/api/admin/auth-check", (req, res) => {
if (!isLauncherAdminProtectionEnabled()) {
res.status(503).json({
ok: false,
accessGranted: false,
adminConfigured: false,
error: "Launcher admin access is not configured on the server.",
});
return;
}
const accessGranted = readLauncherAdminPasswordCandidate(req) === launcherAdminPassword;
if (!accessGranted) {
res.status(401).json({
ok: false,
accessGranted: false,
adminConfigured: true,
error: "Admin access denied.",
});
return;
}
res.json({
ok: true,
accessGranted: true,
adminConfigured: true,
});
});
app.get("/api/debug/recent-saves", (req, res) => {
if (!requireLauncherAdminAccess(req, res)) {
return;
}
res.json({
ok: true,
contentRoot,
@ -2701,6 +2766,9 @@ app.post("/api/launcher-requests", (req, res) => {
app.patch("/api/launcher-requests/:requestId", (req, res) => {
const requestId = String(req.params.requestId || "").trim();
if (!requireLauncherAdminAccess(req, res)) {
return;
}
try {
const payload = readLauncherRequestsPayload();
const index = payload.requests.findIndex((entry) => entry.id === requestId);
@ -2745,6 +2813,9 @@ app.patch("/api/launcher-requests/:requestId", (req, res) => {
app.delete("/api/launcher-requests/:requestId", (req, res) => {
const requestId = String(req.params.requestId || "").trim();
if (!requireLauncherAdminAccess(req, res)) {
return;
}
try {
const payload = readLauncherRequestsPayload();
const existing = payload.requests.find((entry) => entry.id === requestId);
@ -2775,6 +2846,9 @@ app.delete("/api/launcher-requests/:requestId", (req, res) => {
app.post("/api/launcher-requests/:requestId/process-analysis", (req, res) => {
const requestId = String(req.params.requestId || "").trim();
const action = String(req.body?.action || "").trim().toLowerCase();
if (!requireLauncherAdminAccess(req, res)) {
return;
}
try {
const payload = readLauncherRequestsPayload();
const index = payload.requests.findIndex((entry) => entry.id === requestId);
@ -2866,7 +2940,10 @@ app.post("/api/launcher-requests/:requestId/process-analysis", (req, res) => {
}
});
app.post("/api/launcher-requests/process-pending", (_req, res) => {
app.post("/api/launcher-requests/process-pending", (req, res) => {
if (!requireLauncherAdminAccess(req, res)) {
return;
}
try {
const result = launchQueuedRequestAnalysis("manual-api-trigger");
res.json({