npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@hurdlegroup/guacamole-lite

v0.14.1

Published

Standalone WebSocket Guacamole server in node.js. RDP/VNC/SSH client for HTML5 browsers

Downloads

31

Readme

Builld CodeQL

guacws

This is a fork of the hard work done by Vadim Pronin https://github.com/vadimpronin/guacws

Synopsis

guacws is a NodeJS replacement for guacamole-client (server-side Java servlet). Guacamole is a RDP/VNC client for HTML5 browsers.

This is the best solution for those ones who need to integrate Guacamole into an existing projects with their own users and connections management (or without them at all).

This diagram describes the architecture of Guacamole and the role of guacws in it: arch

Installation

npm install --save @hurdlegroup/guacamole-lite

Code Example

Simple example which accepts connections to port 8080 and forwards all traffic to guacd on port 4822

#!/usr/bin/env node

const GuacamoleLite = require('guacws');

const websocketOptions = {
    port: 8080 // we will accept connections to this port
};

const guacdOptions = {
    port: 4822 // port of guacd
};

const clientOptions = {
    crypt: {
        cypher: 'AES-256-CBC',
        key: 'MySuperSecretKeyForParamsToken12'
    }
};

const guacServer = new GuacamoleLite(websocketOptions, guacdOptions, clientOptions);

Now to connect to guacws from the browser you need to add guacamole-common-js into your page. Please refer to Chapter 17 of Guacamole documentation for instructions on how to do it.

Then you need to open guacamole connection to

ws://your-guacamole-server:8080/?token=token

where token is an encrypted token object (json) containing all the parameters needed to establish connection (host ip, login, password, connection type, etc). Here is an example of what it can contain:


{
    "connection": {
        "type": "rdp",
        "settings": {
            "hostname": "10.0.0.12",
            "username": "Administrator",
            "password": "pAsSwOrD",
            "enable-drive": true,
            "create-drive-path": true,
            "security": "any",
            "ignore-cert": true,
            "enable-wallpaper": false
        }
    }
}

As seen in the example token object must contain property connection which in it's turn must contain type (rdp, vnc, ssh, telnet) and settings. For full list of settings and their meaning please refer to Chapter 5 of Guacamole documentation section Configuring connections).

Token object may contain any additional parameters you may need in your application. For example it can contain token expiration time (see below how to make use of it).

Now to get the token we need to encrypt and base64-encode this token object using cyper and key from clientOptions. This is an example how to do it in PHP:

<?php

function encryptToken($value)
{
    $iv = random_bytes(16);

    $value = \openssl_encrypt(
        json_encode($value),
        'AES-256-CBC',
        'MySuperSecretKeyForParamsToken12',
        0,
        $iv
    );

    if ($value === false) {
        throw new \Exception('Could not encrypt the data.');
    }

    $data = [
        'iv' => base64_encode($iv),
        'value' => $value,
    ];

    $json = json_encode($data);

    if (!is_string($json)) {
        throw new \Exception('Could not encrypt the data.');
    }

    return base64_encode($json);
}

another example in NodeJS:

const crypto = require('crypto');

const clientOptions = {
    cypher: 'AES-256-CBC',
    key: 'MySuperSecretKeyForParamsToken12'
}

const encrypt = (value) => {
    const iv = crypto.randomBytes(16);
    const cipher = crypto.createCipheriv(clientOptions.cypher, clientOptions.key, iv);

    let crypted = cipher.update(JSON.stringify(value), 'utf8', 'base64');
    crypted += cipher.final('base64');

    const data = {
        iv: iv.toString('base64'),
        value: crypted
    };

    return new Buffer(JSON.stringify(data)).toString('base64');
};

In other words here's what you'd want to do to get the encrypted token:

  1. Generate initialization vector for encryption (iv).
  2. Take json object with connection settings (see example above) and encrypt it using cyper and key from clientOptions.
  3. Base64 encode result of p.2 (put it in value)
  4. Create anothe json object containing {"iv": iv, "value": value}
  5. Base64 encode result of p.4 and this will be your token

More examples

Websockets and guacd configuration

websocketOptions object is passed directly to ws library. Please refer to ws documentation for more options.

guacdOptions object may contain port and host properties which are passed to node's net.connect() function.

Default connection options

You don't necessary need to pass all connection parameters in the token. You can omit settings that are common for all your connections by moving them to clientOptions.connectionDefaultSettings in guacws server:

#!/usr/bin/env node

const GuacamoleLite = require('guacws');

const clientOptions = {
    crypt: {
        cypher: 'AES-256-CBC',
        key: 'MySuperSecretKeyForParamsToken12'
    },

    connectionDefaultSettings: {
        rdp: {
            'create-drive-path': true,
            'security': 'any',
            'ignore-cert': true,
            'enable-wallpaper': false,
            'create-recording-path': true
        }
    }

};

const guacServer = new GuacamoleLite({}, {}, clientOptions);

Query parameters

Some connection options can be modified in the query:

ws://your-guacamole-server:8080/?token=token&width=1024&height=768&dpi=32

Settings from the query override default settings and settings from the token. By default only width, height and dpi can be set in query. Others are ignored. The list of whitelisted parameters can be modified in clientOptions:

#!/usr/bin/env node

const GuacamoleLite = require('guacws');

