Posts Tagged: ‘ES6’

node.js, domino-db & Docker (10): Protecting Proton Keys

12. November 2018 Posted by Sven Hasselbach

Before we are looking into the details how to setup a non-anynomous connection to Domino’s Proton server, I have an advice for protecting the key files required for the connection.

The keys are not password protected, and this is a high risk which should be avoided: First, if you make a mistake or if there is a bug in the software, the keys could be accessed from „outside“. And second, if you check in your code in a repository, the IT security should roast you. It’s clear that the keys are still unprotected if someone hacks the application, but this is a different topic.

First thing to to is to encrypt the keys with AES. To achive this, we are using openssl:

openssl enc -aes-256-cbc -k "0123456789" -in domino-express.key -out domino-express.key.enc

The parameter -k is the key to use for encryption. The path to the unencrypted file is passed with -in, and -out is the path of the encrypted file.

I also encryted the .crt files, which is not required, but it does not hurt either.

The config.js file must now be changed to the follwing (a new method for reading and decrypting the files with openssl is added):

const exec = require('child_process').execSync;

const path = require('path');

/**
 * reads an aes-256-cbc encrypted file & decrypts it
 * 
 * @param {*} fileName 
 *  the encrpyted file 
 * @param {*} key 
 *  the decryption key
 */
const readFileEncrypted = (fileName, key) => {
  try {
    // resolve the filepath
    const filePath = path.resolve(fileName);

    // use openssl to decrypt the keys
    return exec(`openssl enc -d -aes-256-cbc -k "${key}" -in "${filePath}"`, 
    (error, stdout) => {
      if (error) {
        console.error(`exec error: ${error}`);
      }
      return stdout;
    });
  } catch (error) {
    console.error(error);
    return undefined;
  }
};

// the password (stored in environment)
const password = process.env.PASSWORD_KEYFILES;
if (!password) {
  console.error('Environment Variable "PASSWORD_KEYFILES" is not set.');
  process.exit(1);
}

// load the keys & certificates
const rootCertificate = readFileEncrypted('./app/certs/ca.crt.enc', password);
const clientCertificate = readFileEncrypted('./app/certs/domino-express.crt.enc', password);
const clientKey = readFileEncrypted('./app/certs/domino-express.key.enc', password);

// check if the keys are ok 
if (!rootCertificate || !clientCertificate || !clientKey) {
  console.error('Unable to load certificates and/or key.');
  process.exit(1);
}

const Config = {
    serverConfig: {
        hostName: process.env.NODE_ENV === 'development' ? 'dev.example.com' : 'example.com', // Host name of your server
        connection: {
          port: '3002', // Proton port on your server
          secure: true,
        }, 
        credentials: {
            rootCertificate,
            clientCertificate,
            clientKey,
          },
      },
      databaseConfig: {
        filePath: 'testnode.nsf', // The database file name
      }
};

module.exports = Config;

But where to store the key? Nowhere, the key is stored in an evironmental variable.

This line reads the value from the variable PASSWORD_KEYFILES.

const password = process.env.PASSWORD_KEYFILES;

When running your application in a docker container, just start the container with the following command including the variable:

docker run -e "PASSWORD_KEYFILES=0123456789" --name dominoexpress -p 3000:3000 -d -it shasselba/domino-express

To use it in a IDE like Visual Studio Code, you have to add it to the debugging configuration:

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "Programm starten",
            "program": "${workspaceFolder}/app/bin/www",
            "env": {"PASSWORD_KEYFILES": "0123456789"}
        },
    ]
}

Now you can add the .enc files to your application in folder /app/certs without worrying about it. If the variable is not set, the application terminates with error code 1.

P.S. Don’t forget to remove the original key files from your project!

node.js, domino-db & Docker (9): Global Configurations

9. November 2018 Posted by Sven Hasselbach

The database configuration should not be changing during the different requests, that’s why it is a good idea to store the configuration in a central place of our express application. There are multiple ways of doing this, e.g. you can use app.locals for this. Or you can use the app.set method.

But the best option in my eyes is to have a global configuration file, because using the methods above requires access to the app object (see below how this works).

Using a global configuration file

1. Create a new file in the /app folder named config.js

2. Add a global configuration object

const Config = {};

module.exports = Config;

3. Add the configuration for our database

const Config = {
    serverConfig: {
        hostName: process.env.NODE_ENV === 'development' ? 'dev.example.com' : 'www.example.com', // Host name of your server
        connection: {
          port: '3002', // Proton port on your server
        },
      },
      databaseConfig: {
        filePath: 'node-demo.nsf', // The database file name
      }
};

module.exports = Config;

The configuration above checks the environment variables. In development mode, the dev.example.com server is used, otherwise www.example.com.

4. Import the file in your code

const config = require('../config');

5. Use it by deconstructing the values

const { serverConfig, databaseConfig } = config;

This can be used everywhere in your application.

Using app.set

1. Open the file app.js in the /app folder

2. Add the following code before the module.export statement:

app.set('db', {
  serverConfig: {
    hostName: 'your.server.com', // Host name of your server
    connection: {
      port: '3002', // Proton port on your server
    },
  },
  databaseConfig: {
    filePath: 'node-demo.nsf', // The database file name
  }
});

3. If you want to have different configurations depending of development / productive environment, you can compute the property like this:

...
hostName: app.get('env') === 'development' ? 'your.devserver.com' : 'your.server.com',
...

