mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-08-29 10:21:34 +00:00
fix: prompt shared RDP users for credentials (#1345)
This commit is contained in:
@@ -346,6 +346,7 @@ const CONNECT_LEVEL_FIELDS = new Set([
|
||||
"enableTelnet",
|
||||
"sshPort",
|
||||
"rdpPort",
|
||||
"rdpAuthType",
|
||||
"vncPort",
|
||||
"telnetPort",
|
||||
"defaultPath",
|
||||
|
||||
@@ -34,3 +34,31 @@ export function resolveRdpDomain(
|
||||
? promptedDomain
|
||||
: storedDomain;
|
||||
}
|
||||
|
||||
type SharedRdpAuthResolution =
|
||||
| { source: "personal-override" }
|
||||
| { source: "owner-shared"; authType: string }
|
||||
| { source: "secretless" }
|
||||
| { source: "required" }
|
||||
| null;
|
||||
|
||||
export function resolveRdpAuthTypeForConnect({
|
||||
storedAuthType,
|
||||
credentialId,
|
||||
sharedResolution,
|
||||
}: {
|
||||
storedAuthType?: string | null;
|
||||
credentialId?: number | null;
|
||||
sharedResolution?: SharedRdpAuthResolution;
|
||||
}): string {
|
||||
if (sharedResolution === undefined) {
|
||||
return storedAuthType || (credentialId ? "credential" : "direct");
|
||||
}
|
||||
if (sharedResolution?.source === "personal-override") return "credential";
|
||||
if (sharedResolution?.source === "owner-shared") {
|
||||
return sharedResolution.authType;
|
||||
}
|
||||
return sharedResolution?.source === "secretless" && storedAuthType === "none"
|
||||
? "none"
|
||||
: "direct";
|
||||
}
|
||||
|
||||
@@ -6,7 +6,10 @@ import { withDriveSettings } from "./drive-settings.js";
|
||||
import { guacLogger } from "../../utils/logger.js";
|
||||
import { AuthManager } from "../../utils/auth-manager.js";
|
||||
import { PermissionManager } from "../../utils/permission-manager.js";
|
||||
import { resolveRecipientSharedHostAuthentication } from "../../utils/shared-host-auth-resolver.js";
|
||||
import {
|
||||
resolveRecipientSharedHostAuthentication,
|
||||
type RecipientSharedHostAuthResolution,
|
||||
} from "../../utils/shared-host-auth-resolver.js";
|
||||
import type { AuthOverrideProtocol } from "../../../types/auth-protocols.js";
|
||||
import net from "net";
|
||||
import crypto from "crypto";
|
||||
@@ -25,7 +28,11 @@ import {
|
||||
getRequestMeta,
|
||||
} from "../../utils/audit-logger.js";
|
||||
import { resolveJumpTunnelEndpoint } from "./jump-tunnel-endpoint.js";
|
||||
import { buildRdpSettings, resolveRdpDomain } from "./rdp-settings.js";
|
||||
import {
|
||||
buildRdpSettings,
|
||||
resolveRdpAuthTypeForConnect,
|
||||
resolveRdpDomain,
|
||||
} from "./rdp-settings.js";
|
||||
import { createMacosVncCompatibilityProxy } from "./macos-vnc-proxy.js";
|
||||
|
||||
const router = express.Router();
|
||||
@@ -322,6 +329,7 @@ router.post(
|
||||
const hostRecord = host as Record<string, unknown>;
|
||||
const hostRepository = hostResolutionRepository;
|
||||
const isSharedConnection = host.userId !== userId;
|
||||
let sharedAuthResolution: RecipientSharedHostAuthResolution | null = null;
|
||||
|
||||
if (isSharedConnection) {
|
||||
// Recipients never read the owner's raw secrets; wipe them and use
|
||||
@@ -341,6 +349,7 @@ router.post(
|
||||
userId,
|
||||
connectionType as AuthOverrideProtocol,
|
||||
);
|
||||
sharedAuthResolution = resolution;
|
||||
const auth =
|
||||
resolution.source === "personal-override"
|
||||
? { ...resolution.credential, domain: null }
|
||||
@@ -448,10 +457,11 @@ router.post(
|
||||
let username: string;
|
||||
let password: string;
|
||||
|
||||
const rdpAuthTypeForConnect = isSharedConnection
|
||||
? null
|
||||
: (host.rdpAuthType as string) ||
|
||||
(host.rdpCredentialId ? "credential" : "direct");
|
||||
const rdpAuthTypeForConnect = resolveRdpAuthTypeForConnect({
|
||||
storedAuthType: host.rdpAuthType as string | null,
|
||||
credentialId: host.rdpCredentialId as number | null,
|
||||
sharedResolution: isSharedConnection ? sharedAuthResolution : undefined,
|
||||
});
|
||||
|
||||
switch (connectionType) {
|
||||
case "rdp":
|
||||
|
||||
@@ -394,6 +394,7 @@ describe("sanitizeHostForRecipient", () => {
|
||||
{
|
||||
...sharedHost,
|
||||
permissionLevel: "connect",
|
||||
rdpAuthType: "none",
|
||||
authOverrides: {
|
||||
ssh: {
|
||||
credentialId: 9,
|
||||
@@ -408,6 +409,7 @@ describe("sanitizeHostForRecipient", () => {
|
||||
expect(result.ip).toBe("10.0.0.42");
|
||||
expect(result.enableRdp).toBe(true);
|
||||
expect(result.rdpPort).toBe(3389);
|
||||
expect(result.rdpAuthType).toBe("none");
|
||||
expect(result.permissionLevel).toBe("connect");
|
||||
expect(result.shareSshAuth).toBe(true);
|
||||
expect(result.authOverrides).toEqual({
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
buildRdpSettings,
|
||||
resolveRdpAuthTypeForConnect,
|
||||
resolveRdpDomain,
|
||||
} from "../../../hosts/guacamole/rdp-settings.js";
|
||||
|
||||
@@ -52,3 +53,32 @@ describe("buildRdpSettings", () => {
|
||||
expect(resolveRdpDomain("none", undefined, "SAVED")).toBe("SAVED");
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveRdpAuthTypeForConnect", () => {
|
||||
it("keeps prompt-on-connect authentication for a secretless recipient", () => {
|
||||
expect(
|
||||
resolveRdpAuthTypeForConnect({
|
||||
storedAuthType: "none",
|
||||
sharedResolution: { source: "secretless" },
|
||||
}),
|
||||
).toBe("none");
|
||||
});
|
||||
|
||||
it("uses a recipient override instead of prompting", () => {
|
||||
expect(
|
||||
resolveRdpAuthTypeForConnect({
|
||||
storedAuthType: "none",
|
||||
sharedResolution: { source: "personal-override" },
|
||||
}),
|
||||
).toBe("credential");
|
||||
});
|
||||
|
||||
it("uses authentication shared by the owner", () => {
|
||||
expect(
|
||||
resolveRdpAuthTypeForConnect({
|
||||
storedAuthType: "credential",
|
||||
sharedResolution: { source: "owner-shared", authType: "credential" },
|
||||
}),
|
||||
).toBe("credential");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -46,6 +46,7 @@ import { ShareSessionModal } from "@/features/session-sharing/ShareSessionModal.
|
||||
import type { SSHHost } from "@/types";
|
||||
import { useConnectionDefaults } from "@/contexts/ConnectionDefaultsContext";
|
||||
import { resolveConnectionDefaults } from "@/lib/connection-defaults";
|
||||
import { needsRdpCredentialPrompt } from "@/features/guacamole/rdp-credential-prompt";
|
||||
|
||||
interface GuacamoleAppProps {
|
||||
hostId?: string;
|
||||
@@ -162,6 +163,7 @@ interface GuacamoleAppInnerProps {
|
||||
| "domain"
|
||||
| "guacamoleConfig"
|
||||
| "rdpAuthType"
|
||||
| "authOverrides"
|
||||
| "syncId"
|
||||
| "ip"
|
||||
| "rdpPort"
|
||||
@@ -230,8 +232,11 @@ const GuacamoleAppInner = React.forwardRef<
|
||||
const resolvedProtocolForConnect = (protocol ??
|
||||
hostConfig.connectionType ??
|
||||
"rdp") as "rdp" | "vnc" | "telnet";
|
||||
const needsCredentialPrompt =
|
||||
resolvedProtocolForConnect === "rdp" && hostConfig.rdpAuthType === "none";
|
||||
const needsCredentialPrompt = needsRdpCredentialPrompt({
|
||||
protocol: resolvedProtocolForConnect,
|
||||
rdpAuthType: hostConfig.rdpAuthType,
|
||||
authOverrides: hostConfig.authOverrides,
|
||||
});
|
||||
|
||||
const [promptedCredentials, setPromptedCredentials] = useState<{
|
||||
username: string;
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { HostAuthOverrides } from "@/types/auth-protocols";
|
||||
|
||||
export function needsRdpCredentialPrompt({
|
||||
protocol,
|
||||
rdpAuthType,
|
||||
authOverrides,
|
||||
}: {
|
||||
protocol: "rdp" | "vnc" | "telnet";
|
||||
rdpAuthType?: string;
|
||||
authOverrides?: HostAuthOverrides;
|
||||
}): boolean {
|
||||
return (
|
||||
protocol === "rdp" &&
|
||||
rdpAuthType === "none" &&
|
||||
!authOverrides?.rdp?.credentialId
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { needsRdpCredentialPrompt } from "@/features/guacamole/rdp-credential-prompt";
|
||||
|
||||
describe("needsRdpCredentialPrompt", () => {
|
||||
it("prompts recipients when RDP has no saved authentication", () => {
|
||||
expect(
|
||||
needsRdpCredentialPrompt({ protocol: "rdp", rdpAuthType: "none" }),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("does not prompt when the recipient has a personal override", () => {
|
||||
expect(
|
||||
needsRdpCredentialPrompt({
|
||||
protocol: "rdp",
|
||||
rdpAuthType: "none",
|
||||
authOverrides: {
|
||||
rdp: { credentialId: 7, required: false, ownerAuthShared: true },
|
||||
},
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("does not prompt for other protocols", () => {
|
||||
expect(
|
||||
needsRdpCredentialPrompt({ protocol: "vnc", rdpAuthType: "none" }),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user