From 04c0f3a0013a65eefd59c5fca547989050f39d85 Mon Sep 17 00:00:00 2001 From: indexzero Date: Sat, 19 Sep 2015 00:12:50 -0700 Subject: [PATCH] [api test] Encrypt individual keys instead of entire stringified contents. Added basic unit tests. --- lib/nconf/stores/file.js | 116 +++++++++++++++++---------------- test/stores/file-store-test.js | 23 +++++++ 2 files changed, 84 insertions(+), 55 deletions(-) diff --git a/lib/nconf/stores/file.js b/lib/nconf/stores/file.js index 2af56c8..d3de098 100644 --- a/lib/nconf/stores/file.js +++ b/lib/nconf/stores/file.js @@ -5,7 +5,8 @@ * */ -var fs = require('fs'), +var crypto = require('crypto'), + fs = require('fs'), path = require('path'), util = require('util'), formats = require('../formats'), @@ -71,10 +72,7 @@ File.prototype.save = function (value, callback) { value = null; } - var contents = this.format.stringify(this.store, null, this.spacing), - bytes = this.tryEncrypt(contents); - - fs.writeFile(this.file, bytes, callback); + fs.writeFile(this.file, this.stringify(), callback); }; // @@ -85,10 +83,7 @@ File.prototype.save = function (value, callback) { // using the format specified by `this.format` synchronously. // File.prototype.saveSync = function (value) { - var contents = this.format.stringify(this.store, null, this.spacing), - bytes = this.tryEncrypt(contents); - - fs.writeFileSync(this.file, bytes); + fs.writeFileSync(this.file, this.stringify()); return this.store; }; @@ -120,8 +115,7 @@ File.prototype.load = function (callback) { stringData = stringData.substr(1); } - var contents = self.tryDecrypt(stringData); - self.store = self.format.parse(contents); + self.store = self.parse(stringData); } catch (ex) { return callback(new Error("Error parsing your configuration file: [" + self.file + ']: ' + ex.message)); @@ -138,66 +132,78 @@ File.prototype.load = function (callback) { // and responds appropriately. // File.prototype.loadSync = function () { - var data, self = this; - - if (!existsSync(self.file)) { - self.store = {}; - data = {}; - } - else { - // - // Else, the path exists, read it from disk - // - try { - // Deals with file that include BOM - var fileData = fs.readFileSync(this.file, 'utf8'); - if (fileData.charAt(0) === '\uFEFF') { - fileData = fileData.substr(1); - } - - data = this.format.parse(fileData); - this.store = data; - } - catch (ex) { - throw new Error("Error parsing your configuration file: [" + self.file + ']: ' + ex.message); - } + if (!existsSync(this.file)) { + this.store = {}; + return this.store; } - return data; + // + // Else, the path exists, read it from disk + // + try { + // Deals with file that include BOM + var fileData = fs.readFileSync(this.file, 'utf8'); + if (fileData.charAt(0) === '\uFEFF') { + fileData = fileData.substr(1); + } + + this.store = this.parse(fileData); + } + catch (ex) { + throw new Error("Error parsing your configuration file: [" + this.file + ']: ' + ex.message); + } + + return this.store; }; // -// ### function tryEncrypt () +// ### function stringify () // Returns an encrypted version of the contents IIF // `this.secure` is enabled // -File.prototype.tryEncrypt = function (contents) { - if (!this.secure) { return contents; } +File.prototype.stringify = function () { + var data = this.store, + self = this; - // - // Contents have already been stringified by the format - // so no need to re-stringify here. - // - return cipherConvert(contents, { - alg: this.secure.alg, - secret: this.secure.secret, - encs: { input: 'utf8', output: 'hex' } - }); + if (this.secure) { + data = Object.keys(data).reduce(function (acc, key) { + var value = self.format.stringify(data[key]); + acc[key] = cipherConvert(value, { + alg: self.secure.alg, + secret: self.secure.secret, + encs: { input: 'utf8', output: 'hex' } + }); + + return acc; + }, {}); + } + + return this.format.stringify(data, null, this.spacing); }; // -// ### function tryDecrypt (contents) +// ### function parse (contents) // Returns a decrypted version of the contents IFF // `this.secure` is enabled. // -File.prototype.tryDecrypt = function (contents) { - if (!this.secure) { return contents; } +File.prototype.parse = function (contents) { + var parsed = this.format.parse(contents), + self = this; - return cipherConvert(contents, { - alg: this.secure.alg, - secret: this.secure.secret, - encs: { input: 'hex', output: 'utf8' } - }); + if (!this.secure) { + return parsed; + } + + return Object.keys(parsed).reduce(function (acc, key) { + var decrypted = cipherConvert(parsed[key], { + alg: self.secure.alg, + secret: self.secure.secret, + encs: { input: 'hex', output: 'utf8' } + }); + + acc[key] = self.format.parse(decrypted); + return acc; + }, {}); }; // diff --git a/test/stores/file-store-test.js b/test/stores/file-store-test.js index 6a17290..6ce9818 100644 --- a/test/stores/file-store-test.js +++ b/test/stores/file-store-test.js @@ -222,5 +222,28 @@ vows.describe('nconf/stores/file').addBatch({ } } } +}).addBatch({ + "When using the nconf file store": { + topic: function () { + var secureStore = new nconf.File({ + file: 'mock-file-path.json', + secure: 'super-secretzzz' + }); + + secureStore.store = data; + return secureStore; + }, + "the stringify() method should encrypt properly": function (store) { + var contents = JSON.parse(store.stringify()); + Object.keys(data).forEach(function (key) { + assert.isString(contents[key]); + }); + }, + "the parse() method should decrypt properly": function (store) { + var contents = store.stringify(); + var parsed = store.parse(contents); + assert.deepEqual(parsed, data); + } + } }).export(module);