[api] Allow for "secure" option to be passed to nconf.stores.File
to perform content encryption / decryption with crypto.createCipher
.
This commit is contained in:
parent
4c07028e40
commit
2de2bc0b66
1 changed files with 83 additions and 20 deletions
|
@ -30,7 +30,21 @@ var File = exports.File = function (options) {
|
|||
this.file = options.file;
|
||||
this.dir = options.dir || process.cwd();
|
||||
this.format = options.format || formats.json;
|
||||
this.json_spacing = options.json_spacing || 2;
|
||||
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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue