copay/js/plugins/InsightStorage.js

202 lines
5.6 KiB
JavaScript
Raw Normal View History

var request = require('request');
var cryptoUtil = require('../util/crypto');
2014-11-10 20:23:12 -08:00
var bitcore = require('bitcore');
var buffers = require('buffer');
var querystring = require('querystring');
var Identity = require('../models/Identity');
2014-11-10 20:23:12 -08:00
var log = require('../log');
2014-11-04 06:47:38 -08:00
var SEPARATOR = '|';
function InsightStorage(config) {
this.type = 'DB';
2014-11-10 11:32:55 -08:00
this.storeUrl = config.url || 'https://insight.bitpay.com:443/api/email',
this.request = config.request || request;
2014-11-10 17:58:46 -08:00
this.iterations = config.iterations || 1000;
this.salt = config.salt || 'jBbYTj8zTrOt6V';
}
2014-11-10 17:58:46 -08:00
InsightStorage.prototype.init = function() {};
InsightStorage.prototype.setCredentials = function(email, password, opts) {
this.email = email;
this.password = password;
2014-11-04 06:47:38 -08:00
this._cachedKey = null;
};
2014-10-28 11:20:43 -07:00
InsightStorage.prototype.createItem = function(name, value, callback) {
var self = this;
2014-10-28 11:20:43 -07:00
this.getItem(name, function(err, retrieved) {
if (err || !retrieved) {
return self.setItem(name, value, callback);
} else {
return callback('EEXISTS');
}
});
};
function mayBeOldPassword(password) {
// Test for base64
return /^[a-zA-Z0-9\/=\+]+$/.test(password);
}
InsightStorage.prototype.getItem = function(name, callback) {
2014-11-04 06:47:38 -08:00
var passphrase = this.getPassphrase();
var self = this;
2014-11-04 06:47:38 -08:00
this._makeGetRequest(passphrase, name, function(err, body) {
2014-11-11 04:01:31 -08:00
if (err) log.info('[InsightStorage. err]', err);
if (err && err.indexOf('PNOTFOUND') !== -1 && mayBeOldPassword(self.password)) {
2014-11-04 06:47:38 -08:00
return self._brokenGetItem(name, callback);
}
return callback(err, body);
});
};
2014-11-10 20:23:12 -08:00
/* This key need to have DIFFERENT
* settings(salt,iterations) than the kdf for wallet/profile encryption
* in Encrpted*Storage. The user should be able
2014-11-11 04:01:31 -08:00
* to change the settings on config.js to modify salt / iterations
2014-11-10 20:23:12 -08:00
* for encryption, but
2014-11-11 04:01:31 -08:00
* mantain the same key & passphrase. This is why those settings are
2014-11-10 20:23:12 -08:00
* not shared with encryption
2014-11-10 17:58:46 -08:00
*/
InsightStorage.prototype.getKey = function() {
if (!this._cachedKey) {
this._cachedKey = cryptoUtil.kdf(this.password + SEPARATOR + this.email, this.salt, this.iterations);
}
return this._cachedKey;
};
2014-11-04 06:47:38 -08:00
InsightStorage.prototype.getPassphrase = function() {
2014-11-11 04:01:31 -08:00
return bitcore.util.twoSha256(this.getKey()).toString('base64');
};
2014-11-04 06:47:38 -08:00
InsightStorage.prototype._makeGetRequest = function(passphrase, key, callback) {
var authHeader = new buffers.Buffer(this.email + ':' + passphrase).toString('base64');
var retrieveUrl = this.storeUrl + '/retrieve';
2014-11-11 04:01:31 -08:00
var getParams = {
url: retrieveUrl + '?' + querystring.encode({
key: key
}),
headers: {
'Authorization': authHeader
}
};
log.debug('Insight request', getParams);
this.request.get(getParams,
function(err, response, body) {
if (err) {
return callback('Connection error');
}
2014-10-31 07:24:16 -07:00
if (response.statusCode === 403) {
return callback('PNOTFOUND: Profile not found');
}
if (response.statusCode !== 200) {
return callback('Connection error');
}
return callback(null, body);
}
);
};
2014-11-04 06:47:38 -08:00
InsightStorage.prototype._brokenGetItem = function(name, callback) {
var passphrase = this._makeBrokenSecret();
var self = this;
2014-11-10 20:23:12 -08:00
log.debug('using legacy get');
2014-11-04 06:47:38 -08:00
this._makeGetRequest(passphrase, name, function(err, body) {
if (!err) {
2014-11-04 06:47:38 -08:00
return self._changePassphrase(function(err) {
if (err) {
return callback(err);
}
return callback(null, body);
});
}
return callback(err);
});
};
2014-11-04 06:47:38 -08:00
InsightStorage.prototype._makeBrokenSecret = function() {
2014-11-10 17:58:46 -08:00
var key = cryptoUtil.kdf(this.password + this.email, 'mjuBtGybi/4=', 100);
return cryptoUtil.kdf(key, this.password, 100);
2014-11-04 06:47:38 -08:00
};
InsightStorage.prototype._changePassphrase = function(callback) {
var passphrase = this._makeBrokenSecret();
var newPassphrase = this.getPassphrase();
var authHeader = new buffers.Buffer(this.email + ':' + passphrase).toString('base64');
var url = this.storeUrl + '/change_passphrase';
this.request.post({
url: url,
2014-11-10 17:58:46 -08:00
headers: {
'Authorization': authHeader
},
body: querystring.encode({
2014-11-04 06:47:38 -08:00
newPassphrase: newPassphrase
})
}, function(err, response, body) {
if (err) {
return callback('Connection error');
}
if (response.statusCode === 409) {
return callback('BADCREDENTIALS: Invalid username or password');
}
if (response.statusCode !== 200) {
return callback('Unable to store data on insight');
}
return callback();
});
};
InsightStorage.prototype.setItem = function(name, value, callback) {
2014-11-04 06:47:38 -08:00
var passphrase = this.getPassphrase();
var authHeader = new buffers.Buffer(this.email + ':' + passphrase).toString('base64');
2014-11-10 08:09:43 -08:00
var registerUrl = this.storeUrl + '/save';
this.request.post({
url: registerUrl,
2014-11-10 17:58:46 -08:00
headers: {
'Authorization': authHeader
},
body: querystring.encode({
key: name,
record: value
})
}, function(err, response, body) {
if (err) {
return callback('Connection error');
}
2014-10-31 07:24:16 -07:00
if (response.statusCode === 409) {
return callback('BADCREDENTIALS: Invalid username or password');
2014-10-31 07:24:16 -07:00
}
if (response.statusCode !== 200) {
return callback('Unable to store data on insight');
}
return callback();
});
};
InsightStorage.prototype.removeItem = function(name, callback) {
this.setItem(name, '', callback);
};
InsightStorage.prototype.clear = function(callback) {
// NOOP
callback();
};
InsightStorage.prototype.allKeys = function(callback) {
// TODO: compatibility with localStorage
return callback(null);
};
InsightStorage.prototype.getFirst = function(prefix, opts, callback) {
// TODO: compatibility with localStorage
return callback(null, true, true);
};
module.exports = InsightStorage;