${safeHost}
Opening a browser RDP session to ${safeAddress} using the assigned host credential. Close this tab to end the session.
import { createRequire } from 'node:module'; import { randomBytes } from 'node:crypto'; import { path } from '../utils/pathUtils.js'; import express from 'express'; import { db, visibleClause } from '../db.js'; import { decryptSecret } from './cryptoStore.js'; import { logger } from './logger.js'; import { normalizeOsFamily } from '../utils/osFamily.js'; const require = createRequire(import.meta.url); const rdp = require('node-rdpjs'); const socketIo = require('socket.io'); const mstscPackagePath = require.resolve('mstsc.js/package.json'); const mstscClientDir = path.join(path.dirname(mstscPackagePath), 'client'); const SESSION_TTL_MS = 5 * 60 * 1000; const sessions = new Map(); let socketServer = null; function token() { return randomBytes(24).toString('base64url'); } function escapeHtml(value = '') { return String(value) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function splitDomainUsername(username = '') { const value = String(username || ''); const match = value.match(/^([^\\]+)\\(.+)$/); if (!match) return { domain: '', username: value }; return { domain: match[1], username: match[2] }; } function cleanupExpiredSessions() { const cutoff = Date.now() - SESSION_TTL_MS; for (const [sessionToken, session] of sessions.entries()) { if (session.createdAt < cutoff) sessions.delete(sessionToken); } } function loadRdpTarget(hostId, userId) { return db.prepare(` SELECT h.*, c.name AS credential_name, c.kind AS credential_kind, c.username AS credential_username, c.secret_cipher, c.secret_iv, c.secret_tag FROM hosts h JOIN credentials c ON c.id = h.credential_id WHERE h.id = ? AND ${visibleClause('h')} AND ${visibleClause('c')} `).get(hostId, userId, userId, userId, userId); } export function createRdpLaunchSession(hostId, userId) { cleanupExpiredSessions(); const target = loadRdpTarget(hostId, userId); if (!target) { const error = new Error('Windows host with an assigned visible credential is required for RDP launch.'); error.statusCode = 404; throw error; } if (normalizeOsFamily(target.os_family, 'other') !== 'windows') { const error = new Error('RDP launch is only available for Windows hosts.'); error.statusCode = 400; throw error; } if (target.credential_kind !== 'username_password') { const error = new Error('RDP launch requires a username/password credential assigned to the host.'); error.statusCode = 400; throw error; } const password = decryptSecret(target); if (!password) { const error = new Error('Assigned host credential does not have a decryptable password.'); error.statusCode = 400; throw error; } const sessionToken = token(); const parsedUsername = splitDomainUsername(target.credential_username); const session = { token: sessionToken, userId, hostId: target.id, hostName: target.name, address: target.fqdn || target.address, port: 3389, domain: parsedUsername.domain, username: parsedUsername.username, password, credentialName: target.credential_name, createdAt: Date.now() }; sessions.set(sessionToken, session); logger.info('rdp session created', { hostId: target.id, hostName: target.name, userId, credentialId: target.credential_id }); return { token: sessionToken, url: `/rdp/${sessionToken}`, expiresInSeconds: Math.floor(SESSION_TTL_MS / 1000), host: { id: target.id, name: target.name, address: session.address } }; } function renderSessionPage(session) { const safeToken = escapeHtml(session.token); const safeHost = escapeHtml(session.hostName); const safeAddress = escapeHtml(session.address); return `
Opening a browser RDP session to ${safeAddress} using the assigned host credential. Close this tab to end the session.