large purgatory improvements
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user