const clientOptions = {
    crypt: {
        cypher: 'AES-256-CBC',
        key: 'MySuperSecretKeyForParamsToken12'
    },
    allowedUnencryptedConnectionSettings: {
        rdp: [
            'width',
            'height',
            'dpi',
            'create-drive-path'
        ]
    }
};

const guacServer = new GuacamoleLite({}, {}, clientOptions);

Callbacks

You may need to validate/modify connection parameters after the connection was established.

For this example we will modify token object the following way:


{
    "expiration": 3510738000000,
    "userId": 777,
    "connection": {
        "type": "rdp",
        "settings": {
            "hostname": "10.0.0.12",
            "username": "Administrator",
            "password": "pAsSwOrD",
            "enable-drive": true
        }
    }
}

As you see we have added expiration and userId which are not used by guacws itself, buy may be used by your application built on top of it. Like in this example:

#!/usr/bin/env node

const GuacamoleLite = require('guacws');

const clientOptions = {
    crypt: {
        cypher: 'AES-256-CBC',
        key: 'MySuperSecretKeyForParamsToken12'
    },
};

const callbacks = {
    processConnectionSettings: function (settings, callback) {
        if (settings.expiration < Date.now()) {
            console.error('Token expired');

            return callback(new Error('Token expired'));
        }

        settings.connection['drive-path'] = '/tmp/guacamole_' + settings.userId;

        callback(null, settings);
    }
};

const guacServer = new GuacamoleLite({}, {}, clientOptions, callbacks);

In this example we have new object callbacks which contains function processConnectionSettings. This function accepts settings which is basically slightly flattened token object and callback. Callback in it's turn accepts two parameters: err (in case of an error) and settings which is modified token object (we have added 'drive-path' in the example). Callback must be called at the end of the function.

Please note that connection property does not contain rdp, but instead contains everything that was previously in rpd.

Also note the new fourth parameter (callbacks) in the last line with new GuacamoleLite.

Events

guacws also emits the following events:

  • open - when connection to the host is established
  • close - when connection is closed
  • error - when error in connection occured

In this example we will use these events to send postbacks to our backend:

#!/usr/bin/env node

const GuacamoleLite = require('guacws');
const Http = require('http');

const clientOptions = {
    crypt: {
        cypher: 'AES-256-CBC',
        key: 'MySuperSecretKeyForParamsToken12'
    },
};

const guacServer = new GuacamoleLite({}, {}, clientOptions);

guacServer.on('open', (clientConnection) => {
    const url = 'http://our-backend-server/api/connection/open?userId=' + clientConnection.connectionSettings.userId
    
    Http.request(url).end();
});

guacServer.on('close', (clientConnection) => {
    const url = 'http://our-backend-server/api/connection/close?userId=' + clientConnection.connectionSettings.userId
    
    Http.request(url).end();
});

guacServer.on('error', (clientConnection, error) => {
    console.error(clientConnection, error);
});

Note that clientConnection object is passed to all event listeners and can be used to access connectionSettings (which is token object).

ExpressJS example

#!/usr/bin/env node

const GuacamoleLite = require('guacws');
const express = require('express');
const http = require('http');

const app = express();

const server = http.createServer(app);

const guacdOptions = {
    port: 4822 // port of guacd
};

const clientOptions = {
    crypt: {
        cypher: 'AES-256-CBC',
        key: 'MySuperSecretKeyForParamsToken12'
    }
};

const guacServer = new GuacamoleLite({server}, guacdOptions, clientOptions);

server.listen(8080);

Log levels

#!/usr/bin/env node

const GuacamoleLite = require('guacws');

const websocketOptions = {
    port: 8080 // we will accept connections to this port
};

const guacdOptions = {
    port: 4822 // port of guacd
};

const clientOptions = {
    crypt: {
        cypher: 'AES-256-CBC',
        key: 'MySuperSecretKeyForParamsToken12'
    },
    log: {
        level: 'DEBUG'
    }
};

const guacServer = new GuacamoleLite(websocketOptions, guacdOptions, clientOptions);

clientOptions.log.level defines verbosity of logs. Possible values are:

  • "QUIET" - no logs
  • "ERRORS" - only errors
  • "NORMAL" - errors + minimal logs (startup and shutdown messages)
  • "VERBOSE" - (default) normal + connection messages (opened, closed, guacd exchange, etc)
  • "DEBUG" - verbose + all OPCODES sent/received within guacamole sessions

Custom log functions

By default guacws uses console.log and console.error functions for logging. You can redefine these functions by setting clientOptions.log.stdLog and clientOptions.log.errorLog like in the example below. Note that clientOptions.log.level is still applied, which means that messages that don't match desired log level won't be sent to your custom functions

#!/usr/bin/env node

const GuacamoleLite = require('guacws');

const websocketOptions = {
    port: 8080 // we will accept connections to this port
};

const guacdOptions = {
    port: 4822 // port of guacd
};

const clientOptions = {
    crypt: {
        cypher: 'AES-256-CBC',
        key: 'MySuperSecretKeyForParamsToken12'
    },
    log: {
        level: 'DEBUG',
        stdLog: (...args) => {
            console.log('[MyLog]', ...args)
        },
        errorLog: (...args) => {
            console.error('[MyLog]', ...args)
        }
    }
};

const guacServer = new GuacamoleLite(websocketOptions, guacdOptions, clientOptions);

Tests

No tests yet :(