4. To use the configuration in the router, just use the app.get method (provided by the request object) and deconstruct the values:

router.get('/', (req, res) => {
  const { serverConfig, databaseConfig } = req.app.get('db');
  ...

node.js, domino-db & Docker (7): The ValueHolder

7. November 2018 Posted by Sven Hasselbach

I am using this for years in Java, so I thought it would be great to use this approach also in the JavaScript world: The ValueHolder. The class allows to easily define „cachable“ code and it’s result, without having to handle the memcached part and – maybe in the future – background processing stuff.

To give you an idea what it is for here is a small example:

const allDummyDocs = new ValueHolder('allDummyDocs', 60, async () => {
  // get all documents with the Form 'dummy'
  return useServer(serverConfig).then(
    async server => {
      const db = await server.useDatabase(databaseConfig);
      const response = await db.bulkReadDocuments({
        query: "Form = 'dummy'"
      });
      return JSON.stringify(response);
    }).catch(err => {
      console.log(err);
      return err;
    });
});

The first parameter is the key used to store/retreive the value from memcached. The second one is the time how long the value should be cached. And the third parameter is the code to execute.

To use the definition in the application, you now have to use the get method of the value holder:

router.get('/showAllDummyDocs', (req, res) => {
  allDummyDocs.get(
      (error, result) => {
        if (error) {
          res.render('error', { title: 'Error', error });
        } else {
          res.render('index', { title: 'Express', result: `Result: ${result}` });
        }
      }
    );
  }
);

The ValueHolder checks now automatically, if the result is stored in the cache. If not, the code is executed and stored in the cache.

Here is the ValueHolder.js file (which has to be created in the /app/classes folder):

const mf = require('../classes/MemcachedFactory');

const nullHelper = '###NULL###';
/**
 * Helper class for cached values
 * 
 * @author Sven Hasselbach
 * @version 0.1
 */
class ValueHolder {

    /**
     * 
     * @param {string} key 
     *  unique identifier
     * @param {number} ttl
     *  time-to-live in seconds 
     * @param {function} code 
     *  code to execute to calculate the value
     */
    constructor(key, ttl, code) {
        this.key = key;
        this.code = code;
        this.ttl = ttl;
    }

    /**
     * loads the value from cache or 
     * computes it and stores it in the cache
     * 
     * @param {function} callback 
     * @returns Promise
     */
    async get(callback) {
        const { code, ttl, key } = this;
    
        // check if value is in cache...
        mf.getInstance().get(key, (error, value) => {
            if (error) {
                callback(error);
                return;
            }
            if (value != null) {
                console.debug(`Found '${key}' in cache.`);
                if (value === nullHelper) {
                    // result is "special", so let's return null
                    callback(error, null);
                } else {
                    console.log(value);
                    callback(error, JSON.parse(value));
                }
            } else {
                console.debug(`Computing '${key}' and adding to cache with ttl ${ttl}.`);

                // execute the computation
                code().then((result) => {
                    // check if result must be stored "special" or not
                    if (result === null) {
                        mf.getInstance().set(key, nullHelper, ttl);
                    } else {
                        mf.getInstance().set(key, JSON.stringify(result), ttl);
                    }   
                    callback(error, result);
                });
            }
        });
     }

}

module.exports = ValueHolder;

node.js, domino-db & Docker (6): Using memcached

7. November 2018 Posted by Sven Hasselbach

mem.js

I am using mem.js as client library for accessing memcached. To use it, the first thing to do is to add the requirement to your package.json:

npm install memjs --save

MemcachedFactory

Then we can create a simple helper class to have an abstraction layer between our code and the library itself.

1. Create a folder in /app named classes

2. Create a new file with the name MemcachedFactory.js

3. Add the following code:

const memjs = require('memjs');
/**
 * Helper class for using Memcache
 * 
 * @author Sven Hasselbach
 * @version 0.1
 */
class MemcachedFactory {

    constructor() {
      this.client = memjs.Client.create('127.0.0.1:11211');
    }

    /**
     * returns a single instance of the class
     */
    static getInstance() {
      if (this.instance == null) {
        this.instance = new MemcachedFactory();
      }
      return this.instance;
    }

    /**
     * stores a value in memcache
     * @param {string} key 
     *  the key used
     * @param {*} value
     *  the value to store
     * @param {number} ttl 
     *  time-to-live in seconds
     */
    set(key, value, ttl) {
      this.client.set(key, value, { expires: ttl }, err => {
        if (err) {
          console.log(err);
          throw err;
        }
      });
    }
    /**
     * gets a value from memcache
     * 
     * @param {string} key 
     *  the key used
     * @param {function} callback
     *  the callback containing the value
     */
    get(key, callback) {
      this.client.get(key, (err, value) => {
        if (err) {
          console.error(err);
          callback(err);
        }
        if (value == null) {
          callback(err, null);
        } else {
          callback(err, value.toString());
        }
      });
  }
}
module.exports = MemcachedFactory;

5. To use the class in our code, we have to add the requirement first:

const mf = require('../classes/MemcachedFactory');

6. Here is a small example how the class is used:

mf.getInstance().get(key, (error, value) => {
  if (error) {
    // handle error here
  }else{
    // we have a value
    console.log(`The value is ${value}`); 
  }
});

The getInstance method returns an instance of the class. Then the get method is used with the key to retreive, and a callback method which is called when the request to memcached is completed.

The set method allows to put a key to memcached, and with ttl we can define how long the key is valid and stored.