enforce purgatory
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
const body = document.getElementById("enrollment-body");
|
const body = document.getElementById("enrollment-body");
|
||||||
const loadingRow = document.getElementById("enrollment-loading");
|
const loadingRow = document.getElementById("enrollment-loading");
|
||||||
const emptyEl = document.getElementById("enrollment-empty");
|
const emptyEl = document.getElementById("enrollment-empty");
|
||||||
const toggleEl = document.getElementById("require-approval-toggle");
|
|
||||||
const selectAllEl = document.getElementById("select-all");
|
const selectAllEl = document.getElementById("select-all");
|
||||||
const bulkApproveBtn = document.getElementById("bulk-approve-btn");
|
const bulkApproveBtn = document.getElementById("bulk-approve-btn");
|
||||||
const bulkDenyBtn = document.getElementById("bulk-deny-btn");
|
const bulkDenyBtn = document.getElementById("bulk-deny-btn");
|
||||||
@@ -64,13 +63,6 @@ async function loadStats() {
|
|||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadSettings() {
|
|
||||||
try {
|
|
||||||
const s = await api("/api/enrollment/settings");
|
|
||||||
toggleEl.checked = s.requireApproval;
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadClients() {
|
async function loadClients() {
|
||||||
loadingRow.classList.remove("hidden");
|
loadingRow.classList.remove("hidden");
|
||||||
emptyEl.classList.add("hidden");
|
emptyEl.classList.add("hidden");
|
||||||
@@ -242,25 +234,6 @@ document.querySelectorAll(".enrollment-tab").forEach((tab) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Toggle require approval ────────────────────────────────────────
|
|
||||||
toggleEl.addEventListener("change", async () => {
|
|
||||||
try {
|
|
||||||
await api("/api/enrollment/settings", {
|
|
||||||
method: "PUT",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({ requireApproval: toggleEl.checked }),
|
|
||||||
});
|
|
||||||
if (window.showToast)
|
|
||||||
window.showToast(
|
|
||||||
toggleEl.checked ? "Approval required" : "Auto-approve enabled",
|
|
||||||
"success",
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
toggleEl.checked = !toggleEl.checked;
|
|
||||||
if (window.showToast) window.showToast(`Failed: ${e.message}`, "error");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// ── Table delegation ───────────────────────────────────────────────
|
// ── Table delegation ───────────────────────────────────────────────
|
||||||
body.addEventListener("click", (e) => {
|
body.addEventListener("click", (e) => {
|
||||||
const btn = e.target.closest("button");
|
const btn = e.target.closest("button");
|
||||||
@@ -434,7 +407,6 @@ confirmBanBtn.addEventListener("click", async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ── Init ───────────────────────────────────────────────────────────
|
// ── Init ───────────────────────────────────────────────────────────
|
||||||
loadSettings();
|
|
||||||
loadStats();
|
loadStats();
|
||||||
loadClients();
|
loadClients();
|
||||||
|
|
||||||
|
|||||||
@@ -40,23 +40,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Settings Card -->
|
|
||||||
<div class="mb-6 bg-slate-900/60 border border-slate-800 rounded-xl p-5">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<h3 class="font-semibold text-slate-100">Approval Mode</h3>
|
|
||||||
<p class="text-sm text-slate-400">
|
|
||||||
When enabled, new clients must be approved before connecting.
|
|
||||||
When disabled, clients auto-approve on first connection.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<label class="relative inline-flex items-center cursor-pointer">
|
|
||||||
<input type="checkbox" id="require-approval-toggle" class="sr-only peer" />
|
|
||||||
<div class="w-11 h-6 bg-slate-700 peer-focus:ring-2 peer-focus:ring-sky-500 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-slate-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-amber-500"></div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Filter Tabs -->
|
<!-- Filter Tabs -->
|
||||||
<div class="mb-4 flex items-center gap-2 flex-wrap">
|
<div class="mb-4 flex items-center gap-2 flex-wrap">
|
||||||
<button data-filter="pending"
|
<button data-filter="pending"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { authenticateRequest } from "../../auth";
|
import { authenticateRequest } from "../../auth";
|
||||||
import { getConfig, updateEnrollmentConfig } from "../../config";
|
import { getConfig } from "../../config";
|
||||||
import {
|
import {
|
||||||
getPendingClients,
|
getPendingClients,
|
||||||
getEnrollmentStats,
|
getEnrollmentStats,
|
||||||
@@ -49,37 +49,6 @@ export async function handleEnrollmentRoutes(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// PUT /api/enrollment/settings
|
|
||||||
if (req.method === "PUT" && url.pathname === "/api/enrollment/settings") {
|
|
||||||
const user = await authenticateRequest(req);
|
|
||||||
if (!user) return new Response("Unauthorized", { status: 401 });
|
|
||||||
if (user.role !== "admin") return new Response("Forbidden", { status: 403 });
|
|
||||||
|
|
||||||
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 });
|
|
||||||
}
|
|
||||||
|
|
||||||
await updateEnrollmentConfig({ requireApproval: body.requireApproval });
|
|
||||||
|
|
||||||
logAudit({
|
|
||||||
timestamp: Date.now(),
|
|
||||||
username: user.username,
|
|
||||||
ip: "server",
|
|
||||||
action: AuditAction.ENROLLMENT_SETTINGS,
|
|
||||||
success: true,
|
|
||||||
details: JSON.stringify({ enrollment: { requireApproval: body.requireApproval } }),
|
|
||||||
});
|
|
||||||
|
|
||||||
return Response.json({ ok: true, requireApproval: body.requireApproval });
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST /api/enrollment/:id/approve
|
// POST /api/enrollment/:id/approve
|
||||||
const approveMatch = url.pathname.match(/^\/api\/enrollment\/([^/]+)\/approve$/);
|
const approveMatch = url.pathname.match(/^\/api\/enrollment\/([^/]+)\/approve$/);
|
||||||
if (req.method === "POST" && approveMatch) {
|
if (req.method === "POST" && approveMatch) {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import geoip from "geoip-lite";
|
|||||||
import { logAudit, AuditAction } from "../../auditLog";
|
import { logAudit, AuditAction } from "../../auditLog";
|
||||||
import * as clientManager from "../../clientManager";
|
import * as clientManager from "../../clientManager";
|
||||||
import { clientExists, setOnlineState, upsertClientRow, getClientEnrollmentStatus, setClientEnrollmentStatus, lookupClientByPublicKey, getClientPublicKeyById } from "../../db";
|
import { clientExists, setOnlineState, upsertClientRow, getClientEnrollmentStatus, setClientEnrollmentStatus, lookupClientByPublicKey, getClientPublicKeyById } from "../../db";
|
||||||
import { getConfig } from "../../config";
|
|
||||||
import { logger } from "../../logger";
|
import { logger } from "../../logger";
|
||||||
import { metrics } from "../../metrics";
|
import { metrics } from "../../metrics";
|
||||||
import { decodeMessage, encodeMessage, type WireMessage } from "../../protocol";
|
import { decodeMessage, encodeMessage, type WireMessage } from "../../protocol";
|
||||||
@@ -250,8 +249,6 @@ export async function handleWebSocketMessage(
|
|||||||
ws.data.enrollmentNonce = undefined;
|
ws.data.enrollmentNonce = undefined;
|
||||||
|
|
||||||
const keyFingerprint = computeKeyFingerprint(publicKey);
|
const keyFingerprint = computeKeyFingerprint(publicKey);
|
||||||
const config = getConfig();
|
|
||||||
const requireApproval = config.enrollment?.requireApproval ?? true;
|
|
||||||
|
|
||||||
const existing = lookupClientByPublicKey(publicKey);
|
const existing = lookupClientByPublicKey(publicKey);
|
||||||
let enrollmentStatus: string;
|
let enrollmentStatus: string;
|
||||||
@@ -260,7 +257,7 @@ export async function handleWebSocketMessage(
|
|||||||
enrollmentStatus = existing.enrollmentStatus;
|
enrollmentStatus = existing.enrollmentStatus;
|
||||||
ws.data.clientId = existing.id;
|
ws.data.clientId = existing.id;
|
||||||
} else {
|
} else {
|
||||||
enrollmentStatus = requireApproval ? "pending" : "approved";
|
enrollmentStatus = "pending";
|
||||||
|
|
||||||
const existingPk = getClientPublicKeyById(ws.data.clientId);
|
const existingPk = getClientPublicKeyById(ws.data.clientId);
|
||||||
if (existingPk && existingPk !== publicKey) {
|
if (existingPk && existingPk !== publicKey) {
|
||||||
|
|||||||
Reference in New Issue
Block a user