Fix RDP WebSocket auth and tunnel UUID protocol
- RdpTab: remove token from base URL; pass via client.connect(data) because WebSocketTunnel always appends "?"+data to the URL, corrupting a pre-built ?token=JWT into ?token=JWT?<data> - rdp.ts: send UUID as Guacamole internal instruction "0.,36.<uuid>;" (opcode="" = INTERNAL_DATA_OPCODE) instead of a plain string, matching the WebSocketTunnel 1.5.0+ protocol expectation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -166,10 +166,11 @@ export async function rdpWebsocket(fastify: FastifyInstance) {
|
|||||||
throw new Error(`guacd handshake failed: expected 'ready', got '${readyInstruction[0]}'`);
|
throw new Error(`guacd handshake failed: expected 'ready', got '${readyInstruction[0]}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Send the guacd connection UUID as the first WebSocket message.
|
// 5. Send the tunnel UUID as a Guacamole internal instruction.
|
||||||
// Guacamole.WebSocketTunnel expects this as its tunnel-UUID handshake.
|
// WebSocketTunnel (1.5.0+) expects opcode "" (empty string) with the
|
||||||
|
// UUID as the single argument: "0.,36.<uuid>;"
|
||||||
const guacdUUID = readyInstruction[1] ?? randomUUID();
|
const guacdUUID = readyInstruction[1] ?? randomUUID();
|
||||||
socket.send(guacdUUID);
|
socket.send(buildInstruction('', guacdUUID));
|
||||||
|
|
||||||
// 6. Flush any buffered bytes that arrived after 'ready'
|
// 6. Flush any buffered bytes that arrived after 'ready'
|
||||||
if (tcpBuf.value.length > 0 && socket.readyState === WebSocket.OPEN) {
|
if (tcpBuf.value.length > 0 && socket.readyState === WebSocket.OPEN) {
|
||||||
|
|||||||
@@ -10,10 +10,12 @@ interface Props {
|
|||||||
|
|
||||||
type Status = 'connecting' | 'connected' | 'disconnected' | 'error';
|
type Status = 'connecting' | 'connected' | 'disconnected' | 'error';
|
||||||
|
|
||||||
function getWsUrl(connectionId: string, token: string): string {
|
// NOTE: Do NOT include query params here. Guacamole.WebSocketTunnel always
|
||||||
|
// appends "?" + connectData to the URL, so auth params must go via connect().
|
||||||
|
function getWsUrl(connectionId: string): string {
|
||||||
const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
const host = window.location.host;
|
const host = window.location.host;
|
||||||
return `${proto}//${host}/ws/rdp/${connectionId}?token=${encodeURIComponent(token)}`;
|
return `${proto}//${host}/ws/rdp/${connectionId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RdpTab: React.FC<Props> = ({ session }) => {
|
export const RdpTab: React.FC<Props> = ({ session }) => {
|
||||||
@@ -29,7 +31,7 @@ export const RdpTab: React.FC<Props> = ({ session }) => {
|
|||||||
setStatus('connecting');
|
setStatus('connecting');
|
||||||
setErrorMsg('');
|
setErrorMsg('');
|
||||||
|
|
||||||
const url = getWsUrl(session.connection.id, token);
|
const url = getWsUrl(session.connection.id);
|
||||||
const tunnel = new Guacamole.WebSocketTunnel(url);
|
const tunnel = new Guacamole.WebSocketTunnel(url);
|
||||||
const client = new Guacamole.Client(tunnel);
|
const client = new Guacamole.Client(tunnel);
|
||||||
|
|
||||||
@@ -80,8 +82,9 @@ export const RdpTab: React.FC<Props> = ({ session }) => {
|
|||||||
setErrorMsg(`Client error: ${error.message ?? 'unknown'}`);
|
setErrorMsg(`Client error: ${error.message ?? 'unknown'}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Connect
|
// Connect — pass token as query param via connect(data).
|
||||||
client.connect();
|
// WebSocketTunnel appends "?" + data to the base URL, so auth goes here.
|
||||||
|
client.connect(`token=${encodeURIComponent(token)}`);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
keyboard.onkeydown = null;
|
keyboard.onkeydown = null;
|
||||||
|
|||||||
Reference in New Issue
Block a user