[api] Allow for "secure" option to be passed to `nconf.stores.File` to perform content encryption / decryption with `crypto.createCipher`.

master
indexzero 2015-09-18 20:40:53 -07:00
parent 4c07028e40
commit 2de2bc0b66
1 changed files with 83 additions and 20 deletions

View File

@ -26,11 +26,25 @@ var File = exports.File = function (options) {
Memory.call(this, options);
this.type = 'file';
this.file = options.file;
this.dir = options.dir || process.cwd();
this.format = options.format || formats.json;
this.json_spacing = options.json_spacing || 2;
this.type = 'file';
this.file = options.file;
this.dir = options.dir || process.cwd();
this.format = options.format || formats.json;
this.secure = options.secure;
this.spacing = options.json_spacing
|| options.spacing
|| 2;
if (this.secure) {
this.secure.alg = this.secure.alg || 'aes-256-ctr';
if (this.secure.secretPath) {
this.secret = fs.readFileSync(this.secure.secretPath, 'utf8');
}
if (!this.secure.secret) {
throw new Error('secure.secret option is required');
}
}
if (options.search) {
this.search(this.dir);
@ -53,9 +67,10 @@ File.prototype.save = function (value, callback) {
value = null;
}
fs.writeFile(this.file, this.format.stringify(this.store, null, this.json_spacing), function (err) {
return err ? callback(err) : callback();
});
var contents = this.format.stringify(this.store, null, this.spacing),
bytes = this.tryEncrypt(contents);
fs.writeFile(this.file, bytes, callback);
};
//
@ -66,12 +81,10 @@ File.prototype.save = function (value, callback) {
// using the format specified by `this.format` synchronously.
//
File.prototype.saveSync = function (value) {
try {
fs.writeFileSync(this.file, this.format.stringify(this.store, null, this.json_spacing));
}
catch (ex) {
throw(ex);
}
var contents = this.format.stringify(this.store, null, this.spacing),
bytes = this.tryEncrypt(contents);
fs.writeFileSync(this.file, bytes);
return this.store;
};
@ -97,12 +110,14 @@ File.prototype.load = function (callback) {
}
try {
//deals with string that include BOM
// Deals with string that include BOM
var stringData = data.toString();
if (stringData.charAt(0) === '\uFEFF') {
stringData = stringData.substr(1);
}
if (stringData.charAt(0) === '\uFEFF') stringData = stringData.substr(1);
self.store = self.format.parse(stringData);
var contents = self.tryDecrypt(stringData);
self.store = self.format.parse(contents);
}
catch (ex) {
return callback(new Error("Error parsing your configuration file: [" + self.file + ']: ' + ex.message));
@ -130,9 +145,11 @@ File.prototype.loadSync = function () {
// Else, the path exists, read it from disk
//
try {
//deals with file that include BOM
// Deals with file that include BOM
var fileData = fs.readFileSync(this.file, 'utf8');
if (fileData.charAt(0) === '\uFEFF') fileData = fileData.substr(1);
if (fileData.charAt(0) === '\uFEFF') {
fileData = fileData.substr(1);
}
data = this.format.parse(fileData);
this.store = data;
@ -145,6 +162,40 @@ File.prototype.loadSync = function () {
return data;
};
//
// ### function tryEncrypt ()
// Returns an encrypted version of the contents IIF
// `this.secure` is enabled
//
File.prototype.tryEncrypt = function (contents) {
if (!this.secure) { return contents; }
//
// 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' }
});
};
//
// ### function tryDecrypt (contents)
// Returns a decrypted version of the contents IFF
// `this.secure` is enabled.
//
File.prototype.tryDecrypt = function (contents) {
if (!this.secure) { return contents; }
return cipherConvert(contents, {
alg: this.secure.alg,
secret: this.secure.secret,
encs: { input: 'hex', output: 'utf8' }
});
};
//
// ### function search (base)
// #### @base {string} Base directory (or file) to begin searching for the target file.
@ -235,3 +286,15 @@ File.prototype.search = function (base) {
return fullpath;
};
//
// ### function cipherConvert (contents, opts)
// Returns the result of the cipher operation
// on the contents contents.
//
function cipherConvert(contents, opts) {
var encs = opts.encs;
var cipher = crypto.createCipher(opts.alg, opts.secret);
return cipher.update(contents, encs.input, encs.output)
+ cipher.final(encs.output);
}