mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-31 07:35:24 +00:00
feat: finish the remote-desktop plugin (B14)
This commit is contained in:
1 parent
0af2c94983
commit
14eb558b85
215 files changed
+30029
-3921
No files matched your search
@@ -812,9 +812,6 @@ export function registerHostBulkRoutes(
|
||||
? 1
|
||||
: 0,
|
||||
enableSsh: hostData.enableSsh ?? effectiveConnectionType === "ssh",
|
||||
enableRdp: hostData.enableRdp ?? false,
|
||||
enableVnc: hostData.enableVnc ?? false,
|
||||
enableTelnet: hostData.enableTelnet ?? false,
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
@@ -828,21 +825,10 @@ export function registerHostBulkRoutes(
|
||||
sshDataObj.rdpUser = hostData.rdpUser || null;
|
||||
sshDataObj.rdpPassword = hostData.rdpPassword || null;
|
||||
sshDataObj.rdpDomain = hostData.rdpDomain || null;
|
||||
sshDataObj.rdpSecurity = hostData.rdpSecurity || null;
|
||||
sshDataObj.rdpIgnoreCert = hostData.rdpIgnoreCert ? 1 : 0;
|
||||
sshDataObj.rdpPort = hostData.rdpPort || 3389;
|
||||
sshDataObj.vncUser = hostData.vncUser || null;
|
||||
sshDataObj.vncPassword = hostData.vncPassword || null;
|
||||
sshDataObj.vncPort = hostData.vncPort || 5900;
|
||||
sshDataObj.telnetUser = hostData.telnetUser || null;
|
||||
sshDataObj.telnetPassword = hostData.telnetPassword || null;
|
||||
sshDataObj.telnetPort = hostData.telnetPort || 23;
|
||||
sshDataObj.enableRdp = hostData.enableRdp ? 1 : 0;
|
||||
sshDataObj.enableVnc = hostData.enableVnc ? 1 : 0;
|
||||
sshDataObj.enableTelnet = hostData.enableTelnet ? 1 : 0;
|
||||
sshDataObj.guacamoleConfig = hostData.guacamoleConfig
|
||||
? JSON.stringify(hostData.guacamoleConfig)
|
||||
: null;
|
||||
} else {
|
||||
sshDataObj.password =
|
||||
hostData.authType === "password" ? hostData.password : null;
|
||||
@@ -855,9 +841,6 @@ export function registerHostBulkRoutes(
|
||||
sshDataObj.keyType =
|
||||
hostData.authType === "key" ? hostData.keyType || "auto" : null;
|
||||
sshDataObj.domain = null;
|
||||
sshDataObj.security = null;
|
||||
sshDataObj.ignoreCert = 0;
|
||||
sshDataObj.guacamoleConfig = null;
|
||||
}
|
||||
|
||||
const lookupKey = `${hostData.ip}:${hostData.port}:${hostData.username}`;
|
||||
@@ -1079,9 +1062,6 @@ export function registerHostBulkRoutes(
|
||||
portKnockSequence: null,
|
||||
overrideCredentialUsername: 0,
|
||||
enableSsh: true,
|
||||
enableRdp: false,
|
||||
enableVnc: false,
|
||||
enableTelnet: false,
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
|
||||
@@ -23,12 +23,7 @@ export function applyHostKeyTypeUpdate(
|
||||
}
|
||||
}
|
||||
|
||||
const PROTOCOL_ENABLE_FIELDS = [
|
||||
"enableSsh",
|
||||
"enableRdp",
|
||||
"enableVnc",
|
||||
"enableTelnet",
|
||||
] as const;
|
||||
const PROTOCOL_ENABLE_FIELDS = ["enableSsh"] as const;
|
||||
|
||||
export function normalizeProtocolEnableFields(
|
||||
values: Record<string, unknown>,
|
||||
@@ -214,13 +209,7 @@ export type NormalizedImportedHost = Record<string, unknown> & {
|
||||
portKnockSequence?: unknown;
|
||||
overrideCredentialUsername?: unknown;
|
||||
domain?: unknown;
|
||||
security?: unknown;
|
||||
ignoreCert?: unknown;
|
||||
guacamoleConfig?: unknown;
|
||||
enableSsh: boolean;
|
||||
enableRdp: boolean;
|
||||
enableVnc: boolean;
|
||||
enableTelnet: boolean;
|
||||
};
|
||||
|
||||
export function normalizeImportedHost(
|
||||
@@ -275,18 +264,6 @@ export function normalizeImportedHost(
|
||||
hostData.enableSsh === undefined
|
||||
? connectionType === "ssh"
|
||||
: asBoolean(hostData.enableSsh),
|
||||
enableRdp:
|
||||
hostData.enableRdp === undefined
|
||||
? connectionType === "rdp"
|
||||
: asBoolean(hostData.enableRdp),
|
||||
enableVnc:
|
||||
hostData.enableVnc === undefined
|
||||
? connectionType === "vnc"
|
||||
: asBoolean(hostData.enableVnc),
|
||||
enableTelnet:
|
||||
hostData.enableTelnet === undefined
|
||||
? connectionType === "telnet"
|
||||
: asBoolean(hostData.enableTelnet),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -372,14 +349,8 @@ const CONNECT_LEVEL_FIELDS = new Set([
|
||||
"showDockerInSidebar",
|
||||
"showServerStatsInSidebar",
|
||||
"enableSsh",
|
||||
"enableRdp",
|
||||
"enableVnc",
|
||||
"enableTelnet",
|
||||
"sshPort",
|
||||
"rdpPort",
|
||||
"rdpAuthType",
|
||||
"vncPort",
|
||||
"telnetPort",
|
||||
"defaultPath",
|
||||
"scpLegacy",
|
||||
"tunnelConnections",
|
||||
@@ -472,31 +443,10 @@ export function transformHostResponse(
|
||||
showTunnelInSidebar: !!host.showTunnelInSidebar,
|
||||
showDockerInSidebar: !!host.showDockerInSidebar,
|
||||
showServerStatsInSidebar: !!host.showServerStatsInSidebar,
|
||||
// Old hosts only had connection_type set; the per-protocol enable flags didn't exist yet.
|
||||
// The schema defaults (enableSsh=true, others=false) wrongly mark every old host as SSH.
|
||||
// Detect this migration case: if no non-SSH protocol is explicitly enabled AND
|
||||
// connectionType is set to a non-SSH value, fall back to inferring from connectionType.
|
||||
...(() => {
|
||||
const ct = host.connectionType;
|
||||
const rdp = !!host.enableRdp;
|
||||
const vnc = !!host.enableVnc;
|
||||
const tel = !!host.enableTelnet;
|
||||
const isMigratedNonSsh = !rdp && !vnc && !tel && ct && ct !== "ssh";
|
||||
return {
|
||||
enableSsh: isMigratedNonSsh ? false : !!host.enableSsh,
|
||||
enableRdp: isMigratedNonSsh ? ct === "rdp" : rdp,
|
||||
enableVnc: isMigratedNonSsh ? ct === "vnc" : vnc,
|
||||
enableTelnet: isMigratedNonSsh ? ct === "telnet" : tel,
|
||||
};
|
||||
})(),
|
||||
enableSsh: !!host.enableSsh,
|
||||
sshPort: host.sshPort ?? host.port ?? 22,
|
||||
rdpPort: host.rdpPort ?? 3389,
|
||||
vncPort: host.vncPort ?? 5900,
|
||||
telnetPort: host.telnetPort ?? 23,
|
||||
rdpUser: host.rdpUser || undefined,
|
||||
rdpDomain: host.rdpDomain || undefined,
|
||||
rdpSecurity: host.rdpSecurity || undefined,
|
||||
rdpIgnoreCert: !!host.rdpIgnoreCert,
|
||||
vncUser: host.vncUser || undefined,
|
||||
telnetUser: host.telnetUser || undefined,
|
||||
tunnelConnections: host.tunnelConnections
|
||||
@@ -524,10 +474,5 @@ export function transformHostResponse(
|
||||
? JSON.parse(host.portKnockSequence as string)
|
||||
: [],
|
||||
domain: host.domain || undefined,
|
||||
security: host.security || undefined,
|
||||
ignoreCert: !!host.ignoreCert,
|
||||
guacamoleConfig: host.guacamoleConfig
|
||||
? JSON.parse(host.guacamoleConfig as string)
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
@@ -68,7 +68,6 @@ import type {
|
||||
HostResolutionCredentialRecord,
|
||||
HostResolutionHostRecord,
|
||||
} from "../repositories/host-resolution-repository.js";
|
||||
import { AUTH_PROTOCOL_METADATA } from "../../../types/auth-protocols.js";
|
||||
import {
|
||||
requiresPersonalHostAuthentication,
|
||||
resolveRecipientSharedHostAuthentication,
|
||||
@@ -216,9 +215,6 @@ router.post(
|
||||
terminalConfig,
|
||||
forceKeyboardInteractive,
|
||||
domain,
|
||||
security,
|
||||
ignoreCert,
|
||||
guacamoleConfig,
|
||||
notes,
|
||||
useSocks5,
|
||||
socks5Host,
|
||||
@@ -232,20 +228,12 @@ router.post(
|
||||
macAddress,
|
||||
wolBroadcastAddress,
|
||||
enableSsh,
|
||||
enableRdp,
|
||||
enableVnc,
|
||||
enableTelnet,
|
||||
sshPort,
|
||||
rdpPort,
|
||||
vncPort,
|
||||
telnetPort,
|
||||
rdpAuthType,
|
||||
rdpCredentialId,
|
||||
rdpUser,
|
||||
rdpPassword,
|
||||
rdpDomain,
|
||||
rdpSecurity,
|
||||
rdpIgnoreCert,
|
||||
vncAuthType,
|
||||
vncCredentialId,
|
||||
vncPassword,
|
||||
@@ -267,7 +255,7 @@ router.post(
|
||||
!isNonEmptyString(ip) ||
|
||||
!isValidPort(port) ||
|
||||
!isOptionalBoolean(shareSshAuth) ||
|
||||
![enableSsh, enableRdp, enableVnc, enableTelnet].every(isOptionalBoolean)
|
||||
!isOptionalBoolean(enableSsh)
|
||||
) {
|
||||
sshLogger.warn("Invalid SSH data input validation failed", {
|
||||
operation: "host_create",
|
||||
@@ -364,9 +352,6 @@ router.post(
|
||||
: null,
|
||||
forceKeyboardInteractive: forceKeyboardInteractive ? "true" : "false",
|
||||
domain: domain || null,
|
||||
security: security || null,
|
||||
ignoreCert: ignoreCert ? 1 : 0,
|
||||
guacamoleConfig: guacamoleConfig ? JSON.stringify(guacamoleConfig) : null,
|
||||
notes: notes || null,
|
||||
sudoPassword: sudoPassword || null,
|
||||
useSocks5: useSocks5 ? 1 : 0,
|
||||
@@ -388,27 +373,22 @@ router.post(
|
||||
: null,
|
||||
...normalizeProtocolEnableFields(hostData),
|
||||
sshPort: sshPort || port || 22,
|
||||
rdpPort: rdpPort || 3389,
|
||||
vncPort: vncPort || 5900,
|
||||
telnetPort: telnetPort || 23,
|
||||
rdpAuthType: enableRdp ? rdpAuthType || null : null,
|
||||
rdpAuthType: rdpAuthType || null,
|
||||
rdpCredentialId:
|
||||
enableRdp && rdpAuthType === "credential" && rdpCredentialId
|
||||
rdpAuthType === "credential" && rdpCredentialId
|
||||
? rdpCredentialId
|
||||
: null,
|
||||
rdpUser: rdpUser || null,
|
||||
rdpDomain: rdpDomain || null,
|
||||
rdpSecurity: rdpSecurity || null,
|
||||
rdpIgnoreCert: rdpIgnoreCert ? 1 : 0,
|
||||
vncAuthType: enableVnc ? vncAuthType || null : null,
|
||||
vncAuthType: vncAuthType || null,
|
||||
vncCredentialId:
|
||||
enableVnc && vncAuthType === "credential" && vncCredentialId
|
||||
vncAuthType === "credential" && vncCredentialId
|
||||
? vncCredentialId
|
||||
: null,
|
||||
vncUser: vncUser || null,
|
||||
telnetAuthType: enableTelnet ? telnetAuthType || null : null,
|
||||
telnetAuthType: telnetAuthType || null,
|
||||
telnetCredentialId:
|
||||
enableTelnet && telnetAuthType === "credential" && telnetCredentialId
|
||||
telnetAuthType === "credential" && telnetCredentialId
|
||||
? telnetCredentialId
|
||||
: null,
|
||||
telnetUser: telnetUser || null,
|
||||
@@ -932,9 +912,6 @@ router.put(
|
||||
terminalConfig,
|
||||
forceKeyboardInteractive,
|
||||
domain,
|
||||
security,
|
||||
ignoreCert,
|
||||
guacamoleConfig,
|
||||
notes,
|
||||
useSocks5,
|
||||
socks5Host,
|
||||
@@ -948,20 +925,12 @@ router.put(
|
||||
macAddress,
|
||||
wolBroadcastAddress,
|
||||
enableSsh,
|
||||
enableRdp,
|
||||
enableVnc,
|
||||
enableTelnet,
|
||||
sshPort,
|
||||
rdpPort,
|
||||
vncPort,
|
||||
telnetPort,
|
||||
rdpAuthType,
|
||||
rdpCredentialId,
|
||||
rdpUser,
|
||||
rdpPassword,
|
||||
rdpDomain,
|
||||
rdpSecurity,
|
||||
rdpIgnoreCert,
|
||||
vncAuthType,
|
||||
vncCredentialId,
|
||||
vncPassword,
|
||||
@@ -983,9 +952,7 @@ router.put(
|
||||
!isNonEmptyString(ip) ||
|
||||
!isValidPort(port) ||
|
||||
!isOptionalBoolean(shareSshAuth) ||
|
||||
![enableSsh, enableRdp, enableVnc, enableTelnet].every(
|
||||
isOptionalBoolean,
|
||||
) ||
|
||||
!isOptionalBoolean(enableSsh) ||
|
||||
!hostId
|
||||
) {
|
||||
sshLogger.warn("Invalid SSH data input validation failed for update", {
|
||||
@@ -1083,9 +1050,6 @@ router.put(
|
||||
: null,
|
||||
forceKeyboardInteractive: forceKeyboardInteractive ? "true" : "false",
|
||||
domain: domain || null,
|
||||
security: security || null,
|
||||
ignoreCert: ignoreCert ? 1 : 0,
|
||||
guacamoleConfig: guacamoleConfig ? JSON.stringify(guacamoleConfig) : null,
|
||||
notes: notes || null,
|
||||
sudoPassword: sudoPassword || null,
|
||||
useSocks5: useSocks5 ? 1 : 0,
|
||||
@@ -1107,27 +1071,22 @@ router.put(
|
||||
: null,
|
||||
...normalizeProtocolEnableFields(hostData),
|
||||
sshPort: sshPort || port || 22,
|
||||
rdpPort: rdpPort || 3389,
|
||||
vncPort: vncPort || 5900,
|
||||
telnetPort: telnetPort || 23,
|
||||
rdpAuthType: enableRdp ? rdpAuthType || null : null,
|
||||
rdpAuthType: rdpAuthType || null,
|
||||
rdpCredentialId:
|
||||
enableRdp && rdpAuthType === "credential" && rdpCredentialId
|
||||
rdpAuthType === "credential" && rdpCredentialId
|
||||
? rdpCredentialId
|
||||
: null,
|
||||
rdpUser: rdpUser || null,
|
||||
rdpDomain: rdpDomain || null,
|
||||
rdpSecurity: rdpSecurity || null,
|
||||
rdpIgnoreCert: rdpIgnoreCert ? 1 : 0,
|
||||
vncAuthType: enableVnc ? vncAuthType || null : null,
|
||||
vncAuthType: vncAuthType || null,
|
||||
vncCredentialId:
|
||||
enableVnc && vncAuthType === "credential" && vncCredentialId
|
||||
vncAuthType === "credential" && vncCredentialId
|
||||
? vncCredentialId
|
||||
: null,
|
||||
vncUser: vncUser || null,
|
||||
telnetAuthType: enableTelnet ? telnetAuthType || null : null,
|
||||
telnetAuthType: telnetAuthType || null,
|
||||
telnetCredentialId:
|
||||
enableTelnet && telnetAuthType === "credential" && telnetCredentialId
|
||||
telnetAuthType === "credential" && telnetCredentialId
|
||||
? telnetCredentialId
|
||||
: null,
|
||||
telnetUser: telnetUser || null,
|
||||
@@ -2085,9 +2044,7 @@ router.get(
|
||||
|
||||
const exportedConnectionType =
|
||||
(resolvedHost.connectionType as string) || "ssh";
|
||||
const isRemoteDesktop = ["rdp", "vnc", "telnet"].includes(
|
||||
exportedConnectionType,
|
||||
);
|
||||
const isRemoteDesktop = exportedConnectionType !== "ssh";
|
||||
|
||||
const baseExportData = {
|
||||
exportId: resolvedHost.id,
|
||||
@@ -2104,24 +2061,18 @@ router.get(
|
||||
: resolvedHost.tags || [],
|
||||
pin: !!resolvedHost.pin,
|
||||
notes: resolvedHost.notes || null,
|
||||
// Every plugin's host settings, secrets redacted, for import to hand back.
|
||||
pluginSettings: hostPluginSettings ?? {},
|
||||
};
|
||||
|
||||
const exportData = isRemoteDesktop
|
||||
? {
|
||||
...baseExportData,
|
||||
enableRdp: !!resolvedHost.enableRdp,
|
||||
enableVnc: !!resolvedHost.enableVnc,
|
||||
enableTelnet: !!resolvedHost.enableTelnet,
|
||||
rdpPort: resolvedHost.rdpPort || 3389,
|
||||
vncPort: resolvedHost.vncPort || 5900,
|
||||
telnetPort: resolvedHost.telnetPort || 23,
|
||||
rdpAuthType: resolvedHost.rdpAuthType || null,
|
||||
rdpCredentialId: resolvedHost.rdpCredentialId || null,
|
||||
rdpUser: resolvedHost.rdpUser || null,
|
||||
rdpPassword: resolvedHost.rdpPassword || null,
|
||||
rdpDomain: resolvedHost.rdpDomain || null,
|
||||
rdpSecurity: resolvedHost.rdpSecurity || null,
|
||||
rdpIgnoreCert: !!resolvedHost.rdpIgnoreCert,
|
||||
vncAuthType: resolvedHost.vncAuthType || null,
|
||||
vncCredentialId: resolvedHost.vncCredentialId || null,
|
||||
vncUser: resolvedHost.vncUser || null,
|
||||
@@ -2130,9 +2081,6 @@ router.get(
|
||||
telnetCredentialId: resolvedHost.telnetCredentialId || null,
|
||||
telnetUser: resolvedHost.telnetUser || null,
|
||||
telnetPassword: resolvedHost.telnetPassword || null,
|
||||
guacamoleConfig: resolvedHost.guacamoleConfig
|
||||
? JSON.parse(resolvedHost.guacamoleConfig as string)
|
||||
: null,
|
||||
}
|
||||
: {
|
||||
...baseExportData,
|
||||
@@ -2273,9 +2221,7 @@ router.get(
|
||||
|
||||
const exportedConnectionType =
|
||||
(resolvedHost.connectionType as string) || "ssh";
|
||||
const isRemoteDesktop = ["rdp", "vnc", "telnet"].includes(
|
||||
exportedConnectionType,
|
||||
);
|
||||
const isRemoteDesktop = exportedConnectionType !== "ssh";
|
||||
|
||||
const baseExportData = {
|
||||
exportId: resolvedHost.id,
|
||||
@@ -2292,17 +2238,14 @@ router.get(
|
||||
: resolvedHost.tags || [],
|
||||
pin: !!resolvedHost.pin,
|
||||
notes: resolvedHost.notes || null,
|
||||
// Every plugin's host settings, secrets redacted, for import to hand back.
|
||||
pluginSettings: hostPluginSettings ?? {},
|
||||
};
|
||||
|
||||
const exportData = isRemoteDesktop
|
||||
? {
|
||||
...baseExportData,
|
||||
domain: resolvedHost.domain || null,
|
||||
security: resolvedHost.security || null,
|
||||
ignoreCert: !!resolvedHost.ignoreCert,
|
||||
guacamoleConfig: resolvedHost.guacamoleConfig
|
||||
? JSON.parse(resolvedHost.guacamoleConfig as string)
|
||||
: null,
|
||||
}
|
||||
: {
|
||||
...baseExportData,
|
||||
@@ -2638,8 +2581,9 @@ async function resolveHostCredentials(
|
||||
host.id,
|
||||
requestingUserId,
|
||||
);
|
||||
// Whether a protocol is on is the remote-desktop plugin's host
|
||||
// setting; the client only offers an override for one that is.
|
||||
for (const protocol of ["rdp", "vnc", "telnet"] as const) {
|
||||
if (!host[AUTH_PROTOCOL_METADATA[protocol].enableField]) continue;
|
||||
authOverrides[protocol] = {
|
||||
credentialId: overrideCredentialIds[protocol],
|
||||
required: false,
|
||||
|
||||
@@ -39,11 +39,10 @@ const pickPreferences = (row?: UserPreferenceRecord | null) => ({
|
||||
customThemes: row?.customThemes ?? null,
|
||||
customKeybindings: row?.customKeybindings ?? null,
|
||||
terminalDefaults: row?.terminalDefaults ?? null,
|
||||
rdpDefaults: row?.rdpDefaults ?? null,
|
||||
terminalMacros: row?.terminalMacros ?? null,
|
||||
});
|
||||
|
||||
const connectionDefaultFields = ["terminalDefaults", "rdpDefaults"] as const;
|
||||
const connectionDefaultFields = ["terminalDefaults"] as const;
|
||||
|
||||
export function validateDefaultsJson(value: string): boolean {
|
||||
if (value.length > 32_768) return false;
|
||||
@@ -233,7 +232,6 @@ router.put("/", authenticateJWT, async (req: Request, res: Response) => {
|
||||
customThemes,
|
||||
customKeybindings,
|
||||
terminalDefaults,
|
||||
rdpDefaults,
|
||||
terminalMacros,
|
||||
} = req.body as {
|
||||
reopenTabsOnLogin?: boolean;
|
||||
@@ -256,7 +254,6 @@ router.put("/", authenticateJWT, async (req: Request, res: Response) => {
|
||||
customThemes?: string | null;
|
||||
customKeybindings?: string | null;
|
||||
terminalDefaults?: string | null;
|
||||
rdpDefaults?: string | null;
|
||||
terminalMacros?: string | null;
|
||||
};
|
||||
// showHostTags, hostTrayOnClick, compactHostView, statusColorScheme are no
|
||||
@@ -289,7 +286,6 @@ router.put("/", authenticateJWT, async (req: Request, res: Response) => {
|
||||
customThemes,
|
||||
customKeybindings,
|
||||
terminalDefaults,
|
||||
rdpDefaults,
|
||||
terminalMacros,
|
||||
})) {
|
||||
if (value !== undefined && value !== null && typeof value !== "string") {
|
||||
@@ -299,7 +295,6 @@ router.put("/", authenticateJWT, async (req: Request, res: Response) => {
|
||||
|
||||
const connectionDefaults = {
|
||||
terminalDefaults,
|
||||
rdpDefaults,
|
||||
};
|
||||
for (const key of connectionDefaultFields) {
|
||||
const value = connectionDefaults[key];
|
||||
@@ -435,7 +430,6 @@ router.put("/", authenticateJWT, async (req: Request, res: Response) => {
|
||||
updates.customKeybindings = customKeybindings;
|
||||
if (terminalDefaults !== undefined)
|
||||
updates.terminalDefaults = terminalDefaults;
|
||||
if (rdpDefaults !== undefined) updates.rdpDefaults = rdpDefaults;
|
||||
if (terminalMacros !== undefined) updates.terminalMacros = terminalMacros;
|
||||
|
||||
if (Object.keys(updates).length === 1) {
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import type { AuthenticatedRequest } from "../../../types/index.js";
|
||||
import { getErrorMessage } from "../../utils/error-message.js";
|
||||
import type { RequestHandler, Router } from "express";
|
||||
// Remote Desktop is a first-party plugin (plugins/remote-desktop); this goes
|
||||
// through the same in-core bridge its router is mounted behind, rather than
|
||||
// importing the plugin's TypeScript directly, since plugins/ is compiled
|
||||
// separately from src/backend/ and core cannot statically import across that
|
||||
// boundary. See guacamole-dispatch.ts and that plugin's README.
|
||||
import { restartGuacamoleService as restartGuacServer } from "../../hosts/guacamole-sessions.js";
|
||||
import {
|
||||
authLogger,
|
||||
getGlobalLogLevel,
|
||||
@@ -31,10 +25,6 @@ import {
|
||||
} from "../repositories/factory.js";
|
||||
import type { UserRecord } from "../repositories/user-repository.js";
|
||||
|
||||
function getDefaultGuacUrl(): string {
|
||||
return `${process.env.GUACD_HOST || "localhost"}:${process.env.GUACD_PORT || "4822"}`;
|
||||
}
|
||||
|
||||
export type HostDefaults = {
|
||||
useSocks5?: boolean;
|
||||
socks5Host?: string;
|
||||
@@ -66,119 +56,6 @@ export function registerUserSettingsRoutes(
|
||||
router: Router,
|
||||
authenticateJWT: RequestHandler,
|
||||
): void {
|
||||
/**
|
||||
* @openapi
|
||||
* /users/guacamole-settings:
|
||||
* get:
|
||||
* summary: Get Guacamole settings
|
||||
* description: Returns current guacd enabled status and host:port URL. No authentication required.
|
||||
* tags:
|
||||
* - Users
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Guacamole settings.
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* enabled:
|
||||
* type: boolean
|
||||
* url:
|
||||
* type: string
|
||||
* 500:
|
||||
* description: Failed to get guacamole settings.
|
||||
*/
|
||||
router.get("/guacamole-settings", authenticateJWT, async (_req, res) => {
|
||||
try {
|
||||
const settings = createCurrentSettingsRepository();
|
||||
const enabled = await settings.getBoolean("guac_enabled", true);
|
||||
const url = await settings.get("guac_url");
|
||||
res.json({
|
||||
enabled,
|
||||
url: url ?? getDefaultGuacUrl(),
|
||||
});
|
||||
} catch (err) {
|
||||
authLogger.error("Failed to get guacamole settings", err);
|
||||
res.status(500).json({ error: "Failed to get guacamole settings" });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /users/guacamole-settings:
|
||||
* patch:
|
||||
* summary: Update Guacamole settings
|
||||
* description: Admin-only. Updates guacd enabled status and/or host:port URL.
|
||||
* tags:
|
||||
* - Users
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* enabled:
|
||||
* type: boolean
|
||||
* url:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Guacamole settings updated.
|
||||
* 403:
|
||||
* description: Not authorized.
|
||||
* 500:
|
||||
* description: Failed to update guacamole settings.
|
||||
*/
|
||||
router.patch("/guacamole-settings", authenticateJWT, async (req, res) => {
|
||||
const userId = (req as AuthenticatedRequest).userId;
|
||||
try {
|
||||
const actor = await getAdminActor(userId);
|
||||
if (!actor) {
|
||||
return res.status(403).json({ error: "Not authorized" });
|
||||
}
|
||||
const { enabled, url } = req.body;
|
||||
const settings = createCurrentSettingsRepository();
|
||||
if (typeof enabled === "boolean") {
|
||||
await settings.set("guac_enabled", enabled ? "true" : "false");
|
||||
}
|
||||
if (typeof url === "string") {
|
||||
await settings.set("guac_url", url);
|
||||
try {
|
||||
await restartGuacServer();
|
||||
} catch (err) {
|
||||
authLogger.error(
|
||||
"Failed to restart guac server after URL update",
|
||||
err,
|
||||
);
|
||||
}
|
||||
}
|
||||
const currentEnabled = await settings.getBoolean("guac_enabled", true);
|
||||
const currentUrl = await settings.get("guac_url");
|
||||
|
||||
const { ipAddress, userAgent } = getRequestMeta(req);
|
||||
await logAudit({
|
||||
userId,
|
||||
username: actor.username ?? userId,
|
||||
action: "update_guacamole_settings",
|
||||
resourceType: "setting",
|
||||
details: JSON.stringify({ enabled, url }),
|
||||
ipAddress,
|
||||
userAgent,
|
||||
success: true,
|
||||
});
|
||||
|
||||
res.json({
|
||||
enabled: currentEnabled,
|
||||
url: currentUrl ?? getDefaultGuacUrl(),
|
||||
});
|
||||
} catch (err) {
|
||||
authLogger.error("Failed to update guacamole settings", err);
|
||||
res.status(500).json({ error: "Failed to update guacamole settings" });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /users/log-level:
|
||||
|
||||
Reference in new issue
Block a user