mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-12-06 17:27:59 +00:00
Merge pull request #102 from shekenahglory/develop
Save encrypted blob decrypt key at login if missing
This commit is contained in:
@@ -2,15 +2,13 @@ var async = require('async');
|
||||
var superagent = require('superagent');
|
||||
var RippleTxt = require('./rippletxt').RippleTxt;
|
||||
|
||||
function AuthInfo() {
|
||||
this.rippleTxt = new RippleTxt();
|
||||
var AuthInfo = { };
|
||||
|
||||
AuthInfo._getRippleTxt = function(domain, callback) {
|
||||
RippleTxt.get(domain, callback);
|
||||
};
|
||||
|
||||
AuthInfo.prototype._getRippleTxt = function(domain, callback) {
|
||||
this.rippleTxt.get(domain, callback);
|
||||
};
|
||||
|
||||
AuthInfo.prototype._getUser = function(url, callback) {
|
||||
AuthInfo._getUser = function(url, callback) {
|
||||
superagent.get(url, callback);
|
||||
};
|
||||
|
||||
@@ -23,7 +21,7 @@ AuthInfo.prototype._getUser = function(url, callback) {
|
||||
* @param {function} fn - Callback function
|
||||
*/
|
||||
|
||||
AuthInfo.prototype.get = function(domain, username, callback) {
|
||||
AuthInfo.get = function(domain, username, callback) {
|
||||
var self = this;
|
||||
username = username.toLowerCase();
|
||||
|
||||
|
||||
@@ -102,10 +102,11 @@ BlobObj.prototype.init = function(fn) {
|
||||
if (err || !resp.body || resp.body.result !== 'success') {
|
||||
return fn(new Error('Could not retrieve blob'));
|
||||
}
|
||||
|
||||
self.revision = resp.body.revision;
|
||||
|
||||
self.revision = resp.body.revision;
|
||||
self.encrypted_secret = resp.body.encrypted_secret;
|
||||
|
||||
self.missing_fields = resp.body.missing_fields;
|
||||
|
||||
if (!self.decrypt(resp.body.blob)) {
|
||||
return fn(new Error('Error while decrypting blob'));
|
||||
}
|
||||
@@ -902,7 +903,11 @@ BlobClient.recoverBlob = function (opts, fn) {
|
||||
if (err) {
|
||||
fn(err);
|
||||
} else if (resp.body && resp.body.result === 'success') {
|
||||
handleRecovery(resp);
|
||||
if (!resp.body.encrypted_blobdecrypt_key) {
|
||||
fn(new Error('Missing encrypted blob decrypt key.'));
|
||||
} else {
|
||||
handleRecovery(resp);
|
||||
}
|
||||
} else if (resp.body && resp.body.result === 'error') {
|
||||
fn(new Error(resp.body.message));
|
||||
} else {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
var superagent = require('superagent');
|
||||
var request = require('superagent');
|
||||
var Currency = require('./currency').Currency;
|
||||
|
||||
function RippleTxt() {
|
||||
this.txts = { };
|
||||
var RippleTxt = {
|
||||
txts : { }
|
||||
};
|
||||
|
||||
RippleTxt.urlTemplates = [
|
||||
@@ -13,22 +14,13 @@ RippleTxt.urlTemplates = [
|
||||
'http://ripple.{{domain}}/ripple.txt'
|
||||
];
|
||||
|
||||
RippleTxt.request = function() {
|
||||
return request;
|
||||
};
|
||||
|
||||
RippleTxt.prototype.request = function(url, callback) {
|
||||
return superagent.get(url, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the ripple.txt file for the given domain
|
||||
*
|
||||
* @param {string} domain - Domain to retrieve file from
|
||||
* @param {function} fn - Callback function
|
||||
*/
|
||||
|
||||
RippleTxt.prototype.get = function(domain, fn) {
|
||||
RippleTxt.get = function(domain, fn) {
|
||||
var self = this;
|
||||
|
||||
if (self.txts[domain]) {
|
||||
@@ -44,7 +36,7 @@ RippleTxt.prototype.get = function(domain, fn) {
|
||||
|
||||
url = url.replace('{{domain}}', domain);
|
||||
|
||||
self.request(url, function(err, resp) {
|
||||
request.get(url, function(err, resp) {
|
||||
if (err || !resp.text) {
|
||||
return nextUrl(++i);
|
||||
}
|
||||
@@ -59,11 +51,10 @@ RippleTxt.prototype.get = function(domain, fn) {
|
||||
|
||||
/**
|
||||
* Parse a ripple.txt file
|
||||
*
|
||||
* @param {string} txt - Unparsed ripple.txt data
|
||||
*/
|
||||
|
||||
RippleTxt.prototype.parse = function(txt) {
|
||||
RippleTxt.parse = function(txt) {
|
||||
var currentSection = '';
|
||||
var sections = { };
|
||||
|
||||
@@ -90,4 +81,53 @@ RippleTxt.prototype.parse = function(txt) {
|
||||
return sections;
|
||||
};
|
||||
|
||||
/**
|
||||
* extractDomain
|
||||
* attempt to extract the domain from a given url
|
||||
* returns the url if unsuccessful
|
||||
* @param {Object} url
|
||||
*/
|
||||
|
||||
RippleTxt.extractDomain = function (url) {
|
||||
match = /[^.]*\.[^.]{2,3}(?:\.[^.]{2,3})?([^.\?][^\?.]+?)?$/.exec(url);
|
||||
return match && match[0] ? match[0] : url;
|
||||
};
|
||||
|
||||
/**
|
||||
* getCurrencies
|
||||
* returns domain, issuer account and currency object
|
||||
* for each currency found in the domain's ripple.txt file
|
||||
* @param {Object} domain
|
||||
* @param {Object} fn
|
||||
*/
|
||||
|
||||
RippleTxt.getCurrencies = function(domain, fn) {
|
||||
domain = RippleTxt.extractDomain(domain);
|
||||
this.get(domain, function(err, txt) {
|
||||
if (err) {
|
||||
return fn(err);
|
||||
}
|
||||
|
||||
if (err || !txt.currencies || !txt.accounts) {
|
||||
return fn(null, []);
|
||||
}
|
||||
|
||||
//NOTE: this won't be accurate if there are
|
||||
//multiple issuer accounts with different
|
||||
//currencies associated with each.
|
||||
var issuer = txt.accounts[0];
|
||||
var currencies = [];
|
||||
|
||||
txt.currencies.forEach(function(currency) {
|
||||
currencies.push({
|
||||
issuer : issuer,
|
||||
currency : Currency.from_json(currency),
|
||||
domain : domain
|
||||
});
|
||||
});
|
||||
|
||||
fn(null, currencies);
|
||||
});
|
||||
};
|
||||
|
||||
exports.RippleTxt = RippleTxt;
|
||||
|
||||
@@ -2,7 +2,7 @@ var async = require('async');
|
||||
var blobClient = require('./blob').BlobClient;
|
||||
var AuthInfo = require('./authinfo').AuthInfo;
|
||||
var crypt = require('./crypt').Crypt;
|
||||
|
||||
var log = require('./log').sub('vault');
|
||||
function VaultClient(opts) {
|
||||
|
||||
var self = this;
|
||||
@@ -16,7 +16,6 @@ function VaultClient(opts) {
|
||||
}
|
||||
|
||||
this.domain = opts.domain || 'ripple.com';
|
||||
this.authInfo = new AuthInfo();
|
||||
this.infos = { };
|
||||
};
|
||||
|
||||
@@ -29,7 +28,7 @@ function VaultClient(opts) {
|
||||
*/
|
||||
VaultClient.prototype.getAuthInfo = function (username, callback) {
|
||||
|
||||
this.authInfo.get(this.domain, username, function(err, authInfo) {
|
||||
AuthInfo.get(this.domain, username, function(err, authInfo) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
@@ -78,7 +77,7 @@ VaultClient.prototype._deriveUnlockKey = function (authInfo, password, keys, cal
|
||||
//derive unlock key
|
||||
crypt.derive(authInfo.pakdf, 'unlock', authInfo.username.toLowerCase(), password, function(err, unlock) {
|
||||
if (err) {
|
||||
console.log('Error',err);
|
||||
log.error('derive:', err);
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
@@ -145,6 +144,16 @@ VaultClient.prototype.login = function(username, password, callback) {
|
||||
//save for relogin
|
||||
self.infos[keys.id] = authInfo;
|
||||
|
||||
//migrate missing fields
|
||||
if (blob.missing_fields) {
|
||||
if (blob.missing_fields.encrypted_blobdecrypt_key) {
|
||||
log.info('migration: saving encrypted blob decrypt key');
|
||||
authInfo.blob = blob;
|
||||
//get the key to unlock the secret, then update the blob keys
|
||||
self._deriveUnlockKey(authInfo, password, keys, updateKeys);
|
||||
}
|
||||
}
|
||||
|
||||
callback(null, {
|
||||
blob : blob,
|
||||
username : authInfo.username,
|
||||
@@ -152,6 +161,32 @@ VaultClient.prototype.login = function(username, password, callback) {
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function updateKeys (err, params, keys) {
|
||||
if (err || !keys.unlock) {
|
||||
return; //unable to unlock
|
||||
}
|
||||
|
||||
var secret;
|
||||
try {
|
||||
secret = crypt.decrypt(keys.unlock, params.blob.encrypted_secret);
|
||||
} catch (error) {
|
||||
return log.error('decrypt:', error);
|
||||
}
|
||||
|
||||
options = {
|
||||
username : params.username,
|
||||
blob : params.blob,
|
||||
masterkey : secret,
|
||||
keys : keys
|
||||
};
|
||||
|
||||
blobClient.updateKeys(options, function(err, resp){
|
||||
if (err) {
|
||||
log.error('updateKeys:', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -307,7 +342,7 @@ VaultClient.prototype.loginAndUnlock = function(username, password, fn) {
|
||||
*/
|
||||
|
||||
VaultClient.prototype.exists = function(username, callback) {
|
||||
this.authInfo.get(this.domain, username.toLowerCase(), function(err, authInfo) {
|
||||
AuthInfo.get(this.domain, username.toLowerCase(), function(err, authInfo) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
@@ -462,6 +497,11 @@ VaultClient.prototype.register = function(options, fn) {
|
||||
var self = this;
|
||||
var username = String(options.username).trim();
|
||||
var password = String(options.password).trim();
|
||||
var result = self.validateUsername(username);
|
||||
|
||||
if (!result.valid) {
|
||||
return fn(new Error('invalid username.'));
|
||||
}
|
||||
|
||||
var steps = [
|
||||
getAuthInfo,
|
||||
@@ -473,8 +513,7 @@ VaultClient.prototype.register = function(options, fn) {
|
||||
async.waterfall(steps, fn);
|
||||
|
||||
function getAuthInfo(callback) {
|
||||
self.getAuthInfo(username, function(err, authInfo){
|
||||
|
||||
self.getAuthInfo(username, function(err, authInfo){
|
||||
return callback (err, authInfo, password);
|
||||
});
|
||||
};
|
||||
@@ -485,7 +524,7 @@ VaultClient.prototype.register = function(options, fn) {
|
||||
id : keys.id,
|
||||
crypt : keys.crypt,
|
||||
unlock : keys.unlock,
|
||||
username : authInfo.username,
|
||||
username : username,
|
||||
email : options.email,
|
||||
masterkey : options.masterkey || crypt.createMaster(),
|
||||
activateLink : options.activateLink,
|
||||
@@ -499,11 +538,37 @@ VaultClient.prototype.register = function(options, fn) {
|
||||
} else {
|
||||
callback(null, {
|
||||
blob : blob,
|
||||
username : authInfo.username
|
||||
username : username
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
VaultClient.prototype.validateUsername = function (username) {
|
||||
username = String(username).trim();
|
||||
var result = {
|
||||
valid : false,
|
||||
reason : ''
|
||||
};
|
||||
|
||||
if (username.length < 2) {
|
||||
result.reason = 'tooshort';
|
||||
} else if (username.length > 20) {
|
||||
result.reason = 'toolong';
|
||||
} else if (!/^[a-zA-Z0-9\-]+$/.exec(username)) {
|
||||
result.reason = 'charset';
|
||||
} else if (/^-/.exec(username)) {
|
||||
result.reason = 'starthyphen';
|
||||
} else if (/-$/.exec(username)) {
|
||||
result.reason = 'endhyphen';
|
||||
} else if (/--/.exec(username)) {
|
||||
result.reason = 'multhyphen';
|
||||
} else {
|
||||
result.valid = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
exports.VaultClient = VaultClient;
|
||||
|
||||
@@ -5,7 +5,6 @@ var VaultClient = require('../src/js/ripple/vaultclient').VaultClient;
|
||||
var Blob = require('../src/js/ripple/blob').Blob;
|
||||
var UInt256 = require('../src/js/ripple/uint256').UInt256;
|
||||
var sjcl = require('../build/sjcl');
|
||||
var random = require('crypto').randomBytes(256);
|
||||
var nock = require('nock');
|
||||
var online = process.argv.indexOf('--online-blobvault') !== -1 ? true : false;
|
||||
|
||||
@@ -113,7 +112,9 @@ var blob = new Blob();
|
||||
|
||||
//must be set for self signed certs
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
||||
sjcl.random.addEntropy(random.toString()); //add entropy to seed the generator
|
||||
while(!sjcl.random.isReady()) {
|
||||
sjcl.random.addEntropy(require('crypto').randomBytes(128).toString()); //add entropy to seed the generator
|
||||
}
|
||||
|
||||
var mockRippleTxt;
|
||||
var mockAuthSign;
|
||||
@@ -203,21 +204,30 @@ if (!online) {
|
||||
|
||||
describe('Ripple Txt', function () {
|
||||
it('should get the content of a ripple.txt file from a given domain', function(done) {
|
||||
var rt = new RippleTxt();
|
||||
|
||||
rt.get(exampleData.domain, function(err, resp) {
|
||||
RippleTxt.get(exampleData.domain, function(err, resp) {
|
||||
assert.ifError(err);
|
||||
assert.strictEqual(typeof resp, 'object');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should get currencies from a ripple.txt file for a given domain', function(done) {
|
||||
RippleTxt.getCurrencies(exampleData.domain, function(err, currencies) {
|
||||
assert.ifError(err);
|
||||
assert(Array.isArray(currencies));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should get the domain from a given url', function() {
|
||||
var domain = RippleTxt.extractDomain("http://www.example.com");
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
});
|
||||
});
|
||||
|
||||
describe('AuthInfo', function() {
|
||||
it('should get auth info', function(done) {
|
||||
var auth = new AuthInfo();
|
||||
|
||||
auth.get(exampleData.domain, exampleData.username, function(err, resp) {
|
||||
AuthInfo.get(exampleData.domain, exampleData.username, function(err, resp) {
|
||||
assert.ifError(err);
|
||||
Object.keys(authInfoRes.body).forEach(function(prop) {
|
||||
assert(resp.hasOwnProperty(prop));
|
||||
|
||||
Reference in New Issue
Block a user