mirror of https://github.com/BTCPrivate/copay.git
288 lines
8.6 KiB
JavaScript
288 lines
8.6 KiB
JavaScript
var request = require('request');
|
|
var cryptoUtil = require('../util/crypto');
|
|
var bitcore = require('bitcore');
|
|
var buffers = require('buffer');
|
|
var querystring = require('querystring');
|
|
var Identity = require('../models/Identity');
|
|
var log = require('../util/log');
|
|
|
|
var SEPARATOR = '|';
|
|
|
|
function InsightStorage(config) {
|
|
this.type = 'DB';
|
|
this.storeUrl = config.url || 'https://insight.bitpay.com:443/api/email',
|
|
this.request = config.request || request;
|
|
|
|
this.iterations = config.iterations || 1000;
|
|
this.salt = config.salt || 'jBbYTj8zTrOt6V';
|
|
}
|
|
|
|
InsightStorage.prototype.init = function() {};
|
|
|
|
InsightStorage.prototype.setCredentials = function(email, password, opts) {
|
|
this.email = email;
|
|
this.password = password;
|
|
this._cachedKey = null;
|
|
};
|
|
|
|
InsightStorage.prototype.createItem = function(name, value, callback) {
|
|
var self = this;
|
|
|
|
this.getItem(name, function(err, retrieved) {
|
|
if (err || !retrieved) {
|
|
return self.setItem(name, value, callback);
|
|
} else {
|
|
return callback('EEXISTS');
|
|
}
|
|
});
|
|
};
|
|
|
|
InsightStorage.prototype.resendVerificationEmail = function (callback) {
|
|
var passphrase = this.getPassphrase();
|
|
var authHeader = new buffers.Buffer(this.email + ':' + passphrase).toString('base64');
|
|
var resendUrl = this.storeUrl + '/resend_email';
|
|
|
|
log.debug('Resending verification email: ' + this.email);
|
|
this.request.get({
|
|
url: resendUrl,
|
|
headers: {
|
|
'Authorization': authHeader
|
|
},
|
|
body: null,
|
|
}, function(err, response, body) {
|
|
if (err) {
|
|
return callback('Connection error');
|
|
}
|
|
if (response.statusCode === 409) {
|
|
return callback('BADCREDENTIALS: Invalid username or password');
|
|
} else if (response.statusCode !== 200) {
|
|
return callback('Unable to process the request');
|
|
}
|
|
return callback();
|
|
});
|
|
};
|
|
|
|
function mayBeOldPassword(password) {
|
|
// Test for base64
|
|
return /^[a-zA-Z0-9\/=\+]+$/.test(password);
|
|
}
|
|
|
|
InsightStorage.prototype.getItem = function(name, callback) {
|
|
var passphrase = this.getPassphrase();
|
|
var self = this;
|
|
|
|
this._makeGetRequest(passphrase, name, function(err, body, headers) {
|
|
if (err) log.warn(err);
|
|
if (err && err.indexOf('PNOTFOUND') !== -1 && mayBeOldPassword(self.password)) {
|
|
return self._brokenGetItem(name, callback);
|
|
}
|
|
return callback(err, body, headers);
|
|
});
|
|
};
|
|
|
|
/* This key need to have DIFFERENT
|
|
* settings(salt,iterations) than the kdf for wallet/profile encryption
|
|
* in Encrpted*Storage. The user should be able
|
|
* to change the settings on config.js to modify salt / iterations
|
|
* for encryption, but
|
|
* mantain the same key & passphrase. This is why those settings are
|
|
* not shared with encryption
|
|
*/
|
|
InsightStorage.prototype.getKey = function() {
|
|
if (!this._cachedKey) {
|
|
this._cachedKey = cryptoUtil.kdf(this.password + SEPARATOR + this.email, this.salt, this.iterations);
|
|
}
|
|
return this._cachedKey;
|
|
};
|
|
|
|
InsightStorage.prototype.getPassphrase = function() {
|
|
return bitcore.util.twoSha256(this.getKey()).toString('base64');
|
|
};
|
|
|
|
/**
|
|
* XmlHttpRequest's getAllResponseHeaders() method returns a string of response
|
|
* headers according to the format described here:
|
|
* http://www.w3.org/TR/XMLHttpRequest/#the-getallresponseheaders-method
|
|
* This method parses that string into a user-friendly key/value pair object.
|
|
*/
|
|
InsightStorage.parseResponseHeaders = function (headerStr) {
|
|
var headers = {};
|
|
if (!headerStr) {
|
|
return headers;
|
|
}
|
|
var headerPairs = headerStr.split('\u000d\u000a');
|
|
for (var i = 0, len = headerPairs.length; i < len; i++) {
|
|
var headerPair = headerPairs[i];
|
|
var index = headerPair.indexOf('\u003a\u0020');
|
|
if (index > 0) {
|
|
var key = headerPair.substring(0, index);
|
|
var val = headerPair.substring(index + 2);
|
|
headers[key] = val;
|
|
}
|
|
}
|
|
return headers;
|
|
}
|
|
|
|
InsightStorage.prototype._makeGetRequest = function(passphrase, key, callback) {
|
|
var authHeader = new buffers.Buffer(this.email + ':' + passphrase).toString('base64');
|
|
var retrieveUrl = this.storeUrl + '/retrieve';
|
|
var getParams = {
|
|
url: retrieveUrl + '?' + querystring.encode({
|
|
key: key,
|
|
rand: Math.random() // prevent cache
|
|
}),
|
|
headers: {
|
|
'Authorization': authHeader
|
|
}
|
|
};
|
|
this.request.get(getParams,
|
|
function(err, response, body) {
|
|
if (err) {
|
|
return callback('Connection error');
|
|
}
|
|
if (response.statusCode === 403) {
|
|
return callback('PNOTFOUND: Profile not found');
|
|
}
|
|
if (response.statusCode !== 200) {
|
|
return callback('Unable to read item from insight');
|
|
}
|
|
return callback(null, body, InsightStorage.parseResponseHeaders(response.getAllResponseHeaders()));
|
|
}
|
|
);
|
|
};
|
|
|
|
InsightStorage.prototype._brokenGetItem = function(name, callback) {
|
|
var passphrase = this._makeBrokenSecret();
|
|
var self = this;
|
|
log.debug('using legacy get');
|
|
this._makeGetRequest(passphrase, name, function(err, body) {
|
|
if (!err) {
|
|
return self._changePassphrase(function(err) {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
return callback(null, body);
|
|
});
|
|
}
|
|
return callback(err);
|
|
});
|
|
};
|
|
|
|
InsightStorage.prototype._makeBrokenSecret = function() {
|
|
var key = cryptoUtil.kdf(this.password + this.email, 'mjuBtGybi/4=', 100);
|
|
return cryptoUtil.kdf(key, this.password, 100);
|
|
};
|
|
|
|
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,
|
|
headers: {
|
|
'Authorization': authHeader
|
|
},
|
|
body: querystring.encode({
|
|
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) {
|
|
var passphrase = this.getPassphrase();
|
|
var authHeader = new buffers.Buffer(this.email + ':' + passphrase).toString('base64');
|
|
var registerUrl = this.storeUrl + '/save';
|
|
|
|
log.debug('setItem ' + name + ' size:'+ (value.length/1024).toFixed(1) + 'kb' );
|
|
this.request.post({
|
|
url: registerUrl,
|
|
headers: {
|
|
'Authorization': authHeader
|
|
},
|
|
body: querystring.encode({
|
|
key: name,
|
|
record: value
|
|
})
|
|
}, function(err, response, body) {
|
|
if (err) {
|
|
return callback('Connection error');
|
|
}
|
|
if (response.statusCode === 409) {
|
|
return callback('BADCREDENTIALS: Invalid username or password');
|
|
} else if (response.statusCode === 406) {
|
|
return callback('OVERQUOTA: Quota exceeded');
|
|
} else if (response.statusCode === 501) {
|
|
return callback('EMAILERROR: Error sending verification email');
|
|
} else if (response.statusCode !== 200) {
|
|
return callback('Unable to store data on insight');
|
|
}
|
|
return callback();
|
|
});
|
|
};
|
|
|
|
InsightStorage.prototype.removeItem = function(key, callback) {
|
|
var passphrase = this.getPassphrase();
|
|
var authHeader = new buffers.Buffer(this.email + ':' + passphrase).toString('base64');
|
|
var deleteUrl = this.storeUrl + '/delete/item';
|
|
var getParams = {
|
|
url: deleteUrl + '?' + querystring.encode({
|
|
key: key
|
|
}),
|
|
headers: {
|
|
'Authorization': authHeader
|
|
}
|
|
};
|
|
log.debug('Erasing: ' + key);
|
|
this.request.get(getParams, function(err, response, body) {
|
|
if (err) {
|
|
return callback('Connection error');
|
|
}
|
|
if (response.statusCode === 409) {
|
|
return callback('BADCREDENTIALS: Invalid username or password');
|
|
} else if (response.statusCode !== 200) {
|
|
return callback('Unable to remove data on insight');
|
|
}
|
|
return callback();
|
|
});
|
|
};
|
|
|
|
InsightStorage.prototype.clear = function(callback) {
|
|
var passphrase = this.getPassphrase();
|
|
var authHeader = new buffers.Buffer(this.email + ':' + passphrase).toString('base64');
|
|
var deleteUrl = this.storeUrl + '/delete/profile';
|
|
|
|
log.debug('Clearing storage for: ' + this.email);
|
|
this.request.post({
|
|
url: deleteUrl,
|
|
headers: {
|
|
'Authorization': authHeader
|
|
},
|
|
body: null,
|
|
}, function(err, response, body) {
|
|
if (err) {
|
|
return callback('Connection error');
|
|
}
|
|
if (response.statusCode === 409) {
|
|
return callback('BADCREDENTIALS: Invalid username or password');
|
|
} else if (response.statusCode !== 200) {
|
|
return callback('Unable to remove data on insight');
|
|
}
|
|
return callback();
|
|
});
|
|
};
|
|
|
|
module.exports = InsightStorage;
|