Skip to content

Commit

Permalink
[api test] Encrypt individual keys instead of entire stringified cont…
Browse files Browse the repository at this point in the history
…ents. Added basic unit tests.
  • Loading branch information
indexzero committed Sep 19, 2015
1 parent d2b3561 commit 04c0f3a
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 52 deletions.
110 changes: 58 additions & 52 deletions lib/nconf/stores/file.js
Expand Up @@ -5,7 +5,8 @@
*
*/

var fs = require('fs'),
var crypto = require('crypto'),
fs = require('fs'),
path = require('path'),
util = require('util'),
formats = require('../formats'),
Expand Down Expand Up @@ -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);
};

//
Expand All @@ -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;
};

Expand Down Expand Up @@ -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));
Expand All @@ -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 = {};
if (!existsSync(this.file)) {
this.store = {};
return this.store;
}
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);
//
// 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 data;
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;
}, {});
};

//
Expand Down
23 changes: 23 additions & 0 deletions test/stores/file-store-test.js
Expand Up @@ -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);

0 comments on commit 04c0f3a

Please sign in to comment.