Test peus concluants

This commit is contained in:
Serge NOEL
2025-12-05 16:33:03 +01:00
parent 0d0d52c93c
commit 8ff22dfc2c
14 changed files with 720 additions and 110 deletions

View File

@@ -23,7 +23,9 @@
"dotenv": "^16.3.1",
"compression": "^1.7.4",
"helmet": "^7.1.0",
"cors": "^2.8.5"
"cors": "^2.8.5",
"node-rdpjs-2": "^0.3.4",
"pngjs": "^7.0.0"
},
"devDependencies": {
"nodemon": "^3.0.2"

View File

@@ -80,7 +80,9 @@ class RDPWebGateway {
await this.authenticateAndLoadTargets();
} catch (error) {
console.error('Login error:', error);
this.showError(errorMessage, 'Connection error. Please check your network and try again.');
// Show specific error message if available
const errorMsg = error.message || 'Connection error. Please check your network and try again.';
this.showError(errorMessage, errorMsg);
loginBtn.disabled = false;
btnText.style.display = 'block';
spinner.style.display = 'none';
@@ -244,7 +246,7 @@ class RDPWebGateway {
}
handleWebSocketMessage(event) {
if (typeof event.data === 'string') {
try {
const message = JSON.parse(event.data);
switch (message.type) {
@@ -252,30 +254,44 @@ class RDPWebGateway {
document.getElementById('loadingOverlay').style.display = 'none';
document.getElementById('connectionInfo').textContent =
`Connected to ${this.currentTarget.name}`;
console.log('RDP connection established');
break;
case 'screen':
// Render screen update (PNG image data)
this.renderScreenUpdate(message);
break;
case 'disconnected':
this.showConnectionError('RDP connection closed');
break;
case 'error':
this.showConnectionError(message.error);
break;
case 'resize':
this.canvas.width = message.width;
this.canvas.height = message.height;
break;
default:
console.warn('Unknown message type:', message.type);
}
} else {
// Binary data - frame update
this.renderFrame(event.data);
} catch (error) {
console.error('Error handling WebSocket message:', error);
}
}
renderFrame(data) {
// This is a simplified version
// In production, you'd decode the RDP frame data properly
const imageData = new Uint8ClampedArray(data);
if (imageData.length === this.canvas.width * this.canvas.height * 4) {
const imgData = this.ctx.createImageData(this.canvas.width, this.canvas.height);
imgData.data.set(imageData);
this.ctx.putImageData(imgData, 0, 0);
renderScreenUpdate(update) {
try {
// Decode base64 PNG image
const img = new Image();
img.onload = () => {
// Draw image to canvas at specified position
this.ctx.drawImage(img, update.x, update.y, update.width, update.height);
};
img.onerror = (error) => {
console.error('Failed to load screen update image:', error);
};
img.src = 'data:image/png;base64,' + update.data;
} catch (error) {
console.error('Error rendering screen update:', error);
}
}
@@ -326,13 +342,18 @@ class RDPWebGateway {
const x = Math.floor((event.clientX - rect.left) * (this.canvas.width / rect.width));
const y = Math.floor((event.clientY - rect.top) * (this.canvas.height / rect.height));
// Map button: 0=left, 1=middle, 2=right
let button = 0;
if (type === 'down' || type === 'up') {
button = event.button;
}
this.ws.send(JSON.stringify({
type: 'mouse',
action: type,
x: x,
y: y,
button: event.button,
deltaY: event.deltaY || 0,
button: button
}));
}
@@ -342,8 +363,9 @@ class RDPWebGateway {
this.ws.send(JSON.stringify({
type: 'keyboard',
action: type,
key: event.key,
code: event.code,
code: event.keyCode || event.which
}));
}
keyCode: event.keyCode,
ctrlKey: event.ctrlKey,
altKey: event.altKey,
@@ -438,3 +460,4 @@ class RDPWebGateway {
// Initialize the app
const rdpGateway = new RDPWebGateway();
// v1.4

View File

@@ -8,6 +8,7 @@ class RDPProxyHandler {
this.rdpSocket = null;
this.isConnected = false;
this.isAuthenticated = false;
this.isRdpMode = false; // Track when in binary RDP forwarding mode
this.dataBuffer = '';
this.pendingTarget = null;
}
@@ -84,7 +85,15 @@ class RDPProxyHandler {
}
handleBrokerData(data) {
// Accumulate data in buffer
// If in RDP mode, forward binary data directly
if (this.isRdpMode) {
if (this.ws.readyState === 1) {
this.ws.send(data);
}
return;
}
// Otherwise, accumulate and parse JSON messages
this.dataBuffer += data.toString();
// Check if we have complete message (ends with \n\n or specific delimiter)
@@ -98,13 +107,6 @@ class RDPProxyHandler {
}
});
}
// If already authenticated and in RDP session, forward raw data to WebSocket
if (this.isAuthenticated && this.pendingTarget === null) {
if (this.ws.readyState === 1) {
this.ws.send(data);
}
}
}
processBrokerMessage(message) {
@@ -137,6 +139,8 @@ class RDPProxyHandler {
target: this.pendingTarget
}));
this.pendingTarget = null;
this.isRdpMode = true; // Switch to binary RDP forwarding mode
this.dataBuffer = ''; // Clear JSON buffer
} else {
console.warn(`${new Date().toISOString()} [WARN] Unknown broker message type:`, data.type);

View File

@@ -0,0 +1,355 @@
const net = require('net');
const rdp = require('node-rdpjs-2');
const { PNG } = require('pngjs');
/**
* Clean RDP Proxy Handler - v1.3
* Handles: Authentication with broker → Target selection → RDP connection with screen updates
*/
class RDPProxyHandler {
constructor(websocket, rdpBrokerHost, rdpBrokerPort) {
this.ws = websocket;
this.brokerHost = rdpBrokerHost;
this.brokerPort = rdpBrokerPort;
// State
this.state = 'INIT'; // INIT → AUTH → TARGETS → CONNECTED
this.username = null;
this.currentTarget = null;
// Promise callbacks for async operations
this.authResolve = null;
this.authReject = null;
// Broker connection (for AUTH/SELECT protocol)
this.brokerSocket = null;
this.brokerBuffer = '';
// RDP client (for actual RDP connection to target)
this.rdpClient = null;
this.rdpConnected = false;
}
/**
* Handle incoming WebSocket message from browser
*/
async handleMessage(message) {
try {
const msg = JSON.parse(message);
switch (msg.type) {
case 'authenticate':
await this.handleAuthenticate(msg.username, msg.password);
break;
case 'connect':
await this.handleConnectToTarget(msg.target);
break;
case 'mouse':
this.handleMouseInput(msg);
break;
case 'keyboard':
this.handleKeyboardInput(msg);
break;
default:
console.warn(`Unknown message type: ${msg.type}`);
}
} catch (error) {
console.error('Error handling message:', error);
this.sendError('Failed to process message');
}
}
/**
* Phase 1: Authenticate with RdpBroker
*/
async handleAuthenticate(username, password) {
console.log(`[AUTH] Authenticating user: ${username}`);
this.username = username;
this.state = 'AUTH';
return new Promise((resolve, reject) => {
this.authResolve = resolve;
this.authReject = reject;
this.brokerSocket = new net.Socket();
this.brokerSocket.connect(this.brokerPort, this.brokerHost, () => {
console.log('[AUTH] Connected to broker');
// Send AUTH protocol message
const authMsg = `AUTH\n${username}\n${password}\n`;
this.brokerSocket.write(authMsg);
});
this.brokerSocket.on('data', (data) => {
this.brokerBuffer += data.toString();
// Check for complete JSON message (ends with \n\n)
if (this.brokerBuffer.includes('\n\n')) {
const messages = this.brokerBuffer.split('\n\n');
this.brokerBuffer = messages.pop();
messages.forEach(msgText => {
if (msgText.trim()) {
try {
const msg = JSON.parse(msgText.trim());
this.handleBrokerMessage(msg);
} catch (e) {
console.error('Failed to parse broker message:', e);
}
}
});
}
});
this.brokerSocket.on('error', (error) => {
console.error('[AUTH] Broker connection error:', error);
this.sendError('Failed to connect to authentication server');
if (this.authReject) {
this.authReject(error);
this.authReject = null;
this.authResolve = null;
}
});
this.brokerSocket.on('close', () => {
console.log('[AUTH] Broker connection closed');
});
});
}
/**
* Handle messages from RdpBroker (auth results, target lists, etc)
*/
handleBrokerMessage(msg) {
console.log('[BROKER] Received:', msg.type);
switch (msg.type) {
case 'auth_success':
console.log(`[AUTH] Success - ${msg.targets.length} targets available`);
this.state = 'TARGETS';
this.sendToClient({
type: 'targets',
targets: msg.targets
});
// Resolve the authentication promise
if (this.authResolve) {
this.authResolve();
this.authResolve = null;
this.authReject = null;
}
break;
case 'auth_failed':
console.log('[AUTH] Failed:', msg.message);
this.sendError(msg.message || 'Authentication failed');
// Reject the authentication promise
if (this.authReject) {
this.authReject(new Error(msg.message));
this.authResolve = null;
this.authReject = null;
}
this.cleanup();
break;
case 'rdp_ready':
console.log('[BROKER] RDP session ready signal received');
// Broker has connected to target, but we won't use this connection
// We'll create our own RDP client connection
break;
default:
console.warn('[BROKER] Unknown message type:', msg.type);
}
}
/**
* Phase 2: Connect to selected target via RDP
*/
async handleConnectToTarget(target) {
if (this.state !== 'TARGETS') {
this.sendError('Must authenticate first');
return;
}
console.log(`[RDP] Connecting to target: ${target.name}`);
this.currentTarget = target;
// Send SELECT message to broker (to maintain session tracking)
const selectMsg = `SELECT\n${target.name}\n`;
if (this.brokerSocket && !this.brokerSocket.destroyed) {
this.brokerSocket.write(selectMsg);
}
// Create direct RDP connection to target
await this.connectRDP(target.host, target.port);
}
/**
* Create RDP client connection and handle screen updates
*/
async connectRDP(host, port) {
return new Promise((resolve, reject) => {
try {
console.log(`[RDP] Creating client for ${host}:${port}`);
this.rdpClient = rdp.createClient({
domain: '',
userName: this.username,
password: '', // Already authenticated via broker
enablePerf: true,
autoLogin: true,
screen: { width: 1024, height: 768 },
locale: 'en',
logLevel: 'INFO'
}).on('connect', () => {
console.log('[RDP] Connected');
this.rdpConnected = true;
this.state = 'CONNECTED';
this.sendToClient({
type: 'connected',
target: this.currentTarget
});
resolve();
}).on('bitmap', (bitmap) => {
// Received screen update from RDP server
this.handleScreenUpdate(bitmap);
}).on('close', () => {
console.log('[RDP] Connection closed');
this.rdpConnected = false;
this.sendToClient({ type: 'disconnected' });
}).on('error', (error) => {
console.error('[RDP] Error:', error);
this.sendError('RDP connection failed: ' + error.message);
reject(error);
});
// Connect to RDP server
this.rdpClient.connect(host, port);
} catch (error) {
console.error('[RDP] Failed to create client:', error);
this.sendError('Failed to initialize RDP client');
reject(error);
}
});
}
/**
* Handle screen bitmap updates from RDP server
*/
handleScreenUpdate(bitmap) {
try {
// Convert bitmap to PNG and send to browser
const png = new PNG({
width: bitmap.width,
height: bitmap.height
});
// Copy bitmap data (assuming RGBA format)
png.data = Buffer.from(bitmap.data);
const buffer = PNG.sync.write(png);
const base64Data = buffer.toString('base64');
this.sendToClient({
type: 'screen',
x: bitmap.destLeft || 0,
y: bitmap.destTop || 0,
width: bitmap.width,
height: bitmap.height,
data: base64Data
});
} catch (error) {
console.error('[RDP] Error processing screen update:', error);
}
}
/**
* Handle mouse input from browser
*/
handleMouseInput(msg) {
if (!this.rdpConnected || !this.rdpClient) return;
try {
this.rdpClient.sendPointerEvent(
msg.x,
msg.y,
msg.button || 0,
msg.action === 'down'
);
} catch (error) {
console.error('[RDP] Mouse input error:', error);
}
}
/**
* Handle keyboard input from browser
*/
handleKeyboardInput(msg) {
if (!this.rdpConnected || !this.rdpClient) return;
try {
this.rdpClient.sendKeyEventScancode(
msg.code,
msg.action === 'down'
);
} catch (error) {
console.error('[RDP] Keyboard input error:', error);
}
}
/**
* Send message to browser client
*/
sendToClient(message) {
if (this.ws.readyState === 1) {
this.ws.send(JSON.stringify(message));
}
}
/**
* Send error to browser client
*/
sendError(message) {
this.sendToClient({
type: 'error',
error: message
});
}
/**
* Cleanup all connections
*/
cleanup() {
console.log('[CLEANUP] Closing connections');
if (this.rdpClient) {
try {
this.rdpClient.close();
} catch (e) {
console.error('[CLEANUP] Error closing RDP client:', e);
}
this.rdpClient = null;
}
if (this.brokerSocket && !this.brokerSocket.destroyed) {
this.brokerSocket.destroy();
this.brokerSocket = null;
}
this.rdpConnected = false;
this.state = 'INIT';
}
}
module.exports = RDPProxyHandler;

View File

@@ -6,7 +6,7 @@ const net = require('net');
const compression = require('compression');
const helmet = require('helmet');
const cors = require('cors');
const RDPProxyHandler = require('./rdpProxyHandler');
const RDPProxyHandler = require('./rdpProxyHandler.new');
class RDPWebGatewayServer {
constructor() {
@@ -111,8 +111,8 @@ class RDPWebGatewayServer {
ws.on('message', (data) => {
try {
const message = JSON.parse(data.toString());
proxyHandler.handleMessage(message);
// All messages are JSON (no binary mode)
proxyHandler.handleMessage(data.toString());
} catch (error) {
console.error(`${new Date().toISOString()} [ERROR] WebSocket message error:`, error);
ws.send(JSON.stringify({