large purgatory improvements

This commit is contained in:
KingKDot
2026-05-22 10:12:40 -05:00
parent ce5095ad61
commit eb75f1a49f
7 changed files with 430 additions and 33 deletions
@@ -111,6 +111,7 @@ export async function handleEnrollmentRoutes(
const config = getConfig();
return Response.json({
requireApproval: config.enrollment?.requireApproval ?? true,
autoApproveUnlessSuspicious: config.enrollment?.autoApproveUnlessSuspicious ?? false,
});
}
@@ -123,11 +124,15 @@ export async function handleEnrollmentRoutes(
let body: any;
try { body = await req.json(); } catch { return Response.json({ error: "Invalid JSON" }, { status: 400 }); }
if (typeof body?.requireApproval !== "boolean") {
return Response.json({ error: "requireApproval must be a boolean" }, { status: 400 });
const updates: { requireApproval?: boolean; autoApproveUnlessSuspicious?: boolean } = {};
if (typeof body?.requireApproval === "boolean") updates.requireApproval = body.requireApproval;
if (typeof body?.autoApproveUnlessSuspicious === "boolean") updates.autoApproveUnlessSuspicious = body.autoApproveUnlessSuspicious;
if (Object.keys(updates).length === 0) {
return Response.json({ error: "No valid boolean fields provided" }, { status: 400 });
}
const updated = await updateEnrollmentConfig({ requireApproval: body.requireApproval });
const updated = await updateEnrollmentConfig(updates);
logAudit({
timestamp: Date.now(),
@@ -135,10 +140,10 @@ export async function handleEnrollmentRoutes(
ip: "server",
action: AuditAction.ENROLLMENT_SETTINGS,
success: true,
details: JSON.stringify({ requireApproval: body.requireApproval }),
details: JSON.stringify(updates),
});
return Response.json({ ok: true, requireApproval: updated.requireApproval });
return Response.json({ ok: true, requireApproval: updated.requireApproval, autoApproveUnlessSuspicious: updated.autoApproveUnlessSuspicious });
}
// POST /api/enrollment/:id/approve
@@ -200,7 +205,11 @@ export async function handleEnrollmentRoutes(
return new Response("Forbidden: Client access denied", { status: 403 });
}
setClientEnrollmentStatus(clientId, "denied");
let denyBody: any = {};
try { denyBody = await req.json(); } catch {}
const denyReason = typeof denyBody?.reason === "string" ? denyBody.reason.slice(0, 200).trim() || undefined : undefined;
setClientEnrollmentStatus(clientId, "denied", undefined, denyReason);
logAudit({
timestamp: Date.now(),
@@ -296,6 +305,7 @@ export async function handleEnrollmentRoutes(
return Response.json({ ok: true, action, updated: banned });
}
const bulkDenyReason = typeof body?.reason === "string" ? body.reason.slice(0, 200).trim() || undefined : undefined;
const status = action === "approve" ? "approved" : action === "deny" ? "denied" : "pending";
let updated = 0;
for (const id of ids) {
@@ -304,6 +314,7 @@ export async function handleEnrollmentRoutes(
id,
status as "approved" | "denied" | "pending",
action === "approve" ? user.username : undefined,
action === "deny" ? bulkDenyReason : undefined,
);
if (ok) {
updated++;
@@ -9,7 +9,8 @@ async function getGeoip() {
}
import { logAudit, AuditAction } from "../../auditLog";
import * as clientManager from "../../clientManager";
import { clientExists, setOnlineState, upsertClientRow, getClientEnrollmentStatus, setClientEnrollmentStatus, lookupClientByPublicKey, getClientPublicKeyById, getBuildByTag } from "../../db";
import { clientExists, setOnlineState, upsertClientRow, getClientEnrollmentStatus, setClientEnrollmentStatus, lookupClientByPublicKey, getClientPublicKeyById, getBuildByTag, computeClientSuspiciousFlags } from "../../db";
import { getConfig } from "../../config";
import { logger } from "../../logger";
import { metrics } from "../../metrics";
import { decodeMessage, encodeMessage, type WireMessage, type Hello, type Ping } from "../../protocol";
@@ -419,6 +420,33 @@ export async function handleWebSocketMessage(
}
if (enrollmentStatus === "pending") {
const enrollConfig = getConfig().enrollment;
if (!enrollConfig.requireApproval) {
let blocked = false;
if (enrollConfig.autoApproveUnlessSuspicious) {
const flags = computeClientSuspiciousFlags({
hwid: typeof (payload as any).hwid === "string" ? (payload as any).hwid : null,
cpu: typeof (payload as any).cpu === "string" ? (payload as any).cpu : null,
gpu: typeof (payload as any).gpu === "string" ? (payload as any).gpu : null,
ram: typeof (payload as any).ram === "string" ? (payload as any).ram : null,
os: typeof (payload as any).os === "string" ? (payload as any).os : null,
host: typeof (payload as any).host === "string" ? (payload as any).host : null,
user: typeof (payload as any).user === "string" ? (payload as any).user : null,
ip: ip || null,
});
if (flags.length > 0) {
blocked = true;
logger.info(`[purgatory] auto-approve blocked for ${resolvedId} — suspicious: ${flags.join(", ")}`);
}
}
if (!blocked) {
enrollmentStatus = "approved";
logger.info(`[purgatory] auto-approved ${resolvedId} (requireApproval=false)`);
}
}
if (enrollmentStatus !== "approved") {
const geoip = await getGeoip();
const geo = ip ? geoip.lookup(ip) : undefined;
const countryRaw = geo?.country || (payload as any).country || "ZZ";
@@ -476,6 +504,7 @@ export async function handleWebSocketMessage(
}
setTimeout(() => { try { ws.close(4001, "pending"); } catch {} }, 100);
return;
}
}
const existingClient = clientManager.getClient(resolvedId);