cscg24-guacamole

CSCG 2024 Challenge 'Guacamole Mashup'
git clone https://git.sinitax.com/sinitax/cscg24-guacamole
Log | Files | Refs | sfeed.txt

ClientConnection.js (4783B)


      1const Url = require('url');
      2const DeepExtend = require('deep-extend');
      3const Moment = require('moment');
      4
      5const GuacdClient = require('./GuacdClient.js');
      6const Crypt = require('./Crypt.js');
      7
      8class ClientConnection {
      9
     10    constructor(server, connectionId, webSocket) {
     11        this.STATE_OPEN = 1;
     12        this.STATE_CLOSED = 2;
     13
     14        this.state = this.STATE_OPEN;
     15
     16        this.server = server;
     17        this.connectionId = connectionId;
     18        this.webSocket = webSocket;
     19        this.query = Url.parse(this.webSocket.upgradeReq.url, true).query;
     20        this.lastActivity = Date.now();
     21        this.activityCheckInterval = null;
     22
     23        this.log(this.server.LOGLEVEL.VERBOSE, 'Client connection open');
     24
     25        try {
     26            this.connectionSettings = this.decryptToken();
     27
     28            this.connectionType = this.connectionSettings.connection.type;
     29
     30            this.connectionSettings['connection'] = this.mergeConnectionOptions();
     31
     32        } catch (error) {
     33            this.log(this.server.LOGLEVEL.ERRORS, 'Token validation failed');
     34            this.close(error);
     35            return;
     36        }
     37
     38        server.callbacks.processConnectionSettings(this.connectionSettings, (err, settings) => {
     39            if (err) {
     40                return this.close(err);
     41            }
     42
     43            this.connectionSettings = settings;
     44
     45            this.log(this.server.LOGLEVEL.VERBOSE, 'Opening guacd connection');
     46
     47            this.guacdClient = new GuacdClient(server, this);
     48
     49            webSocket.on('close', this.close.bind(this));
     50            webSocket.on('message', this.processReceivedMessage.bind(this));
     51
     52            if (server.clientOptions.maxInactivityTime > 0) {
     53                this.activityCheckInterval = setInterval(this.checkActivity.bind(this), 1000);
     54            }
     55        });
     56
     57    }
     58
     59    decryptToken() {
     60        const crypt = new Crypt(this.server);
     61
     62        const encrypted = this.query.token;
     63        delete this.query.token;
     64
     65        return crypt.decrypt(encrypted);
     66    }
     67
     68    log(level, ...args) {
     69        if (level > this.server.clientOptions.log.level) {
     70            return;
     71        }
     72
     73        const stdLogFunc = this.server.clientOptions.log.stdLog;
     74        const errorLogFunc = this.server.clientOptions.log.errorLog;
     75
     76        let logFunc = stdLogFunc;
     77        if (level === this.server.LOGLEVEL.ERRORS) {
     78            logFunc = errorLogFunc;
     79        }
     80
     81        logFunc(this.getLogPrefix(), ...args);
     82    }
     83
     84    getLogPrefix() {
     85        return '[' + Moment().format('YYYY-MM-DD HH:mm:ss') + '] [Connection ' + this.connectionId + '] ';
     86    }
     87
     88    close(error) {
     89        if (this.state == this.STATE_CLOSED) {
     90            return;
     91        }
     92
     93        if (this.activityCheckInterval !== undefined && this.activityCheckInterval !== null) {
     94            clearInterval(this.activityCheckInterval);
     95        }
     96
     97        if (error) {
     98            this.log(this.server.LOGLEVEL.ERRORS, 'Closing connection with error: ', error);
     99        }
    100
    101        if (this.guacdClient) {
    102            this.guacdClient.close();
    103        }
    104
    105        this.webSocket.removeAllListeners('close');
    106        this.webSocket.close();
    107        this.server.activeConnections.delete(this.connectionId);
    108
    109        this.state = this.STATE_CLOSED;
    110
    111        this.log(this.server.LOGLEVEL.VERBOSE, 'Client connection closed');
    112    }
    113
    114    error(error) {
    115        this.server.emit('error', this, error);
    116        this.close(error);
    117    }
    118
    119    processReceivedMessage(message) {
    120        this.lastActivity = Date.now();
    121        this.guacdClient.send(message);
    122    }
    123
    124    send(message) {
    125        if (this.state == this.STATE_CLOSED) {
    126            return;
    127        }
    128
    129        this.log(this.server.LOGLEVEL.DEBUG, '>>>G2W> ' + message + '###');
    130        this.webSocket.send(message, {binary: false, mask: false}, (error) => {
    131            if (error) {
    132                this.close(error);
    133            }
    134        });
    135    }
    136
    137    mergeConnectionOptions() {
    138        let unencryptedConnectionSettings = {};
    139
    140        Object
    141            .keys(this.query)
    142            .filter(key => this.server.clientOptions.allowedUnencryptedConnectionSettings[this.connectionType].includes(key))
    143            .forEach(key => unencryptedConnectionSettings[key] = this.query[key]);
    144
    145        let compiledSettings = {};
    146
    147        DeepExtend(
    148            compiledSettings,
    149            this.server.clientOptions.connectionDefaultSettings[this.connectionType],
    150            this.connectionSettings.connection.settings,
    151            unencryptedConnectionSettings
    152        );
    153
    154        return compiledSettings;
    155    }
    156
    157    checkActivity() {
    158        if (Date.now() > (this.lastActivity + this.server.clientOptions.maxInactivityTime)) {
    159            this.close(new Error('WS was inactive for too long'));
    160        }
    161    }
    162
    163
    164}
    165
    166module.exports = ClientConnection;