diff --git a/package-lock.json b/package-lock.json index 85f0430..47905f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1341,6 +1341,16 @@ "@types/node": "*" } }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", @@ -2047,6 +2057,10 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/backend": { + "resolved": "packages/rdp", + "link": true + }, "node_modules/bagpipe": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/bagpipe/-/bagpipe-0.3.5.tgz", @@ -2722,6 +2736,19 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "license": "MIT" }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cpu-features": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz", @@ -3648,6 +3675,40 @@ "integrity": "sha512-zxztif3GGhKbg1RgOqwmqot8kXgv2HmHFg1EvWwd4q7UfEKvBcYZ0f+7G8HzvU+FUxF0Psqm9Kl5vCbgfrRgJg==", "license": "Apache 2.0" }, + "node_modules/guacamole-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/guacamole-lite/-/guacamole-lite-0.7.3.tgz", + "integrity": "sha512-BXq7BpleikHe+B/Z1NSJIlTzzAnEX87eFW9WCl3aZ5DoEaGcNbVD9Tzj8CUQI3FFYk1LqtZ31MxlRdVojDLVLw==", + "license": "Apache-2.0", + "dependencies": { + "deep-extend": "^0.6.0", + "moment": "^2.17.1", + "ws": "^1.1.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/guacamole-lite/node_modules/ws": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", + "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", + "license": "MIT", + "dependencies": { + "options": ">=0.0.5", + "ultron": "1.0.x" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -3848,6 +3909,13 @@ "node": ">= 4" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -4699,6 +4767,15 @@ "pathe": "^2.0.1" } }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/monaco-editor": { "version": "0.52.2", "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz", @@ -4979,6 +5056,35 @@ "node": ">=6.0.0" } }, + "node_modules/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, "node_modules/noms": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/noms/-/noms-0.0.0.tgz", @@ -5131,6 +5237,14 @@ "wrappy": "1" } }, + "node_modules/options": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", + "integrity": "sha512-bOj3L1ypm++N+n7CEbbe473A414AB7z+amKYshRb//iuL3MpdDCLhPnw6aVTdKB9g5ZRVHIEp8eUln6L2NUStg==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -5481,6 +5595,13 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, "node_modules/pump": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", @@ -6097,6 +6218,19 @@ "simple-concat": "^1.0.0" } }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/slash": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", @@ -6384,6 +6518,19 @@ "node": ">=16" } }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -6585,6 +6732,16 @@ "node": ">=0.6" } }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -6779,6 +6936,12 @@ "node": ">= 0.8" } }, + "node_modules/ultron": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz", + "integrity": "sha512-QMpnpVtYaWEeY+MwKDN/UdKlE/LsFZXM5lO1u7GaZzNgmIbGixHEmVMIKT+vqYOALu3m5GYQy9kz4Xu4IVn7Ow==", + "license": "MIT" + }, "node_modules/unctx": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/unctx/-/unctx-2.4.1.tgz", @@ -6800,6 +6963,13 @@ "@types/estree": "^1.0.0" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", @@ -7494,6 +7664,43 @@ "vite": "^5.2.0", "vue-tsc": "^2.2.8" } + }, + "packages/rdp": { + "name": "backend", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "cors": "^2.8.5", + "express": "^5.1.0", + "guacamole-lite": "^0.7.3", + "ws": "^8.18.1" + }, + "devDependencies": { + "@types/cors": "^2.8.17", + "@types/express": "^5.0.1", + "@types/node": "^22.15.2", + "@types/ws": "^8.18.1", + "nodemon": "^3.1.10", + "ts-node": "^10.9.2", + "typescript": "^5.8.3" + } + }, + "packages/rdp/node_modules/@types/node": { + "version": "22.15.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.2.tgz", + "integrity": "sha512-uKXqKN9beGoMdBfcaTY1ecwz6ctxuJAcUlwE55938g0ZJ8lRxwAZqRz2AJ4pzpt5dHdTPMB863UZ0ESiFUcP7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "packages/rdp/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" } } } diff --git a/packages/rdp/src/server.ts b/packages/rdp/src/server.ts index 9d934d5..a6a62fd 100644 --- a/packages/rdp/src/server.ts +++ b/packages/rdp/src/server.ts @@ -4,16 +4,21 @@ import express, { Request, Response } from 'express'; import http from 'http'; import crypto from 'crypto'; import cors from 'cors'; +// --- Removed fs, path, dotenv imports --- +// --- Configuration --- const GUAC_WS_PORT = process.env.GUAC_WS_PORT || 8081; const API_PORT = process.env.API_PORT || 9090; const GUACD_HOST = process.env.GUACD_HOST || 'localhost'; const GUACD_PORT = parseInt(process.env.GUACD_PORT || '4822', 10); -const ENCRYPTION_KEY_STRING = process.env.ENCRYPTION_KEY || 'ThisIsASecretKeyForGuacLite123!!'; -if (Buffer.byteLength(ENCRYPTION_KEY_STRING, 'utf8') !== 32) { - process.exit(1); -} +// --- Generate In-Memory Encryption Key on Startup --- +console.log("Generating new in-memory encryption key for this session..."); +const ENCRYPTION_KEY_STRING = crypto.randomBytes(32).toString('hex'); +const ENCRYPTION_KEY_BUFFER = Buffer.from(ENCRYPTION_KEY_STRING, 'hex'); +console.log("In-memory encryption key generated."); + +// --- Express App Setup --- const app = express(); const apiServer = http.createServer(app); @@ -36,7 +41,7 @@ const websocketOptions = { const clientOptions = { crypt: { - key: ENCRYPTION_KEY_STRING + key: ENCRYPTION_KEY_STRING // GuacamoleLite expects the string key }, }; @@ -73,31 +78,47 @@ try { process.exit(1); } -const encryptToken = (data: string, key: string): string => { +// Updated encryptToken to use the validated Buffer key and AES-GCM +const encryptToken = (data: string, keyBuffer: Buffer): string => { try { - const iv = crypto.randomBytes(16); - const cipher = crypto.createCipheriv('aes-256-cbc', key, iv); + const iv = crypto.randomBytes(12); // GCM recommended IV size is 12 bytes + const cipher = crypto.createCipheriv('aes-256-gcm', keyBuffer, iv); const encryptedBuffer = Buffer.concat([cipher.update(data, 'utf8'), cipher.final()]); + const tag = cipher.getAuthTag(); // Get the authentication tag - const ivBase64 = iv.toString('base64'); - const encryptedBase64 = encryptedBuffer.toString('base64'); + // Combine IV, encrypted data, and tag for storage/transmission + // Using a format like IV:TAG:ENCRYPTED_DATA (Base64 encoded) is common + const combined = Buffer.concat([iv, tag, encryptedBuffer]); - const encodedObject = { - iv: ivBase64, - value: encryptedBase64 - }; + return combined.toString('base64'); - const jsonString = JSON.stringify(encodedObject); - - const finalToken = Buffer.from(jsonString, 'utf8').toString('base64'); - - return finalToken; } catch (e) { + console.error("Token encryption failed:", e); // Log the actual error throw new Error("Token encryption failed."); } }; +// Example Decryption (if needed elsewhere, ensure consistency) +// const decryptToken = (token: string, keyBuffer: Buffer): string => { +// try { +// const combined = Buffer.from(token, 'base64'); +// const iv = combined.slice(0, 12); +// const tag = combined.slice(12, 12 + 16); +// const encrypted = combined.slice(12 + 16); +// +// const decipher = crypto.createDecipheriv('aes-256-gcm', keyBuffer, iv); +// decipher.setAuthTag(tag); +// +// const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]); +// return decrypted.toString('utf8'); +// } catch (e) { +// console.error("Token decryption failed:", e); +// throw new Error("Token decryption failed."); +// } +// }; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any app.get('/api/get-token', (req: any, res: any) => { const { hostname, port, username, password, security = 'any', ignoreCert = 'true' } = req.query; @@ -126,33 +147,39 @@ app.get('/api/get-token', (req: any, res: any) => { try { const tokenData = JSON.stringify(connectionParams); - const encryptedToken = encryptToken(tokenData, ENCRYPTION_KEY_STRING); + // Use the validated key buffer for encryption + // Use the validated key buffer for encryption + const encryptedToken = encryptToken(tokenData, ENCRYPTION_KEY_BUFFER); res.json({ token: encryptedToken }); } catch (error) { + console.error("Error in /api/get-token:", error); // Log specific error res.status(500).json({ error: 'Failed to generate token' }); } }); apiServer.listen(API_PORT, () => { - // Startup log removed + console.log(`RDP API server listening on port ${API_PORT}`); // Added startup log + console.log(`Guacamole WebSocket server expected to be running on port ${GUAC_WS_PORT}`); }); const gracefulShutdown = (signal: string) => { + console.log(`Received ${signal}. Shutting down gracefully...`); // Added shutdown log let guacClosed = false; let apiClosed = false; const tryExit = () => { if (guacClosed && apiClosed) { + console.log("All servers closed. Exiting."); // Added exit log process.exit(0); } }; apiServer.close((err) => { if (err) { - // Minimal error handling + console.error("Error closing API server:", err); // Added error log } else { - // Minimal close handling + console.log("API server closed."); // Added close log } apiClosed = true; tryExit(); @@ -160,24 +187,30 @@ const gracefulShutdown = (signal: string) => { // @ts-ignore - Assuming close method exists based on common patterns if (typeof guacServer !== 'undefined' && guacServer && typeof guacServer.close === 'function') { + console.log("Closing Guacamole server..."); // Added close log // @ts-ignore guacServer.close(() => { + console.log("Guacamole server closed."); // Added close log guacClosed = true; tryExit(); }); } else { + console.log("Guacamole server not running or doesn't support close()."); // Added info log guacClosed = true; tryExit(); } + // Force exit after timeout setTimeout(() => { + console.error("Graceful shutdown timed out. Forcing exit."); // Added timeout log process.exit(1); - }, 10000); + }, 10000); // 10 seconds timeout }; process.on('SIGINT', () => gracefulShutdown('SIGINT')); process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); process.on('SIGUSR2', () => { + // Handle nodemon restarts gracefully gracefulShutdown('SIGUSR2 (nodemon restart)'); }); \ No newline at end of file