mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-12-06 17:27:59 +00:00
[CHORE] merge upstream changes
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -43,3 +43,6 @@ test/config.js
|
|||||||
/src-cov
|
/src-cov
|
||||||
/coverage.html
|
/coverage.html
|
||||||
/coverage
|
/coverage
|
||||||
|
|
||||||
|
# Ignore IntelliJ files
|
||||||
|
.idea
|
||||||
@@ -593,8 +593,23 @@ Amount.prototype.invert = function() {
|
|||||||
* 25.2 XRP => 25200000/XRP
|
* 25.2 XRP => 25200000/XRP
|
||||||
* USD 100.40 => 100.4/USD/?
|
* USD 100.40 => 100.4/USD/?
|
||||||
* 100 => 100000000/XRP
|
* 100 => 100000000/XRP
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* The regular expression below matches above cases, broken down for better understanding:
|
||||||
|
*
|
||||||
|
* ^\s* // start with any amount of whitespace
|
||||||
|
* ([a-z]{3})? // optional any 3 letters
|
||||||
|
* \s* // any amount of whitespace
|
||||||
|
* (-)? // optional dash
|
||||||
|
* (\d+) // 1 or more digits
|
||||||
|
* (\.(\d*))? // optional . character with any amount of digits
|
||||||
|
* \s* // any amount of whitespace
|
||||||
|
* ([a-f0-9]{40}|[a-z0-9]{3})? // optional 40 character hex string OR 3 letters
|
||||||
|
* \s* // any amount of whitespace
|
||||||
|
* $ // end of string
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
Amount.human_RE = /^\s*([a-z]{3})?\s*(-)?(\d+)(?:\.(\d*))?\s*([a-f0-9]{40}|[a-z0-9]{3})?\s*$/i;
|
Amount.human_RE = /^\s*([a-z]{3})?\s*(-)?(\d+)(\.(\d*))?\s*([a-f0-9]{40}|[a-z0-9]{3})?\s*$/i;
|
||||||
|
|
||||||
Amount.prototype.parse_human = function(j, opts) {
|
Amount.prototype.parse_human = function(j, opts) {
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
|
|||||||
@@ -1,36 +1,60 @@
|
|||||||
|
var async = require('async');
|
||||||
|
var superagent = require('superagent');
|
||||||
var RippleTxt = require('./rippletxt').RippleTxt;
|
var RippleTxt = require('./rippletxt').RippleTxt;
|
||||||
var request = require('superagent');
|
|
||||||
|
|
||||||
function AuthInfo() {
|
function AuthInfo() {
|
||||||
this.rippleTxt = new RippleTxt();
|
this.rippleTxt = new RippleTxt();
|
||||||
}
|
};
|
||||||
|
|
||||||
|
AuthInfo.prototype._getRippleTxt = function(domain, callback) {
|
||||||
|
this.rippleTxt.get(domain, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
AuthInfo.prototype._getUser = function(url, callback) {
|
||||||
|
superagent.get(url, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get auth info for a given username
|
* Get auth info for a given username
|
||||||
|
*
|
||||||
* @param {string} domain - Domain which hosts the user's info
|
* @param {string} domain - Domain which hosts the user's info
|
||||||
* @param {string} username - Username who's info we are retreiving
|
* @param {string} username - Username who's info we are retreiving
|
||||||
* @param {function} fn - Callback function
|
* @param {function} fn - Callback function
|
||||||
*/
|
*/
|
||||||
AuthInfo.prototype.get = function (domain, username, fn) {
|
|
||||||
|
AuthInfo.prototype.get = function(domain, username, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.rippleTxt.get(domain, function(err, txt){
|
function getRippleTxt(callback) {
|
||||||
if (err) return fn(err);
|
self._getRippleTxt(domain, function(err, txt) {
|
||||||
|
if (err) {
|
||||||
processTxt(txt);
|
return callback(err);
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
function processTxt(txt) {
|
|
||||||
if (!txt.authinfo_url) return fn(new Error("Authentication is not supported on "+domain));
|
|
||||||
var url = Array.isArray(txt.authinfo_url) ? txt.authinfo_url[0] : txt.authinfo_url;
|
|
||||||
url += "?domain="+domain+"&username="+username;
|
|
||||||
|
|
||||||
request.get(url, function(err, resp){
|
|
||||||
if (err || resp.error) return fn(new Error("Authentication info server unreachable"));
|
|
||||||
fn(null, resp.body);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!txt.authinfo_url) {
|
||||||
|
return callback(new Error('Authentication is not supported on ' + domain));
|
||||||
|
}
|
||||||
|
|
||||||
|
var url = Array.isArray(txt.authinfo_url) ? txt.authinfo_url[0] : txt.authinfo_url;
|
||||||
|
|
||||||
|
url += '?domain=' + domain + '&username=' + username;
|
||||||
|
|
||||||
|
callback(null, url);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.AuthInfo = AuthInfo;
|
function getUser(url, callback) {
|
||||||
|
self._getUser(url, function(err, res) {
|
||||||
|
if (err || res.error) {
|
||||||
|
callback(new Error('Authentication info server unreachable'));
|
||||||
|
} else {
|
||||||
|
callback(null, res.body);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
async.waterfall([ getRippleTxt, getUser ], callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.AuthInfo = AuthInfo;
|
||||||
|
|||||||
@@ -1,45 +1,44 @@
|
|||||||
var crypt = require('./crypt').Crypt;
|
var crypt = require('./crypt').Crypt;
|
||||||
var request = require('superagent');
|
var request = require('superagent');
|
||||||
var async = require('async');
|
|
||||||
var extend = require("extend");
|
var extend = require("extend");
|
||||||
|
|
||||||
var BlobClient = {};
|
var BlobClient = {};
|
||||||
|
|
||||||
//Blob object class
|
//Blob object class
|
||||||
var BlobObj = function (url, id, key) {
|
function BlobObj(url, id, key) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.data = {};
|
|
||||||
this.identity = new Identity(this);
|
this.identity = new Identity(this);
|
||||||
|
this.data = { };
|
||||||
};
|
};
|
||||||
|
|
||||||
// Blob operations
|
// Blob operations
|
||||||
// Do NOT change the mapping of existing ops
|
// Do NOT change the mapping of existing ops
|
||||||
BlobObj.ops = {
|
BlobObj.ops = {
|
||||||
// Special
|
// Special
|
||||||
"noop" : 0,
|
noop: 0,
|
||||||
|
|
||||||
// Simple ops
|
// Simple ops
|
||||||
"set" : 16,
|
set: 16,
|
||||||
"unset" : 17,
|
unset: 17,
|
||||||
"extend" : 18,
|
extend: 18,
|
||||||
|
|
||||||
// Meta ops
|
// Meta ops
|
||||||
"push" : 32,
|
push: 32,
|
||||||
"pop" : 33,
|
pop: 33,
|
||||||
"shift" : 34,
|
shift: 34,
|
||||||
"unshift" : 35,
|
unshift: 35,
|
||||||
"filter" : 36
|
filter: 36
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
BlobObj.opsReverseMap = [ ];
|
BlobObj.opsReverseMap = [ ];
|
||||||
|
|
||||||
for (var name in BlobObj.ops) {
|
for (var name in BlobObj.ops) {
|
||||||
BlobObj.opsReverseMap[BlobObj.ops[name]] = name;
|
BlobObj.opsReverseMap[BlobObj.ops[name]] = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//Identity fields
|
//Identity fields
|
||||||
var identityRoot = 'identityVault';
|
var identityRoot = 'identityVault';
|
||||||
var identityFields = [
|
var identityFields = [
|
||||||
@@ -83,26 +82,31 @@ var idTypeFields = [
|
|||||||
'other'
|
'other'
|
||||||
];
|
];
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Initialize a new blob object
|
* Initialize a new blob object
|
||||||
|
*
|
||||||
* @param {function} fn - Callback function
|
* @param {function} fn - Callback function
|
||||||
*/
|
*/
|
||||||
|
|
||||||
BlobObj.prototype.init = function(fn) {
|
BlobObj.prototype.init = function(fn) {
|
||||||
var self = this, url;
|
var self = this, url;
|
||||||
if (self.url.indexOf("://") === -1) self.url = "http://" + url;
|
|
||||||
|
if (self.url.indexOf('://') === -1) {
|
||||||
|
self.url = 'http://' + url;
|
||||||
|
}
|
||||||
|
|
||||||
url = self.url + '/v1/blob/' + self.id;
|
url = self.url + '/v1/blob/' + self.id;
|
||||||
|
|
||||||
request.get(url, function(err, resp) {
|
request.get(url, function(err, resp) {
|
||||||
|
if (err || !resp.body || resp.body.result !== 'success') {
|
||||||
if (err || !resp.body || resp.body.result !== 'success')
|
return fn(new Error('Could not retrieve blob'));
|
||||||
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.encrypted_secret = resp.body.encrypted_secret;
|
||||||
|
|
||||||
if (!self.decrypt(resp.body.blob)) {
|
if (!self.decrypt(resp.body.blob)) {
|
||||||
return fn(new Error("Error while decrypting blob"));
|
return fn(new Error('Error while decrypting blob'));
|
||||||
}
|
}
|
||||||
|
|
||||||
//Apply patches
|
//Apply patches
|
||||||
@@ -112,26 +116,30 @@ BlobObj.prototype.init = function (fn) {
|
|||||||
successful = successful && self.applyEncryptedPatch(patch);
|
successful = successful && self.applyEncryptedPatch(patch);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (successful) self.consolidate();
|
if (successful) {
|
||||||
|
self.consolidate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn(null, self);//return with newly decrypted blob
|
//return with newly decrypted blob
|
||||||
|
fn(null, self);
|
||||||
}).timeout(8000);
|
}).timeout(8000);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
/*
|
|
||||||
* Consolidate -
|
* Consolidate -
|
||||||
* Consolidate patches as a new revision
|
* Consolidate patches as a new revision
|
||||||
|
*
|
||||||
* @param {function} fn - Callback function
|
* @param {function} fn - Callback function
|
||||||
*/
|
*/
|
||||||
|
|
||||||
BlobObj.prototype.consolidate = function(fn) {
|
BlobObj.prototype.consolidate = function(fn) {
|
||||||
|
|
||||||
// Callback is optional
|
// Callback is optional
|
||||||
if ("function" !== typeof fn) fn = function(){};
|
if (typeof fn !== 'function') {
|
||||||
|
fn = function(){};
|
||||||
|
}
|
||||||
|
|
||||||
console.log("client: blob: consolidation at revision", this.revision);
|
//console.log('client: blob: consolidation at revision', this.revision);
|
||||||
var encrypted = this.encrypt();
|
var encrypted = this.encrypt();
|
||||||
|
|
||||||
var config = {
|
var config = {
|
||||||
@@ -150,80 +158,83 @@ BlobObj.prototype.consolidate = function (fn) {
|
|||||||
request.post(signed.url)
|
request.post(signed.url)
|
||||||
.send(signed.data)
|
.send(signed.data)
|
||||||
.end(function(err, resp) {
|
.end(function(err, resp) {
|
||||||
|
|
||||||
// XXX Add better error information to exception
|
// XXX Add better error information to exception
|
||||||
if (err) return fn(new Error("Failed to consolidate blob - XHR error"));
|
if (err) {
|
||||||
else if (resp.body && resp.body.result === 'success') return fn(null, resp.body);
|
fn(new Error('Failed to consolidate blob - XHR error'));
|
||||||
else return fn(new Error("Failed to consolidate blob"));
|
} else if (resp.body && resp.body.result === 'success') {
|
||||||
|
fn(null, resp.body);
|
||||||
|
} else {
|
||||||
|
fn(new Error('Failed to consolidate blob'));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
/*
|
|
||||||
* ApplyEncryptedPatch -
|
* ApplyEncryptedPatch -
|
||||||
* save changes from a downloaded patch to the blob
|
* save changes from a downloaded patch to the blob
|
||||||
|
*
|
||||||
* @param {string} patch - encrypted patch string
|
* @param {string} patch - encrypted patch string
|
||||||
*/
|
*/
|
||||||
BlobObj.prototype.applyEncryptedPatch = function (patch)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
var params = JSON.parse(crypt.decrypt(this.key, patch));
|
|
||||||
var op = params.shift();
|
|
||||||
var path = params.shift();
|
|
||||||
|
|
||||||
this.applyUpdate(op, path, params);
|
BlobObj.prototype.applyEncryptedPatch = function(patch) {
|
||||||
|
try {
|
||||||
|
var args = JSON.parse(crypt.decrypt(this.key, patch));
|
||||||
|
var op = args.shift();
|
||||||
|
var path = args.shift();
|
||||||
|
|
||||||
|
this.applyUpdate(op, path, args);
|
||||||
this.revision++;
|
this.revision++;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("client: blob: failed to apply patch:", err.toString());
|
//console.log('client: blob: failed to apply patch:', err.toString());
|
||||||
console.log(err.stack);
|
//console.log(err.stack);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt secret with unlock key
|
* Encrypt secret with unlock key
|
||||||
|
*
|
||||||
* @param {string} secretUnlockkey
|
* @param {string} secretUnlockkey
|
||||||
*/
|
*/
|
||||||
|
|
||||||
BlobObj.prototype.encryptSecret = function(secretUnlockKey, secret) {
|
BlobObj.prototype.encryptSecret = function(secretUnlockKey, secret) {
|
||||||
return crypt.encrypt(secretUnlockKey, secret);
|
return crypt.encrypt(secretUnlockKey, secret);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt secret with unlock key
|
* Decrypt secret with unlock key
|
||||||
|
*
|
||||||
* @param {string} secretUnlockkey
|
* @param {string} secretUnlockkey
|
||||||
*/
|
*/
|
||||||
|
|
||||||
BlobObj.prototype.decryptSecret = function(secretUnlockKey) {
|
BlobObj.prototype.decryptSecret = function(secretUnlockKey) {
|
||||||
return crypt.decrypt(secretUnlockKey, this.encrypted_secret);
|
return crypt.decrypt(secretUnlockKey, this.encrypted_secret);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt blob with crypt key
|
* Decrypt blob with crypt key
|
||||||
|
*
|
||||||
* @param {string} data - encrypted blob data
|
* @param {string} data - encrypted blob data
|
||||||
*/
|
*/
|
||||||
BlobObj.prototype.decrypt = function (data) {
|
|
||||||
|
|
||||||
|
BlobObj.prototype.decrypt = function(data) {
|
||||||
try {
|
try {
|
||||||
this.data = JSON.parse(crypt.decrypt(this.key, data));
|
this.data = JSON.parse(crypt.decrypt(this.key, data));
|
||||||
return this;
|
return this;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("client: blob: decryption failed", e.toString());
|
//console.log('client: blob: decryption failed', e.toString());
|
||||||
console.log(e.stack);
|
//console.log(e.stack);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt blob with crypt key
|
* Encrypt blob with crypt key
|
||||||
*/
|
*/
|
||||||
BlobObj.prototype.encrypt = function()
|
|
||||||
{
|
|
||||||
|
|
||||||
|
BlobObj.prototype.encrypt = function() {
|
||||||
// Filter Angular metadata before encryption
|
// Filter Angular metadata before encryption
|
||||||
// if ('object' === typeof this.data &&
|
// if ('object' === typeof this.data &&
|
||||||
// 'object' === typeof this.data.contacts)
|
// 'object' === typeof this.data.contacts)
|
||||||
@@ -232,12 +243,13 @@ BlobObj.prototype.encrypt = function()
|
|||||||
return crypt.encrypt(this.key, JSON.stringify(this.data));
|
return crypt.encrypt(this.key, JSON.stringify(this.data));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt recovery key
|
* Encrypt recovery key
|
||||||
|
*
|
||||||
* @param {string} secret
|
* @param {string} secret
|
||||||
* @param {string} blobDecryptKey
|
* @param {string} blobDecryptKey
|
||||||
*/
|
*/
|
||||||
|
|
||||||
BlobObj.prototype.encryptBlobCrypt = function(secret, blobDecryptKey) {
|
BlobObj.prototype.encryptBlobCrypt = function(secret, blobDecryptKey) {
|
||||||
var recoveryEncryptionKey = crypt.deriveRecoveryEncryptionKeyFromSecret(secret);
|
var recoveryEncryptionKey = crypt.deriveRecoveryEncryptionKeyFromSecret(secret);
|
||||||
return crypt.encrypt(recoveryEncryptionKey, blobDecryptKey);
|
return crypt.encrypt(recoveryEncryptionKey, blobDecryptKey);
|
||||||
@@ -245,55 +257,53 @@ BlobObj.prototype.encryptBlobCrypt = function (secret, blobDecryptKey) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt recovery key
|
* Decrypt recovery key
|
||||||
|
*
|
||||||
* @param {string} secret
|
* @param {string} secret
|
||||||
*/
|
*/
|
||||||
|
|
||||||
BlobObj.prototype.decryptBlobCrypt = function(secret) {
|
BlobObj.prototype.decryptBlobCrypt = function(secret) {
|
||||||
var recoveryEncryptionKey = crypt.deriveRecoveryEncryptionKeyFromSecret(secret);
|
var recoveryEncryptionKey = crypt.deriveRecoveryEncryptionKeyFromSecret(secret);
|
||||||
return crypt.decrypt(recoveryEncryptionKey, this.encrypted_blobdecrypt_key);
|
return crypt.decrypt(recoveryEncryptionKey, this.encrypted_blobdecrypt_key);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**** Blob updating functions ****/
|
/**** Blob updating functions ****/
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set blob element
|
* Set blob element
|
||||||
*/
|
*/
|
||||||
|
|
||||||
BlobObj.prototype.set = function(pointer, value, fn) {
|
BlobObj.prototype.set = function(pointer, value, fn) {
|
||||||
this.applyUpdate('set', pointer, [value]);
|
this.applyUpdate('set', pointer, [value]);
|
||||||
this.postUpdate('set', pointer, [value], fn);
|
this.postUpdate('set', pointer, [value], fn);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove blob element
|
* Remove blob element
|
||||||
*/
|
*/
|
||||||
|
|
||||||
BlobObj.prototype.unset = function(pointer, fn) {
|
BlobObj.prototype.unset = function(pointer, fn) {
|
||||||
this.applyUpdate('unset', pointer, []);
|
this.applyUpdate('unset', pointer, []);
|
||||||
this.postUpdate('unset', pointer, [], fn);
|
this.postUpdate('unset', pointer, [], fn);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extend blob object
|
* Extend blob object
|
||||||
*/
|
*/
|
||||||
|
|
||||||
BlobObj.prototype.extend = function(pointer, value, fn) {
|
BlobObj.prototype.extend = function(pointer, value, fn) {
|
||||||
this.applyUpdate('extend', pointer, [value]);
|
this.applyUpdate('extend', pointer, [value]);
|
||||||
this.postUpdate('extend', pointer, [value], fn);
|
this.postUpdate('extend', pointer, [value], fn);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepend blob array
|
* Prepend blob array
|
||||||
*/
|
*/
|
||||||
|
|
||||||
BlobObj.prototype.unshift = function(pointer, value, fn) {
|
BlobObj.prototype.unshift = function(pointer, value, fn) {
|
||||||
this.applyUpdate('unshift', pointer, [value]);
|
this.applyUpdate('unshift', pointer, [value]);
|
||||||
this.postUpdate('unshift', pointer, [value], fn);
|
this.postUpdate('unshift', pointer, [value], fn);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter the row(s) from an array.
|
* Filter the row(s) from an array.
|
||||||
*
|
*
|
||||||
@@ -302,49 +312,50 @@ BlobObj.prototype.unshift = function (pointer, value, fn) {
|
|||||||
*
|
*
|
||||||
* The subcommands can be any commands with the pointer parameter left out.
|
* The subcommands can be any commands with the pointer parameter left out.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
BlobObj.prototype.filter = function(pointer, field, value, subcommands, callback) {
|
BlobObj.prototype.filter = function(pointer, field, value, subcommands, callback) {
|
||||||
var params = Array.prototype.slice.apply(arguments);
|
var args = Array.prototype.slice.apply(arguments);
|
||||||
if ("function" === typeof params[params.length-1]) {
|
|
||||||
callback = params.pop();
|
if (typeof args[args.length - 1] === 'function') {
|
||||||
|
callback = args.pop();
|
||||||
}
|
}
|
||||||
params.shift();
|
|
||||||
|
args.shift();
|
||||||
|
|
||||||
// Normalize subcommands to minimize the patch size
|
// Normalize subcommands to minimize the patch size
|
||||||
params = params.slice(0, 2).concat(normalizeSubcommands(params.slice(2), true));
|
args = args.slice(0, 2).concat(normalizeSubcommands(args.slice(2), true));
|
||||||
|
|
||||||
this.applyUpdate('filter', pointer, params);
|
this.applyUpdate('filter', pointer, args);
|
||||||
this.postUpdate('filter', pointer, params, callback);
|
this.postUpdate('filter', pointer, args, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply udpdate to the blob
|
* Apply udpdate to the blob
|
||||||
*/
|
*/
|
||||||
BlobObj.prototype.applyUpdate = function (op, path, params) {
|
|
||||||
|
|
||||||
|
BlobObj.prototype.applyUpdate = function(op, path, params) {
|
||||||
// Exchange from numeric op code to string
|
// Exchange from numeric op code to string
|
||||||
if ("number" === typeof op) {
|
if (typeof op === 'number') {
|
||||||
op = BlobObj.opsReverseMap[op];
|
op = BlobObj.opsReverseMap[op];
|
||||||
}
|
}
|
||||||
if ("string" !== typeof op) {
|
|
||||||
throw new Error("Blob update op code must be a number or a valid op id string");
|
if (typeof op !== 'string') {
|
||||||
|
throw new Error('Blob update op code must be a number or a valid op id string');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Separate each step in the "pointer"
|
// Separate each step in the 'pointer'
|
||||||
var pointer = path.split("/");
|
var pointer = path.split('/');
|
||||||
|
|
||||||
var first = pointer.shift();
|
var first = pointer.shift();
|
||||||
if (first !== "") {
|
|
||||||
throw new Error("Invalid JSON pointer: "+path);
|
if (first !== '') {
|
||||||
|
throw new Error('Invalid JSON pointer: '+path);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._traverse(this.data, pointer, path, op, params);
|
this._traverse(this.data, pointer, path, op, params);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
//for applyUpdate function
|
//for applyUpdate function
|
||||||
BlobObj.prototype._traverse = function (context, pointer,
|
BlobObj.prototype._traverse = function(context, pointer, originalPointer, op, params) {
|
||||||
originalPointer, op, params) {
|
|
||||||
var _this = this;
|
var _this = this;
|
||||||
var part = _this.unescapeToken(pointer.shift());
|
var part = _this.unescapeToken(pointer.shift());
|
||||||
|
|
||||||
@@ -352,16 +363,15 @@ BlobObj.prototype._traverse = function (context, pointer,
|
|||||||
if (part === '-') {
|
if (part === '-') {
|
||||||
part = context.length;
|
part = context.length;
|
||||||
} else if (part % 1 !== 0 && part >= 0) {
|
} else if (part % 1 !== 0 && part >= 0) {
|
||||||
throw new Error("Invalid pointer, array element segments must be " +
|
throw new Error('Invalid pointer, array element segments must be a positive integer, zero or '-'');
|
||||||
"a positive integer, zero or '-'");
|
|
||||||
}
|
}
|
||||||
} else if ("object" !== typeof context) {
|
} else if (typeof context !== 'object') {
|
||||||
return null;
|
return null;
|
||||||
} else if (!context.hasOwnProperty(part)) {
|
} else if (!context.hasOwnProperty(part)) {
|
||||||
// Some opcodes create the path as they're going along
|
// Some opcodes create the path as they're going along
|
||||||
if (op === "set") {
|
if (op === 'set') {
|
||||||
context[part] = {};
|
context[part] = {};
|
||||||
} else if (op === "unshift") {
|
} else if (op === 'unshift') {
|
||||||
context[part] = [];
|
context[part] = [];
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
@@ -369,42 +379,39 @@ BlobObj.prototype._traverse = function (context, pointer,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (pointer.length !== 0) {
|
if (pointer.length !== 0) {
|
||||||
return this._traverse(context[part], pointer,
|
return this._traverse(context[part], pointer, originalPointer, op, params);
|
||||||
originalPointer, op, params);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (op) {
|
switch (op) {
|
||||||
case "set":
|
case 'set':
|
||||||
context[part] = params[0];
|
context[part] = params[0];
|
||||||
break;
|
break;
|
||||||
case "unset":
|
case 'unset':
|
||||||
if (Array.isArray(context)) {
|
if (Array.isArray(context)) {
|
||||||
context.splice(part, 1);
|
context.splice(part, 1);
|
||||||
} else {
|
} else {
|
||||||
delete context[part];
|
delete context[part];
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "extend":
|
case 'extend':
|
||||||
if ("object" !== typeof context[part]) {
|
if (typeof context[part] !== 'object') {
|
||||||
throw new Error("Tried to extend a non-object");
|
throw new Error('Tried to extend a non-object');
|
||||||
}
|
}
|
||||||
extend(true, context[part], params[0]);
|
extend(true, context[part], params[0]);
|
||||||
break;
|
break;
|
||||||
case "unshift":
|
case 'unshift':
|
||||||
if ("undefined" === typeof context[part]) {
|
if (typeof context[part] === 'undefined') {
|
||||||
context[part] = [ ];
|
context[part] = [ ];
|
||||||
} else if (!Array.isArray(context[part])) {
|
} else if (!Array.isArray(context[part])) {
|
||||||
throw new Error("Operator 'unshift' must be applied to an array.");
|
throw new Error('Operator "unshift" must be applied to an array.');
|
||||||
}
|
}
|
||||||
context[part].unshift(params[0]);
|
context[part].unshift(params[0]);
|
||||||
break;
|
break;
|
||||||
case "filter":
|
case 'filter':
|
||||||
if (Array.isArray(context[part])) {
|
if (Array.isArray(context[part])) {
|
||||||
context[part].forEach(function(element, i) {
|
context[part].forEach(function(element, i) {
|
||||||
if ("object" === typeof element &&
|
if (typeof element === 'object' && element.hasOwnProperty(params[0]) && element[params[0]] === params[1]) {
|
||||||
element.hasOwnProperty(params[0]) &&
|
var subpointer = originalPointer + '/' + i;
|
||||||
element[params[0]] === params[1]) {
|
|
||||||
var subpointer = originalPointer+"/"+i;
|
|
||||||
var subcommands = normalizeSubcommands(params.slice(2));
|
var subcommands = normalizeSubcommands(params.slice(2));
|
||||||
|
|
||||||
subcommands.forEach(function(subcommand) {
|
subcommands.forEach(function(subcommand) {
|
||||||
@@ -417,47 +424,51 @@ BlobObj.prototype._traverse = function (context, pointer,
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error("Unsupported op "+op);
|
throw new Error('Unsupported op '+op);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
BlobObj.prototype.escapeToken = function(token) {
|
BlobObj.prototype.escapeToken = function(token) {
|
||||||
return token.replace(/[~\/]/g, function (key) { return key === "~" ? "~0" : "~1"; });
|
return token.replace(/[~\/]/g, function(key) {
|
||||||
|
return key === '~' ? '~0' : '~1';
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
BlobObj.prototype.unescapeToken = function(str) {
|
BlobObj.prototype.unescapeToken = function(str) {
|
||||||
return str.replace(/~./g, function(m) {
|
return str.replace(/~./g, function(m) {
|
||||||
switch (m) {
|
switch (m) {
|
||||||
case "~0":
|
case '~0':
|
||||||
return "~";
|
return '~';
|
||||||
case "~1":
|
case '~1':
|
||||||
return "/";
|
return '/';
|
||||||
}
|
}
|
||||||
throw("Invalid tilde escape: " + m);
|
throw new Error('Invalid tilde escape: ' + m);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sumbit update to blob vault
|
* Sumbit update to blob vault
|
||||||
*/
|
*/
|
||||||
|
|
||||||
BlobObj.prototype.postUpdate = function(op, pointer, params, fn) {
|
BlobObj.prototype.postUpdate = function(op, pointer, params, fn) {
|
||||||
// Callback is optional
|
// Callback is optional
|
||||||
if ("function" !== typeof fn) fn = function(){};
|
if (typeof fn !== 'function') {
|
||||||
|
fn = function(){};
|
||||||
|
}
|
||||||
|
|
||||||
if ("string" === typeof op) {
|
if (typeof op === 'string') {
|
||||||
op = BlobObj.ops[op];
|
op = BlobObj.ops[op];
|
||||||
}
|
}
|
||||||
if ("number" !== typeof op) {
|
|
||||||
throw new Error("Blob update op code must be a number or a valid op id string");
|
if (typeof op !== 'number') {
|
||||||
}
|
throw new Error('Blob update op code must be a number or a valid op id string');
|
||||||
if (op < 0 || op > 255) {
|
|
||||||
throw new Error("Blob update op code out of bounds");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("client: blob: submitting update", BlobObj.opsReverseMap[op], pointer, params);
|
if (op < 0 || op > 255) {
|
||||||
|
throw new Error('Blob update op code out of bounds');
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log('client: blob: submitting update', BlobObj.opsReverseMap[op], pointer, params);
|
||||||
|
|
||||||
params.unshift(pointer);
|
params.unshift(pointer);
|
||||||
params.unshift(op);
|
params.unshift(op);
|
||||||
@@ -472,36 +483,29 @@ BlobObj.prototype.postUpdate = function (op, pointer, params, fn) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
var signed = crypt.signRequestHmac(config, this.data.auth_secret, this.id);
|
var signed = crypt.signRequestHmac(config, this.data.auth_secret, this.id);
|
||||||
|
|
||||||
request.post(signed.url)
|
request.post(signed.url)
|
||||||
.send(signed.data)
|
.send(signed.data)
|
||||||
.end(function(err, resp) {
|
.end(function(err, resp) {
|
||||||
if (err)
|
if (err) {
|
||||||
return fn(new Error("Patch could not be saved - XHR error"));
|
fn(new Error('Patch could not be saved - XHR error'));
|
||||||
else if (!resp.body || resp.body.result !== 'success')
|
} else if (!resp.body || resp.body.result !== 'success') {
|
||||||
return fn(new Error("Patch could not be saved - bad result"));
|
fn(new Error('Patch could not be saved - bad result'));
|
||||||
|
} else {
|
||||||
return fn(null, resp.body);
|
fn(null, resp.body);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/***** helper functions *****/
|
/***** helper functions *****/
|
||||||
|
|
||||||
|
|
||||||
function normalizeSubcommands(subcommands, compress) {
|
function normalizeSubcommands(subcommands, compress) {
|
||||||
// Normalize parameter structure
|
// Normalize parameter structure
|
||||||
if ("number" === typeof subcommands[0] ||
|
if (/(number|string)/.test(typeof subcommands[0])) {
|
||||||
"string" === typeof subcommands[0]) {
|
|
||||||
// Case 1: Single subcommand inline
|
// Case 1: Single subcommand inline
|
||||||
subcommands = [subcommands];
|
subcommands = [subcommands];
|
||||||
} else if (subcommands.length === 1 &&
|
} else if (subcommands.length === 1 && Array.isArray(subcommands[0]) && /(number|string)/.test(typeof subcommands[0][0])) {
|
||||||
Array.isArray(subcommands[0]) &&
|
|
||||||
("number" === typeof subcommands[0][0] ||
|
|
||||||
"string" === typeof subcommands[0][0])) {
|
|
||||||
// Case 2: Single subcommand as array
|
// Case 2: Single subcommand as array
|
||||||
// (nothing to do)
|
// (nothing to do)
|
||||||
} else if (Array.isArray(subcommands[0])) {
|
} else if (Array.isArray(subcommands[0])) {
|
||||||
@@ -511,15 +515,18 @@ function normalizeSubcommands(subcommands, compress) {
|
|||||||
|
|
||||||
// Normalize op name and convert strings to numeric codes
|
// Normalize op name and convert strings to numeric codes
|
||||||
subcommands = subcommands.map(function(subcommand) {
|
subcommands = subcommands.map(function(subcommand) {
|
||||||
if ("string" === typeof subcommand[0]) {
|
if (typeof subcommand[0] === 'string') {
|
||||||
subcommand[0] = BlobObj.ops[subcommand[0]];
|
subcommand[0] = BlobObj.ops[subcommand[0]];
|
||||||
}
|
}
|
||||||
if ("number" !== typeof subcommand[0]) {
|
|
||||||
throw new Error("Invalid op in subcommand");
|
if (typeof subcommand[0] !== 'number') {
|
||||||
|
throw new Error('Invalid op in subcommand');
|
||||||
}
|
}
|
||||||
if ("string" !== typeof subcommand[1]) {
|
|
||||||
throw new Error("Invalid path in subcommand");
|
if (typeof subcommand[1] !== 'string') {
|
||||||
|
throw new Error('Invalid path in subcommand');
|
||||||
}
|
}
|
||||||
|
|
||||||
return subcommand;
|
return subcommand;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -542,6 +549,7 @@ function normalizeSubcommands(subcommands, compress) {
|
|||||||
* Identity class
|
* Identity class
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var Identity = function (blob) {
|
var Identity = function (blob) {
|
||||||
var self = this;
|
var self = this;
|
||||||
self.blob = blob;
|
self.blob = blob;
|
||||||
@@ -559,12 +567,12 @@ var Identity = function (blob) {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* getFullAddress
|
* getFullAddress
|
||||||
* returns the address formed into a text string
|
* returns the address formed into a text string
|
||||||
* @param {string} key - Encryption key
|
* @param {string} key - Encryption key
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Identity.prototype.getFullAddress = function (key) {
|
Identity.prototype.getFullAddress = function (key) {
|
||||||
if (!this.blob ||
|
if (!this.blob ||
|
||||||
!this.blob.data ||
|
!this.blob.data ||
|
||||||
@@ -592,6 +600,7 @@ Identity.prototype.getFullAddress = function (key) {
|
|||||||
* @param {string} key - Encryption key
|
* @param {string} key - Encryption key
|
||||||
* @param {function} fn - Callback function
|
* @param {function} fn - Callback function
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Identity.prototype.getAll = function (key) {
|
Identity.prototype.getAll = function (key) {
|
||||||
|
|
||||||
if (!this.blob || !this.blob.data || !this.blob.data[identityRoot]) {
|
if (!this.blob || !this.blob.data || !this.blob.data[identityRoot]) {
|
||||||
@@ -606,13 +615,13 @@ Identity.prototype.getAll = function (key) {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get
|
* get
|
||||||
* get and decrypt a single identity field
|
* get and decrypt a single identity field
|
||||||
* @param {string} pointer - Field to retrieve
|
* @param {string} pointer - Field to retrieve
|
||||||
* @param {string} key - Encryption key
|
* @param {string} key - Encryption key
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Identity.prototype.get = function (pointer, key) {
|
Identity.prototype.get = function (pointer, key) {
|
||||||
if (!this.blob || !this.blob.data || !this.blob.data[identityRoot]) {
|
if (!this.blob || !this.blob.data || !this.blob.data[identityRoot]) {
|
||||||
return null;
|
return null;
|
||||||
@@ -651,7 +660,6 @@ Identity.prototype.get = function (pointer, key) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* set
|
* set
|
||||||
* set and encrypt a single identity field.
|
* set and encrypt a single identity field.
|
||||||
@@ -660,6 +668,7 @@ Identity.prototype.get = function (pointer, key) {
|
|||||||
* @param {string} value - Unencrypted data
|
* @param {string} value - Unencrypted data
|
||||||
* @param {function} fn - Callback function
|
* @param {function} fn - Callback function
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Identity.prototype.set = function (pointer, key, value, fn) {
|
Identity.prototype.set = function (pointer, key, value, fn) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
@@ -732,7 +741,6 @@ Identity.prototype.set = function (pointer, key, value, fn) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* unset
|
* unset
|
||||||
* remove a single identity field - will only be removed
|
* remove a single identity field - will only be removed
|
||||||
@@ -741,6 +749,7 @@ Identity.prototype.set = function (pointer, key, value, fn) {
|
|||||||
* @param {string} key - Encryption key
|
* @param {string} key - Encryption key
|
||||||
* @param {function} fn - Callback function
|
* @param {function} fn - Callback function
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Identity.prototype.unset = function (pointer, key, fn) {
|
Identity.prototype.unset = function (pointer, key, fn) {
|
||||||
|
|
||||||
//NOTE: this is rather useless since you can overwrite
|
//NOTE: this is rather useless since you can overwrite
|
||||||
@@ -753,52 +762,62 @@ Identity.prototype.unset = function (pointer, key, fn) {
|
|||||||
this.blob.unset("/" + identityRoot+"/" + pointer, fn);
|
this.blob.unset("/" + identityRoot+"/" + pointer, fn);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/***** blob client methods ****/
|
/***** blob client methods ****/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Blob object class
|
* Blob object class
|
||||||
*/
|
*/
|
||||||
BlobClient.Blob = BlobObj;
|
|
||||||
|
exports.Blob = BlobObj;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get ripple name for a given address
|
* Get ripple name for a given address
|
||||||
*/
|
*/
|
||||||
module.exports.getRippleName = function (url, address, fn) {
|
|
||||||
|
|
||||||
if (!crypt.isValidAddress(address)) return fn (new Error("Invalid ripple address"));
|
exports.getRippleName = function(url, address, fn) {
|
||||||
|
if (!crypt.isValidAddress(address)) {
|
||||||
|
return fn (new Error('Invalid ripple address'));
|
||||||
|
}
|
||||||
|
|
||||||
request.get(url + '/v1/user/' + address, function(err, resp){
|
request.get(url + '/v1/user/' + address, function(err, resp){
|
||||||
if (err) return fn(new Error("Unable to access vault sever"));
|
if (err) {
|
||||||
else if (resp.body && resp.body.username) return fn(null, resp.body.username);
|
fn(new Error('Unable to access vault sever'));
|
||||||
else if (resp.body && resp.body.exists === false) return fn (new Error("No ripple name for this address"));
|
} else if (resp.body && resp.body.username) {
|
||||||
else return fn(new Error("Unable to determine if ripple name exists"));
|
fn(null, resp.body.username);
|
||||||
|
} else if (resp.body && resp.body.exists === false) {
|
||||||
|
fn (new Error('No ripple name for this address'));
|
||||||
|
} else {
|
||||||
|
fn(new Error('Unable to determine if ripple name exists'));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
/*
|
|
||||||
* Retrive a blob with url, id and key
|
* Retrive a blob with url, id and key
|
||||||
*/
|
*/
|
||||||
|
|
||||||
BlobClient.get = function (url, id, crypt, fn) {
|
BlobClient.get = function (url, id, crypt, fn) {
|
||||||
var blob = new BlobObj(url, id, crypt);
|
var blob = new BlobObj(url, id, crypt);
|
||||||
blob.init(fn);
|
blob.init(fn);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
/*
|
|
||||||
* Verify email address
|
* Verify email address
|
||||||
*/
|
*/
|
||||||
|
|
||||||
BlobClient.verify = function(url, username, token, fn) {
|
BlobClient.verify = function(url, username, token, fn) {
|
||||||
url += '/v1/user/' + username + '/verify/' + token;
|
url += '/v1/user/' + username + '/verify/' + token;
|
||||||
request.get(url, function(err, resp){
|
request.get(url, function(err, resp){
|
||||||
if (err) return fn(err);
|
if (err) {
|
||||||
else if (resp.body && resp.body.result === 'success') return fn(null, data);
|
fn(err);
|
||||||
else return fn(new Error("Failed to verify the account"));
|
} else if (resp.body && resp.body.result === 'success') {
|
||||||
|
fn(null, data);
|
||||||
|
} else {
|
||||||
|
fn(new Error('Failed to verify the account'));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a blob object
|
* Create a blob object
|
||||||
*
|
*
|
||||||
@@ -812,11 +831,12 @@ BlobClient.verify = function (url, username, token, fn) {
|
|||||||
* @param {object} options.oldUserBlob
|
* @param {object} options.oldUserBlob
|
||||||
* @param {function} fn
|
* @param {function} fn
|
||||||
*/
|
*/
|
||||||
BlobClient.create = function (options, fn) {
|
|
||||||
|
|
||||||
|
BlobClient.create = function(options, fn) {
|
||||||
var blob = new BlobObj(options.url, options.id, options.crypt);
|
var blob = new BlobObj(options.url, options.id, options.crypt);
|
||||||
|
|
||||||
blob.revision = 0;
|
blob.revision = 0;
|
||||||
|
|
||||||
blob.data = {
|
blob.data = {
|
||||||
auth_secret: crypt.createSecret(8),
|
auth_secret: crypt.createSecret(8),
|
||||||
account_id: crypt.getAddress(options.masterkey),
|
account_id: crypt.getAddress(options.masterkey),
|
||||||
@@ -834,7 +854,7 @@ BlobClient.create = function (options, fn) {
|
|||||||
|
|
||||||
//post to the blob vault to create
|
//post to the blob vault to create
|
||||||
var config = {
|
var config = {
|
||||||
method : "POST",
|
method: 'POST',
|
||||||
url: options.url + '/v1/user',
|
url: options.url + '/v1/user',
|
||||||
data: {
|
data: {
|
||||||
blob_id: options.id,
|
blob_id: options.id,
|
||||||
@@ -854,10 +874,14 @@ BlobClient.create = function (options, fn) {
|
|||||||
request.post(signed)
|
request.post(signed)
|
||||||
.send(signed.data)
|
.send(signed.data)
|
||||||
.end(function(err, resp) {
|
.end(function(err, resp) {
|
||||||
if (err) return fn(err);
|
if (err) {
|
||||||
else if (resp.body && resp.body.result === 'success') return fn(null, blob,resp.body);
|
fn(err);
|
||||||
else return fn(new Error("Could not create blob"));
|
} else if (resp.body && resp.body.result === 'success') {
|
||||||
|
fn(null, blob,resp.body);
|
||||||
|
} else {
|
||||||
|
fn(new Error('Could not create blob'));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.BlobClient = BlobClient;
|
exports.BlobClient = BlobClient;
|
||||||
|
|||||||
@@ -3,32 +3,33 @@ var base = require('./base').Base;
|
|||||||
var UInt160 = require('./uint160').UInt160;
|
var UInt160 = require('./uint160').UInt160;
|
||||||
var message = require('./message');
|
var message = require('./message');
|
||||||
var request = require('superagent');
|
var request = require('superagent');
|
||||||
|
var querystring = require('querystring');
|
||||||
var extend = require("extend");
|
var extend = require("extend");
|
||||||
var parser = require("url");
|
var parser = require("url");
|
||||||
var Crypt = { };
|
var Crypt = { };
|
||||||
|
|
||||||
var cryptConfig = {
|
var cryptConfig = {
|
||||||
cipher : "aes",
|
cipher : 'aes',
|
||||||
mode : "ccm",
|
mode : 'ccm',
|
||||||
ts : 64, // tag length
|
ts : 64, // tag length
|
||||||
ks : 256, // key size
|
ks : 256, // key size
|
||||||
iter : 1000 // iterations (key derivation)
|
iter : 1000 // iterations (key derivation)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Full domain hash based on SHA512
|
* Full domain hash based on SHA512
|
||||||
*/
|
*/
|
||||||
function fdh(data, bytelen)
|
|
||||||
{
|
function fdh(data, bytelen) {
|
||||||
var bitlen = bytelen << 3;
|
var bitlen = bytelen << 3;
|
||||||
|
|
||||||
if (typeof data === "string") {
|
if (typeof data === 'string') {
|
||||||
data = sjcl.codec.utf8String.toBits(data);
|
data = sjcl.codec.utf8String.toBits(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add hashing rounds until we exceed desired length in bits
|
// Add hashing rounds until we exceed desired length in bits
|
||||||
var counter = 0, output = [];
|
var counter = 0, output = [];
|
||||||
|
|
||||||
while (sjcl.bitArray.bitLength(output) < bitlen) {
|
while (sjcl.bitArray.bitLength(output) < bitlen) {
|
||||||
var hash = sjcl.hash.sha512.hash(sjcl.bitArray.concat([counter], data));
|
var hash = sjcl.hash.sha512.hash(sjcl.bitArray.concat([counter], data));
|
||||||
output = sjcl.bitArray.concat(output, hash);
|
output = sjcl.bitArray.concat(output, hash);
|
||||||
@@ -39,86 +40,90 @@ function fdh(data, bytelen)
|
|||||||
output = sjcl.bitArray.clamp(output, bitlen);
|
output = sjcl.bitArray.clamp(output, bitlen);
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a function to derive different hashes from the same key.
|
* This is a function to derive different hashes from the same key.
|
||||||
* Each hash is derived as HMAC-SHA512HALF(key, token).
|
* Each hash is derived as HMAC-SHA512HALF(key, token).
|
||||||
|
*
|
||||||
* @param {string} key
|
* @param {string} key
|
||||||
* @param {string} hash
|
* @param {string} hash
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function keyHash(key, token) {
|
function keyHash(key, token) {
|
||||||
var hmac = new sjcl.misc.hmac(key, sjcl.hash.sha512);
|
var hmac = new sjcl.misc.hmac(key, sjcl.hash.sha512);
|
||||||
return sjcl.codec.hex.fromBits(sjcl.bitArray.bitSlice(hmac.encrypt(token), 0, 256));
|
return sjcl.codec.hex.fromBits(sjcl.bitArray.bitSlice(hmac.encrypt(token), 0, 256));
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/****** exposed functions ******/
|
/****** exposed functions ******/
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* KEY DERIVATION FUNCTION
|
* KEY DERIVATION FUNCTION
|
||||||
*
|
*
|
||||||
* This service takes care of the key derivation, i.e. converting low-entropy
|
* This service takes care of the key derivation, i.e. converting low-entropy
|
||||||
* secret into higher entropy secret via either computationally expensive
|
* secret into higher entropy secret via either computationally expensive
|
||||||
* processes or peer-assisted key derivation (PAKDF).
|
* processes or peer-assisted key derivation (PAKDF).
|
||||||
|
*
|
||||||
* @param {object} opts
|
* @param {object} opts
|
||||||
* @param {string} purpose - Key type/purpose
|
* @param {string} purpose - Key type/purpose
|
||||||
* @param {string} username
|
* @param {string} username
|
||||||
* @param {string} secret - Also known as passphrase/password
|
* @param {string} secret - Also known as passphrase/password
|
||||||
* @param {function} fn
|
* @param {function} fn
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Crypt.derive = function(opts, purpose, username, secret, fn) {
|
Crypt.derive = function(opts, purpose, username, secret, fn) {
|
||||||
|
|
||||||
var tokens;
|
var tokens;
|
||||||
if (purpose=='login') tokens = ['id', 'crypt'];
|
|
||||||
else tokens = ['unlock'];
|
|
||||||
|
|
||||||
var iExponent = new sjcl.bn(String(opts.exponent)),
|
if (purpose === 'login') {
|
||||||
iModulus = new sjcl.bn(String(opts.modulus)),
|
tokens = ['id', 'crypt'];
|
||||||
iAlpha = new sjcl.bn(String(opts.alpha));
|
} else {
|
||||||
|
tokens = ['unlock'];
|
||||||
|
}
|
||||||
|
|
||||||
var publicInfo = "PAKDF_1_0_0:"+opts.host.length+":"+opts.host+
|
var iExponent = new sjcl.bn(String(opts.exponent));
|
||||||
":"+username.length+":"+username+
|
var iModulus = new sjcl.bn(String(opts.modulus));
|
||||||
":"+purpose.length+":"+purpose+
|
var iAlpha = new sjcl.bn(String(opts.alpha));
|
||||||
":",
|
|
||||||
publicSize = Math.ceil(Math.min((7+iModulus.bitLength()) >>> 3, 256)/8),
|
var publicInfo = [ 'PAKDF_1_0_0', opts.host.length, opts.host, username.length, username, purpose.length, purpose ].join(':') + ':';
|
||||||
publicHash = fdh(publicInfo, publicSize),
|
var publicSize = Math.ceil(Math.min((7 + iModulus.bitLength()) >>> 3, 256) / 8);
|
||||||
publicHex = sjcl.codec.hex.fromBits(publicHash),
|
var publicHash = fdh(publicInfo, publicSize);
|
||||||
iPublic = new sjcl.bn(String(publicHex)).setBitM(0),
|
var publicHex = sjcl.codec.hex.fromBits(publicHash);
|
||||||
secretInfo = publicInfo+":"+secret.length+":"+secret+":",
|
var iPublic = new sjcl.bn(String(publicHex)).setBitM(0);
|
||||||
secretSize = (7+iModulus.bitLength()) >>> 3,
|
var secretInfo = [ publicInfo, secret.length, secret ].join(':') + ':';
|
||||||
secretHash = fdh(secretInfo, secretSize),
|
var secretSize = (7 + iModulus.bitLength()) >>> 3;
|
||||||
secretHex = sjcl.codec.hex.fromBits(secretHash),
|
var secretHash = fdh(secretInfo, secretSize);
|
||||||
iSecret = new sjcl.bn(String(secretHex)).mod(iModulus);
|
var secretHex = sjcl.codec.hex.fromBits(secretHash);
|
||||||
|
var iSecret = new sjcl.bn(String(secretHex)).mod(iModulus);
|
||||||
|
|
||||||
if (iSecret.jacobi(iModulus) !== 1) {
|
if (iSecret.jacobi(iModulus) !== 1) {
|
||||||
iSecret = iSecret.mul(iAlpha).mod(iModulus);
|
iSecret = iSecret.mul(iAlpha).mod(iModulus);
|
||||||
}
|
}
|
||||||
|
|
||||||
var iRandom;
|
var iRandom;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
iRandom = sjcl.bn.random(iModulus, 0);
|
iRandom = sjcl.bn.random(iModulus, 0);
|
||||||
if (iRandom.jacobi(iModulus) === 1)
|
if (iRandom.jacobi(iModulus) === 1) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var iBlind = iRandom.powermodMontgomery(iPublic.mul(iExponent), iModulus);
|
var iBlind = iRandom.powermodMontgomery(iPublic.mul(iExponent), iModulus);
|
||||||
var iSignreq = iSecret.mulmod(iBlind, iModulus);
|
var iSignreq = iSecret.mulmod(iBlind, iModulus);
|
||||||
var signreq = sjcl.codec.hex.fromBits(iSignreq.toBits());
|
var signreq = sjcl.codec.hex.fromBits(iSignreq.toBits());
|
||||||
|
|
||||||
request.post(opts.url)
|
request.post(opts.url)
|
||||||
.send({
|
.send({ info: publicInfo, signreq: signreq })
|
||||||
info : publicInfo,
|
.end(function(err, resp) {
|
||||||
signreq : signreq
|
if (err || !resp) {
|
||||||
}).end(function(err, resp) {
|
return fn(new Error('Could not query PAKDF server ' + opts.host));
|
||||||
|
}
|
||||||
if (err || !resp) return fn(new Error("Could not query PAKDF server "+opts.host));
|
|
||||||
|
|
||||||
var data = resp.body || resp.text ? JSON.parse(resp.text) : {};
|
var data = resp.body || resp.text ? JSON.parse(resp.text) : {};
|
||||||
|
|
||||||
if (data.result !== 'success') return fn(new Error("Could not query PAKDF server "+opts.host));
|
if (data.result !== 'success') {
|
||||||
|
return fn(new Error('Could not query PAKDF server '+opts.host));
|
||||||
|
}
|
||||||
|
|
||||||
var iSignres = new sjcl.bn(String(data.signres));
|
var iSignres = new sjcl.bn(String(data.signres));
|
||||||
var iRandomInv = iRandom.inverseMod(iModulus);
|
var iRandomInv = iRandom.inverseMod(iModulus);
|
||||||
@@ -136,9 +141,10 @@ Crypt.derive = function (opts, purpose, username, secret, fn) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Imported from ripple-client
|
* Imported from ripple-client
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Crypt.RippleAddress = (function() {
|
Crypt.RippleAddress = (function() {
|
||||||
|
|
||||||
function append_int(a, i) {
|
function append_int(a, i) {
|
||||||
return [].concat(a, i >> 24, (i >> 16) & 0xff, (i >> 8) & 0xff, i & 0xff);
|
return [].concat(a, i >> 24, (i >> 16) & 0xff, (i >> 8) & 0xff, i & 0xff);
|
||||||
}
|
}
|
||||||
@@ -148,23 +154,24 @@ Crypt.RippleAddress = (function () {
|
|||||||
sjcl.hash.sha512.hash(sjcl.codec.bytes.toBits(bytes)),
|
sjcl.hash.sha512.hash(sjcl.codec.bytes.toBits(bytes)),
|
||||||
0, 256
|
0, 256
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
function SHA256_RIPEMD160(bits) {
|
function SHA256_RIPEMD160(bits) {
|
||||||
return sjcl.hash.ripemd160.hash(sjcl.hash.sha256.hash(bits));
|
return sjcl.hash.ripemd160.hash(sjcl.hash.sha256.hash(bits));
|
||||||
}
|
};
|
||||||
|
|
||||||
return function(seed) {
|
return function(seed) {
|
||||||
this.seed = base.decode_check(33, seed);
|
this.seed = base.decode_check(33, seed);
|
||||||
|
|
||||||
if (!this.seed) {
|
if (!this.seed) {
|
||||||
throw "Invalid seed.";
|
throw new Error('Invalid seed.');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.getAddress = function(seq) {
|
this.getAddress = function(seq) {
|
||||||
seq = seq || 0;
|
seq = seq || 0;
|
||||||
|
|
||||||
var private_gen, public_gen, i = 0;
|
var private_gen, public_gen, i = 0;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
private_gen = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(this.seed, i)));
|
private_gen = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(this.seed, i)));
|
||||||
i++;
|
i++;
|
||||||
@@ -174,6 +181,7 @@ Crypt.RippleAddress = (function () {
|
|||||||
|
|
||||||
var sec;
|
var sec;
|
||||||
i = 0;
|
i = 0;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
sec = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(append_int(public_gen.toBytesCompressed(), seq), i)));
|
sec = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(append_int(public_gen.toBytesCompressed(), seq), i)));
|
||||||
i++;
|
i++;
|
||||||
@@ -188,11 +196,12 @@ Crypt.RippleAddress = (function () {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt data
|
* Encrypt data
|
||||||
* @params {string} key
|
*
|
||||||
* @params {string} data
|
* @param {string} key
|
||||||
|
* @param {string} data
|
||||||
*/
|
*/
|
||||||
Crypt.encrypt = function(key, data)
|
|
||||||
{
|
Crypt.encrypt = function(key, data) {
|
||||||
key = sjcl.codec.hex.toBits(key);
|
key = sjcl.codec.hex.toBits(key);
|
||||||
|
|
||||||
var opts = extend(true, {}, cryptConfig);
|
var opts = extend(true, {}, cryptConfig);
|
||||||
@@ -208,12 +217,13 @@ Crypt.encrypt = function(key, data)
|
|||||||
return sjcl.codec.base64.fromBits(encryptedBits);
|
return sjcl.codec.base64.fromBits(encryptedBits);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt data
|
* Decrypt data
|
||||||
* @params {string} key
|
*
|
||||||
* @params {string} data
|
* @param {string} key
|
||||||
|
* @param {string} data
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Crypt.decrypt = function (key, data) {
|
Crypt.decrypt = function (key, data) {
|
||||||
|
|
||||||
key = sjcl.codec.hex.toBits(key);
|
key = sjcl.codec.hex.toBits(key);
|
||||||
@@ -222,7 +232,7 @@ Crypt.decrypt = function (key, data) {
|
|||||||
var version = sjcl.bitArray.extract(encryptedBits, 0, 8);
|
var version = sjcl.bitArray.extract(encryptedBits, 0, 8);
|
||||||
|
|
||||||
if (version !== 0) {
|
if (version !== 0) {
|
||||||
throw new Error("Unsupported encryption version: "+version);
|
throw new Error('Unsupported encryption version: '+version);
|
||||||
}
|
}
|
||||||
|
|
||||||
var encrypted = extend(true, {}, cryptConfig, {
|
var encrypted = extend(true, {}, cryptConfig, {
|
||||||
@@ -236,25 +246,28 @@ Crypt.decrypt = function (key, data) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate a ripple address
|
* Validate a ripple address
|
||||||
|
*
|
||||||
* @param {string} address
|
* @param {string} address
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Crypt.isValidAddress = function (address) {
|
Crypt.isValidAddress = function (address) {
|
||||||
return UInt160.is_valid(address);
|
return UInt160.is_valid(address);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate a ripple address
|
* Validate a ripple address
|
||||||
|
*
|
||||||
* @param {integer} nWords - number of words
|
* @param {integer} nWords - number of words
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Crypt.createSecret = function (nWords) {
|
Crypt.createSecret = function (nWords) {
|
||||||
return sjcl.codec.hex.fromBits(sjcl.random.randomWords(nWords));
|
return sjcl.codec.hex.fromBits(sjcl.random.randomWords(nWords));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new master key
|
* Create a new master key
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Crypt.createMaster = function () {
|
Crypt.createMaster = function () {
|
||||||
return base.encode_check(33, sjcl.codec.bytes.fromBits(sjcl.random.randomWords(4)));
|
return base.encode_check(33, sjcl.codec.bytes.fromBits(sjcl.random.randomWords(4)));
|
||||||
};
|
};
|
||||||
@@ -262,78 +275,89 @@ Crypt.createMaster = function () {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a ripple address from a master key
|
* Create a ripple address from a master key
|
||||||
|
*
|
||||||
* @param {string} masterkey
|
* @param {string} masterkey
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Crypt.getAddress = function (masterkey) {
|
Crypt.getAddress = function (masterkey) {
|
||||||
return new Crypt.RippleAddress(masterkey).getAddress();
|
return new Crypt.RippleAddress(masterkey).getAddress();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hash data
|
* Hash data
|
||||||
|
*
|
||||||
* @param {string} data
|
* @param {string} data
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Crypt.hashSha512 = function (data) {
|
Crypt.hashSha512 = function (data) {
|
||||||
return sjcl.codec.hex.fromBits(sjcl.hash.sha512.hash(data));
|
return sjcl.codec.hex.fromBits(sjcl.hash.sha512.hash(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sign a data string with a secret key
|
* Sign a data string with a secret key
|
||||||
|
*
|
||||||
* @param {string} secret
|
* @param {string} secret
|
||||||
* @param {string} data
|
* @param {string} data
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Crypt.signString = function(secret, data) {
|
Crypt.signString = function(secret, data) {
|
||||||
var hmac = new sjcl.misc.hmac(sjcl.codec.hex.toBits(secret), sjcl.hash.sha512);
|
var hmac = new sjcl.misc.hmac(sjcl.codec.hex.toBits(secret), sjcl.hash.sha512);
|
||||||
return sjcl.codec.hex.fromBits(hmac.mac(data));
|
return sjcl.codec.hex.fromBits(hmac.mac(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an an accout recovery key
|
* Create an an accout recovery key
|
||||||
|
*
|
||||||
* @param {string} secret
|
* @param {string} secret
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Crypt.deriveRecoveryEncryptionKeyFromSecret = function(secret) {
|
Crypt.deriveRecoveryEncryptionKeyFromSecret = function(secret) {
|
||||||
var seed = ripple.Seed.from_json(secret).to_bits();
|
var seed = ripple.Seed.from_json(secret).to_bits();
|
||||||
var hmac = new sjcl.misc.hmac(seed, sjcl.hash.sha512);
|
var hmac = new sjcl.misc.hmac(seed, sjcl.hash.sha512);
|
||||||
var key = hmac.mac("ripple/hmac/recovery_encryption_key/v1");
|
var key = hmac.mac('ripple/hmac/recovery_encryption_key/v1');
|
||||||
key = sjcl.bitArray.bitSlice(key, 0, 256);
|
key = sjcl.bitArray.bitSlice(key, 0, 256);
|
||||||
return sjcl.codec.hex.fromBits(key);
|
return sjcl.codec.hex.fromBits(key);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert base64 encoded data into base64url encoded data.
|
* Convert base64 encoded data into base64url encoded data.
|
||||||
|
*
|
||||||
* @param {String} base64 Data
|
* @param {String} base64 Data
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Crypt.base64ToBase64Url = function(encodedData) {
|
Crypt.base64ToBase64Url = function(encodedData) {
|
||||||
return encodedData.replace(/\+/g, '-').replace(/\//g, '_').replace(/[=]+$/, '');
|
return encodedData.replace(/\+/g, '-').replace(/\//g, '_').replace(/[=]+$/, '');
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert base64url encoded data into base64 encoded data.
|
* Convert base64url encoded data into base64 encoded data.
|
||||||
|
*
|
||||||
* @param {String} base64 Data
|
* @param {String} base64 Data
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Crypt.base64UrlToBase64 = function(encodedData) {
|
Crypt.base64UrlToBase64 = function(encodedData) {
|
||||||
encodedData = encodedData.replace(/-/g, '+').replace(/_/g, '/');
|
encodedData = encodedData.replace(/-/g, '+').replace(/_/g, '/');
|
||||||
|
|
||||||
while (encodedData.length % 4) {
|
while (encodedData.length % 4) {
|
||||||
encodedData += '=';
|
encodedData += '=';
|
||||||
}
|
}
|
||||||
|
|
||||||
return encodedData;
|
return encodedData;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a string from request parameters that
|
* Create a string from request parameters that
|
||||||
* will be used to sign a request
|
* will be used to sign a request
|
||||||
|
*
|
||||||
* @param {Object} config - request params
|
* @param {Object} config - request params
|
||||||
* @param {Object} parsed - parsed url
|
* @param {Object} parsed - parsed url
|
||||||
* @param {Object} date
|
* @param {Object} date
|
||||||
* @param {Object} mechanism - type of signing
|
* @param {Object} mechanism - type of signing
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Crypt.getStringToSign = function(config, parsed, date, mechanism) {
|
Crypt.getStringToSign = function(config, parsed, date, mechanism) {
|
||||||
// XXX This method doesn't handle signing GET requests correctly. The data
|
// XXX This method doesn't handle signing GET requests correctly. The data
|
||||||
// field will be merged into the search string, not the request body.
|
// field will be merged into the search string, not the request body.
|
||||||
|
|
||||||
// Sort the properties of the JSON object into canonical form
|
// Sort the properties of the JSON object into canonical form
|
||||||
var canonicalData = JSON.stringify(copyObjectWithSortedKeys(config.data));
|
var canonicalData = JSON.stringify(copyObjectWithSortedKeys(config.data));
|
||||||
|
|
||||||
@@ -362,13 +386,14 @@ Crypt.getStringToSign = function (config, parsed, date, mechanism) {
|
|||||||
].join('\n');
|
].join('\n');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HMAC signed request
|
* HMAC signed request
|
||||||
|
*
|
||||||
* @param {Object} config
|
* @param {Object} config
|
||||||
* @param {Object} auth_secret
|
* @param {Object} auth_secret
|
||||||
* @param {Object} blob_id
|
* @param {Object} blob_id
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Crypt.signRequestHmac = function(config, auth_secret, blob_id) {
|
Crypt.signRequestHmac = function(config, auth_secret, blob_id) {
|
||||||
config = extend(true, {}, config);
|
config = extend(true, {}, config);
|
||||||
|
|
||||||
@@ -379,22 +404,26 @@ Crypt.signRequestHmac = function (config, auth_secret, blob_id) {
|
|||||||
var stringToSign = Crypt.getStringToSign(config, parsed, date, signatureType);
|
var stringToSign = Crypt.getStringToSign(config, parsed, date, signatureType);
|
||||||
var signature = Crypt.signString(auth_secret, stringToSign);
|
var signature = Crypt.signString(auth_secret, stringToSign);
|
||||||
|
|
||||||
config.url += (parsed.search ? "&" : "?") +
|
var query = querystring.stringify({
|
||||||
'signature='+Crypt.base64ToBase64Url(signature)+
|
signature: Crypt.base64ToBase64Url(signature),
|
||||||
'&signature_date='+date+
|
signature_date: date,
|
||||||
'&signature_blob_id='+blob_id+
|
signature_blob_id: blob_id,
|
||||||
'&signature_type='+signatureType;
|
signature_type: signatureType
|
||||||
|
});
|
||||||
|
|
||||||
|
config.url += (parsed.search ? '&' : '?') + query;
|
||||||
return config;
|
return config;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asymmetric signed request
|
* Asymmetric signed request
|
||||||
|
*
|
||||||
* @param {Object} config
|
* @param {Object} config
|
||||||
* @param {Object} secretKey
|
* @param {Object} secretKey
|
||||||
* @param {Object} account
|
* @param {Object} account
|
||||||
* @param {Object} blob_id
|
* @param {Object} blob_id
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Crypt.signRequestAsymmetric = function(config, secretKey, account, blob_id) {
|
Crypt.signRequestAsymmetric = function(config, secretKey, account, blob_id) {
|
||||||
config = extend(true, {}, config);
|
config = extend(true, {}, config);
|
||||||
|
|
||||||
@@ -405,65 +434,71 @@ Crypt.signRequestAsymmetric = function (config, secretKey, account, blob_id) {
|
|||||||
var stringToSign = Crypt.getStringToSign(config, parsed, date, signatureType);
|
var stringToSign = Crypt.getStringToSign(config, parsed, date, signatureType);
|
||||||
var signature = message.signMessage(stringToSign, secretKey);
|
var signature = message.signMessage(stringToSign, secretKey);
|
||||||
|
|
||||||
config.url += (parsed.search ? "&" : "?") +
|
var query = querystring.stringify({
|
||||||
'signature='+Crypt.base64ToBase64Url(signature)+
|
signature: Crypt.base64ToBase64Url(signature),
|
||||||
'&signature_date='+date+
|
signature_date: date,
|
||||||
'&signature_blob_id='+blob_id+
|
signature_blob_id: blob_id,
|
||||||
'&signature_account='+account+
|
signature_account: account,
|
||||||
'&signature_type='+signatureType;
|
signature_type: signatureType
|
||||||
|
})
|
||||||
|
|
||||||
|
config.url += (parsed.search ? '&' : '?') + query;
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
//prepare for signing
|
//prepare for signing
|
||||||
function copyObjectWithSortedKeys(object) {
|
function copyObjectWithSortedKeys(object) {
|
||||||
if (isPlainObject(object)) {
|
if (isPlainObject(object)) {
|
||||||
var newObj = {};
|
var newObj = {};
|
||||||
var keysSorted = Object.keys(object).sort();
|
var keysSorted = Object.keys(object).sort();
|
||||||
var key;
|
var key;
|
||||||
|
|
||||||
for (var i in keysSorted) {
|
for (var i in keysSorted) {
|
||||||
key = keysSorted[i];
|
key = keysSorted[i];
|
||||||
if (Object.prototype.hasOwnProperty.call(object, key)) {
|
if (Object.prototype.hasOwnProperty.call(object, key)) {
|
||||||
newObj[key] = copyObjectWithSortedKeys(object[key]);
|
newObj[key] = copyObjectWithSortedKeys(object[key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return newObj;
|
return newObj;
|
||||||
} else if (Array.isArray(object)) {
|
} else if (Array.isArray(object)) {
|
||||||
return object.map(copyObjectWithSortedKeys);
|
return object.map(copyObjectWithSortedKeys);
|
||||||
} else {
|
} else {
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
//from npm extend
|
//from npm extend
|
||||||
function isPlainObject(obj) {
|
function isPlainObject(obj) {
|
||||||
var hasOwn = Object.prototype.hasOwnProperty;
|
var hasOwn = Object.prototype.hasOwnProperty;
|
||||||
var toString = Object.prototype.toString;
|
var toString = Object.prototype.toString;
|
||||||
|
|
||||||
if (!obj || toString.call(obj) !== '[object Object]' || obj.nodeType || obj.setInterval)
|
if (!obj || toString.call(obj) !== '[object Object]' || obj.nodeType || obj.setInterval) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var has_own_constructor = hasOwn.call(obj, 'constructor');
|
var has_own_constructor = hasOwn.call(obj, 'constructor');
|
||||||
var has_is_property_of_method = hasOwn.call(obj.constructor.prototype, 'isPrototypeOf');
|
var has_is_property_of_method = hasOwn.call(obj.constructor.prototype, 'isPrototypeOf');
|
||||||
|
|
||||||
// Not own constructor property must be Object
|
// Not own constructor property must be Object
|
||||||
if (obj.constructor && !has_own_constructor && !has_is_property_of_method)
|
if (obj.constructor && !has_own_constructor && !has_is_property_of_method) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Own properties are enumerated firstly, so to speed up,
|
// Own properties are enumerated firstly, so to speed up,
|
||||||
// if last one is own, then all properties are own.
|
// if last one is own, then all properties are own.
|
||||||
var key;
|
var key;
|
||||||
|
|
||||||
for ( key in obj ) {}
|
for ( key in obj ) {}
|
||||||
|
|
||||||
return key === undefined || hasOwn.call( obj, key );
|
return key === void(0) || hasOwn.call( obj, key );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var dateAsIso8601 = (function() {
|
var dateAsIso8601 = (function() {
|
||||||
function pad(n) {
|
function pad(n) {
|
||||||
return (n < 0 || n > 9 ? "" : "0") + n;
|
return (n < 0 || n > 9 ? '' : '0') + n;
|
||||||
}
|
};
|
||||||
|
|
||||||
return function dateAsIso8601() {
|
return function dateAsIso8601() {
|
||||||
var date = new Date();
|
var date = new Date();
|
||||||
@@ -476,5 +511,5 @@ var dateAsIso8601 = (function () {
|
|||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
exports.Crypt = Crypt;
|
||||||
|
|
||||||
module.exports.Crypt = Crypt;
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,16 +1,33 @@
|
|||||||
var request = require('superagent');
|
var superagent = require('superagent');
|
||||||
|
|
||||||
|
|
||||||
function RippleTxt() {
|
function RippleTxt() {
|
||||||
this.txts = { };
|
this.txts = { };
|
||||||
}
|
};
|
||||||
|
|
||||||
|
RippleTxt.urlTemplates = [
|
||||||
|
'https://ripple.{{domain}}/ripple.txt',
|
||||||
|
'https://www.{{domain}}/ripple.txt',
|
||||||
|
'https://{{domain}}/ripple.txt',
|
||||||
|
'http://ripple.{{domain}}/ripple.txt',
|
||||||
|
'http://www.{{domain}}/ripple.txt',
|
||||||
|
'http://{{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
|
* Gets the ripple.txt file for the given domain
|
||||||
|
*
|
||||||
* @param {string} domain - Domain to retrieve file from
|
* @param {string} domain - Domain to retrieve file from
|
||||||
* @param {function} fn - Callback function
|
* @param {function} fn - Callback function
|
||||||
*/
|
*/
|
||||||
|
|
||||||
RippleTxt.prototype.get = function(domain, fn) {
|
RippleTxt.prototype.get = function(domain, fn) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
@@ -18,47 +35,47 @@ RippleTxt.prototype.get = function (domain, fn) {
|
|||||||
return fn(null, self.txts[domain]);
|
return fn(null, self.txts[domain]);
|
||||||
}
|
}
|
||||||
|
|
||||||
var urls = [
|
;(function nextUrl(i) {
|
||||||
'https://ripple.'+domain+'/ripple.txt',
|
var url = RippleTxt.urlTemplates[i];
|
||||||
'https://www.'+domain+'/ripple.txt',
|
|
||||||
'https://'+domain+'/ripple.txt',
|
|
||||||
'http://ripple.'+domain+'/ripple.txt',
|
|
||||||
'http://www.'+domain+'/ripple.txt',
|
|
||||||
'http://'+domain+'/ripple.txt'
|
|
||||||
].reverse();
|
|
||||||
|
|
||||||
next();
|
if (!url) {
|
||||||
function next () {
|
return fn(new Error('No ripple.txt found'));
|
||||||
if (!urls.length) return fn(new Error("No ripple.txt found"));
|
}
|
||||||
var url = urls.pop();
|
|
||||||
|
|
||||||
request.get(url, function(err, resp) {
|
url = url.replace('{{domain}}', domain);
|
||||||
if (err || !resp.text) return next();
|
|
||||||
|
self.request(url, function(err, resp) {
|
||||||
|
if (err || !resp.text) {
|
||||||
|
return nextUrl(++i);
|
||||||
|
}
|
||||||
|
|
||||||
var sections = self.parse(resp.text);
|
var sections = self.parse(resp.text);
|
||||||
self.txts[domain] = sections;
|
self.txts[domain] = sections;
|
||||||
|
|
||||||
fn(null, sections);
|
fn(null, sections);
|
||||||
});
|
});
|
||||||
}
|
})(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse a ripple.txt file
|
* Parse a ripple.txt file
|
||||||
|
*
|
||||||
* @param {string} txt - Unparsed ripple.txt data
|
* @param {string} txt - Unparsed ripple.txt data
|
||||||
*/
|
*/
|
||||||
|
|
||||||
RippleTxt.prototype.parse = function(txt) {
|
RippleTxt.prototype.parse = function(txt) {
|
||||||
|
var txt = txt.replace(/\r?\n/g, '\n').split('\n')
|
||||||
|
var currentSection = '';
|
||||||
|
var sections = { };
|
||||||
|
|
||||||
txt = txt.replace('\r\n', '\n');
|
|
||||||
txt = txt.replace('\r', '\n');
|
|
||||||
txt = txt.split('\n');
|
|
||||||
|
|
||||||
var currentSection = "", sections = {};
|
|
||||||
for (var i = 0, l = txt.length; i < l; i++) {
|
for (var i = 0, l = txt.length; i < l; i++) {
|
||||||
var line = txt[i];
|
var line = txt[i];
|
||||||
|
|
||||||
if (!line.length || line[0] === '#') {
|
if (!line.length || line[0] === '#') {
|
||||||
continue;
|
continue;
|
||||||
} else if (line[0] === '[' && line[line.length-1] === ']') {
|
}
|
||||||
|
|
||||||
|
if (line[0] === '[' && line[line.length - 1] === ']') {
|
||||||
currentSection = line.slice(1, line.length - 1);
|
currentSection = line.slice(1, line.length - 1);
|
||||||
sections[currentSection] = [];
|
sections[currentSection] = [];
|
||||||
} else {
|
} else {
|
||||||
@@ -72,4 +89,4 @@ RippleTxt.prototype.parse = function (txt) {
|
|||||||
return sections;
|
return sections;
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.RippleTxt = RippleTxt;
|
exports.RippleTxt = RippleTxt;
|
||||||
|
|||||||
@@ -7,17 +7,19 @@ var log = require('./log').internal.sub('server');
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @constructor Server
|
* @constructor Server
|
||||||
|
*
|
||||||
* @param {Remote} Reference to a Remote object
|
* @param {Remote} Reference to a Remote object
|
||||||
* @param {Object} Options
|
* @param {Object} Options
|
||||||
*
|
* @param {String} host
|
||||||
* host: String
|
* @param {Number|String} port
|
||||||
* port: String or Number
|
* @param [Boolean] securec
|
||||||
* secure: Boolean
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function Server(remote, opts) {
|
function Server(remote, opts) {
|
||||||
EventEmitter.call(this);
|
EventEmitter.call(this);
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
if (typeof opts === 'string') {
|
if (typeof opts === 'string') {
|
||||||
var parsedUrl = url.parse(opts);
|
var parsedUrl = url.parse(opts);
|
||||||
opts = {
|
opts = {
|
||||||
@@ -31,16 +33,6 @@ function Server(remote, opts) {
|
|||||||
throw new TypeError('Server configuration is not an Object');
|
throw new TypeError('Server configuration is not an Object');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!opts.host) {
|
|
||||||
opts.host = opts.websocket_ip;
|
|
||||||
}
|
|
||||||
if (!opts.port) {
|
|
||||||
opts.port = opts.websocket_port;
|
|
||||||
}
|
|
||||||
if (!opts.secure) {
|
|
||||||
opts.secure = opts.websocket_ssl;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNaN(opts.port)) {
|
if (isNaN(opts.port)) {
|
||||||
throw new TypeError('Server port must be a number');
|
throw new TypeError('Server port must be a number');
|
||||||
}
|
}
|
||||||
@@ -50,7 +42,7 @@ function Server(remote, opts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof opts.secure !== 'boolean') {
|
if (typeof opts.secure !== 'boolean') {
|
||||||
opts.secure = false;
|
opts.secure = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We want to allow integer strings as valid port numbers for backward compatibility
|
// We want to allow integer strings as valid port numbers for backward compatibility
|
||||||
@@ -66,52 +58,56 @@ function Server(remote, opts) {
|
|||||||
throw new TypeError('Server "secure" configuration is not a Boolean');
|
throw new TypeError('Server "secure" configuration is not a Boolean');
|
||||||
}
|
}
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
this._remote = remote;
|
this._remote = remote;
|
||||||
this._opts = opts;
|
this._opts = opts;
|
||||||
this._host = opts.host;
|
|
||||||
this._port = opts.port;
|
|
||||||
this._secure = opts.secure;
|
|
||||||
this._ws = void(0);
|
this._ws = void(0);
|
||||||
|
|
||||||
this._connected = false;
|
this._connected = false;
|
||||||
this._shouldConnect = false;
|
this._shouldConnect = false;
|
||||||
this._state = 'offline';
|
this._state = 'offline';
|
||||||
|
|
||||||
this._id = 0;
|
this._id = 0;
|
||||||
this._retry = 0;
|
this._retry = 0;
|
||||||
this._requests = { };
|
this._requests = { };
|
||||||
|
|
||||||
this._load_base = 256;
|
this._load_base = 256;
|
||||||
this._load_factor = 256;
|
this._load_factor = 256;
|
||||||
|
|
||||||
|
this._fee = 10;
|
||||||
this._fee_ref = 10;
|
this._fee_ref = 10;
|
||||||
this._fee_base = 10;
|
this._fee_base = 10;
|
||||||
this._reserve_base = void(0);
|
this._reserve_base = void(0);
|
||||||
this._reserve_inc = void(0);
|
this._reserve_inc = void(0);
|
||||||
this._fee_cushion = this._remote.fee_cushion;
|
this._fee_cushion = this._remote.fee_cushion;
|
||||||
|
|
||||||
this._opts.url = (opts.secure ? 'wss://' : 'ws://') + opts.host + ':' + opts.port;
|
this._lastLedgerIndex = NaN;
|
||||||
|
this._lastLedgerClose = NaN;
|
||||||
|
|
||||||
this.on('message', function(message) {
|
this._score = 0;
|
||||||
|
|
||||||
|
this._scoreWeights = {
|
||||||
|
ledgerclose: 5,
|
||||||
|
response: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
this._url = this._opts.url = (this._opts.secure ? 'wss://' : 'ws://')
|
||||||
|
+ this._opts.host + ':' + this._opts.port;
|
||||||
|
|
||||||
|
this.on('message', onMessage);
|
||||||
|
|
||||||
|
function onMessage(message) {
|
||||||
self._handleMessage(message);
|
self._handleMessage(message);
|
||||||
});
|
};
|
||||||
|
|
||||||
this.on('response_subscribe', function(message) {
|
this.on('response_subscribe', onSubscribeResponse);
|
||||||
|
|
||||||
|
function onSubscribeResponse(message) {
|
||||||
self._handleResponseSubscribe(message);
|
self._handleResponseSubscribe(message);
|
||||||
});
|
|
||||||
|
|
||||||
function checkServerActivity() {
|
|
||||||
if (isNaN(self._lastLedgerClose)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var delta = (Date.now() - self._lastLedgerClose);
|
|
||||||
|
|
||||||
if (delta > (1000 * 20)) {
|
|
||||||
self.reconnect();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function setActivityInterval() {
|
function setActivityInterval() {
|
||||||
self._activityInterval = setInterval(checkServerActivity, 1000);
|
var interval = self._checkActivity.bind(self);
|
||||||
|
self._activityInterval = setInterval(interval, 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.on('disconnect', function onDisconnect() {
|
this.on('disconnect', function onDisconnect() {
|
||||||
@@ -119,8 +115,18 @@ function Server(remote, opts) {
|
|||||||
//self.once('ledger_closed', setActivityInterval);
|
//self.once('ledger_closed', setActivityInterval);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.once('ledger_closed', function() {
|
//this.once('ledger_closed', setActivityInterval);
|
||||||
//setActiviyInterval();
|
|
||||||
|
this._remote.on('ledger_closed', function(ledger) {
|
||||||
|
self._updateScore('ledgerclose', ledger);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on('response_ping', function(message, request) {
|
||||||
|
self._updateScore('response', request);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on('load_changed', function(load) {
|
||||||
|
self._updateScore('loadchange', load);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -143,6 +149,23 @@ Server.onlineStates = [
|
|||||||
'full'
|
'full'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the final interface between client code and a socket connection to a
|
||||||
|
* `rippled` server. As such, this is a decent hook point to allow a WebSocket
|
||||||
|
* interface conforming object to be used as a basis to mock rippled. This
|
||||||
|
* avoids the need to bind a websocket server to a port and allows a more
|
||||||
|
* synchronous style of code to represent a client <-> server message sequence.
|
||||||
|
* We can also use this to log a message sequence to a buffer.
|
||||||
|
*
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
|
||||||
|
Server.websocketConstructor = function() {
|
||||||
|
// We require this late, because websocket shims may be loaded after
|
||||||
|
// ripple-lib in the browser
|
||||||
|
return require('ws');
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set server state
|
* Set server state
|
||||||
*
|
*
|
||||||
@@ -172,6 +195,76 @@ Server.prototype._setState = function(state) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that server is still active.
|
||||||
|
*
|
||||||
|
* Server activity is determined by ledger_closed events.
|
||||||
|
* Maximum delay to receive a ledger_closed event is 20s.
|
||||||
|
*
|
||||||
|
* If server is inactive, reconnect
|
||||||
|
*
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
|
||||||
|
Server.prototype._checkActivity = function() {
|
||||||
|
if (!this._connected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNaN(this._lastLedgerClose)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var delta = (Date.now() - this._lastLedgerClose);
|
||||||
|
|
||||||
|
if (delta > (1000 * 25)) {
|
||||||
|
//this.reconnect();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server maintains a score for request prioritization.
|
||||||
|
*
|
||||||
|
* The score is determined by various data including
|
||||||
|
* this server's lag to receive ledger_closed events,
|
||||||
|
* ping response time, and load(fee) change
|
||||||
|
*
|
||||||
|
* @param {String} type
|
||||||
|
* @param {Object} data
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
|
||||||
|
Server.prototype._updateScore = function(type, data) {
|
||||||
|
if (!this._connected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var weight = this._scoreWeights[type] || 1;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'ledgerclose':
|
||||||
|
// Ledger lag
|
||||||
|
var delta = data.ledger_index - this._lastLedgerIndex;
|
||||||
|
if (delta > 0) {
|
||||||
|
this._score += weight * delta;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'response':
|
||||||
|
// Ping lag
|
||||||
|
var delta = Math.floor((Date.now() - data.time) / 200);
|
||||||
|
this._score += weight * delta;
|
||||||
|
break;
|
||||||
|
case 'loadchange':
|
||||||
|
// Load/fee change
|
||||||
|
this._fee = Number(this._computeFee(10));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._score > 1e3) {
|
||||||
|
//this.reconnect();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the remote address for a server.
|
* Get the remote address for a server.
|
||||||
* Incompatible with ripple-lib client build
|
* Incompatible with ripple-lib client build
|
||||||
@@ -186,22 +279,6 @@ Server.prototype._remoteAddress = function() {
|
|||||||
return address;
|
return address;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** This is the final interface between client code and a socket connection to a
|
|
||||||
* `rippled` server. As such, this is a decent hook point to allow a WebSocket
|
|
||||||
* interface conforming object to be used as a basis to mock rippled. This
|
|
||||||
* avoids the need to bind a websocket server to a port and allows a more
|
|
||||||
* synchronous style of code to represent a client <-> server message sequence.
|
|
||||||
* We can also use this to log a message sequence to a buffer.
|
|
||||||
*
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
Server.websocketConstructor = function() {
|
|
||||||
// We require this late, because websocket shims may be loaded after
|
|
||||||
// ripple-lib in the browser
|
|
||||||
return require('ws');
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disconnect from rippled WebSocket server
|
* Disconnect from rippled WebSocket server
|
||||||
*
|
*
|
||||||
@@ -274,7 +351,6 @@ Server.prototype.connect = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
ws.onopen = function onOpen() {
|
ws.onopen = function onOpen() {
|
||||||
// If we are no longer the active socket, simply ignore any event
|
|
||||||
if (ws === self._ws) {
|
if (ws === self._ws) {
|
||||||
self.emit('socket_open');
|
self.emit('socket_open');
|
||||||
// Subscribe to events
|
// Subscribe to events
|
||||||
@@ -283,7 +359,6 @@ Server.prototype.connect = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
ws.onerror = function onError(e) {
|
ws.onerror = function onError(e) {
|
||||||
// If we are no longer the active socket, simply ignore any event
|
|
||||||
if (ws === self._ws) {
|
if (ws === self._ws) {
|
||||||
self.emit('socket_error');
|
self.emit('socket_error');
|
||||||
|
|
||||||
@@ -309,9 +384,7 @@ Server.prototype.connect = function() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Failure to open.
|
|
||||||
ws.onclose = function onClose() {
|
ws.onclose = function onClose() {
|
||||||
// If we are no longer the active socket, simply ignore any event
|
|
||||||
if (ws === self._ws) {
|
if (ws === self._ws) {
|
||||||
if (self._remote.trace) {
|
if (self._remote.trace) {
|
||||||
log.info('onclose:', self._opts.url, ws.readyState);
|
log.info('onclose:', self._opts.url, ws.readyState);
|
||||||
@@ -333,12 +406,16 @@ Server.prototype._retryConnect = function() {
|
|||||||
this._retry += 1;
|
this._retry += 1;
|
||||||
|
|
||||||
var retryTimeout = (this._retry < 40)
|
var retryTimeout = (this._retry < 40)
|
||||||
? (1000 / 20) // First, for 2 seconds: 20 times per second
|
// First, for 2 seconds: 20 times per second
|
||||||
|
? (1000 / 20)
|
||||||
: (this._retry < 40 + 60)
|
: (this._retry < 40 + 60)
|
||||||
? (1000) // Then, for 1 minute: once per second
|
// Then, for 1 minute: once per second
|
||||||
|
? (1000)
|
||||||
: (this._retry < 40 + 60 + 60)
|
: (this._retry < 40 + 60 + 60)
|
||||||
? (10 * 1000) // Then, for 10 minutes: once every 10 seconds
|
// Then, for 10 minutes: once every 10 seconds
|
||||||
: (30 * 1000); // Then: once every 30 seconds
|
? (10 * 1000)
|
||||||
|
// Then: once every 30 seconds
|
||||||
|
: (30 * 1000);
|
||||||
|
|
||||||
function connectionRetry() {
|
function connectionRetry() {
|
||||||
if (self._shouldConnect) {
|
if (self._shouldConnect) {
|
||||||
@@ -365,8 +442,10 @@ Server.prototype._handleClose = function() {
|
|||||||
this.emit('socket_close');
|
this.emit('socket_close');
|
||||||
this._setState('offline');
|
this._setState('offline');
|
||||||
|
|
||||||
|
function noOp() {};
|
||||||
|
|
||||||
// Prevent additional events from this socket
|
// Prevent additional events from this socket
|
||||||
ws.onopen = ws.onerror = ws.onclose = ws.onmessage = function noOp() {};
|
ws.onopen = ws.onerror = ws.onclose = ws.onmessage = noOp;
|
||||||
|
|
||||||
if (self._shouldConnect) {
|
if (self._shouldConnect) {
|
||||||
this._retryConnect();
|
this._retryConnect();
|
||||||
@@ -389,75 +468,122 @@ Server.prototype._handleMessage = function(message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!Server.isValidMessage(message)) {
|
if (!Server.isValidMessage(message)) {
|
||||||
|
this.emit('unexpected', message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case 'ledgerClosed':
|
case 'ledgerClosed':
|
||||||
|
this._handleLedgerClosed(message);
|
||||||
|
break;
|
||||||
|
case 'serverStatus':
|
||||||
|
this._handleServerStatus(message);
|
||||||
|
break;
|
||||||
|
case 'response':
|
||||||
|
this._handleResponse(message);
|
||||||
|
break;
|
||||||
|
case 'path_find':
|
||||||
|
this._handlePathFind(message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Server.prototype._handleLedgerClosed = function(message) {
|
||||||
|
this._lastLedgerIndex = message.ledger_index;
|
||||||
this._lastLedgerClose = Date.now();
|
this._lastLedgerClose = Date.now();
|
||||||
this.emit('ledger_closed', message);
|
this.emit('ledger_closed', message);
|
||||||
break;
|
};
|
||||||
|
|
||||||
case 'serverStatus':
|
Server.prototype._handleServerStatus = function(message) {
|
||||||
// This message is only received when online.
|
// This message is only received when online.
|
||||||
// As we are connected, it is the definitive final state.
|
// As we are connected, it is the definitive final state.
|
||||||
|
var isOnline = ~Server.onlineStates.indexOf(message.server_status);
|
||||||
|
this._setState(isOnline ? 'online' : 'offline');
|
||||||
|
|
||||||
this._setState(~(Server.onlineStates.indexOf(message.server_status)) ? 'online' : 'offline');
|
if (!Server.isLoadStatus(message)) {
|
||||||
|
return;
|
||||||
if (Server.isLoadStatus(message)) {
|
|
||||||
self.emit('load', message, self);
|
|
||||||
self._remote.emit('load', message, self);
|
|
||||||
|
|
||||||
if (message.load_base !== self._load_base || message.load_factor !== self._load_factor) {
|
|
||||||
// Load changed
|
|
||||||
self._load_base = message.load_base;
|
|
||||||
self._load_factor = message.load_factor;
|
|
||||||
self.emit('load_changed', message, self);
|
|
||||||
self._remote.emit('load_changed', message, self);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'response':
|
this.emit('load', message, this);
|
||||||
|
this._remote.emit('load', message, this);
|
||||||
|
|
||||||
|
var loadChanged = message.load_base !== this._load_base
|
||||||
|
|| message.load_factor !== this._load_factor
|
||||||
|
|
||||||
|
if (loadChanged) {
|
||||||
|
this._load_base = message.load_base;
|
||||||
|
this._load_factor = message.load_factor;
|
||||||
|
this.emit('load_changed', message, this);
|
||||||
|
this._remote.emit('load_changed', message, this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Server.prototype._handleResponse = function(message) {
|
||||||
// A response to a request.
|
// A response to a request.
|
||||||
var request = self._requests[message.id];
|
var request = this._requests[message.id];
|
||||||
delete self._requests[message.id];
|
|
||||||
|
delete this._requests[message.id];
|
||||||
|
|
||||||
if (!request) {
|
if (!request) {
|
||||||
if (this._remote.trace) {
|
if (this._remote.trace) {
|
||||||
log.info('UNEXPECTED:', self._opts.url, message);
|
log.info('UNEXPECTED:', this._opts.url, message);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.status === 'success') {
|
if (message.status === 'success') {
|
||||||
if (this._remote.trace) {
|
if (this._remote.trace) {
|
||||||
log.info('response:', self._opts.url, message);
|
log.info('response:', this._opts.url, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
request.emit('success', message.result);
|
var command = request.message.command;
|
||||||
|
var result = message.result;
|
||||||
|
var responseEvent = 'response_' + command;
|
||||||
|
|
||||||
[ self, self._remote ].forEach(function(emitter) {
|
request.emit('success', result);
|
||||||
emitter.emit('response_' + request.message.command, message.result, request, message);
|
|
||||||
|
[ this, this._remote ].forEach(function(emitter) {
|
||||||
|
emitter.emit(responseEvent, result, request, message);
|
||||||
});
|
});
|
||||||
} else if (message.error) {
|
} else if (message.error) {
|
||||||
if (this._remote.trace) {
|
if (this._remote.trace) {
|
||||||
log.info('error:', self._opts.url, message);
|
log.info('error:', this._opts.url, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
request.emit('error', {
|
var error = {
|
||||||
error: 'remoteError',
|
error: 'remoteError',
|
||||||
error_message: 'Remote reported an error.',
|
error_message: 'Remote reported an error.',
|
||||||
remote: message
|
remote: message
|
||||||
});
|
};
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'path_find':
|
request.emit('error', error);
|
||||||
if (this._remote.trace) {
|
|
||||||
log.info('path_find:', self._opts.url, message);
|
|
||||||
}
|
}
|
||||||
break;
|
};
|
||||||
|
|
||||||
|
Server.prototype._handlePathFind = function(message) {
|
||||||
|
if (this._remote.trace) {
|
||||||
|
log.info('path_find:', this._opts.url, message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle subscription response messages. Subscription response
|
||||||
|
* messages indicate that a connection to the server is ready
|
||||||
|
*
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
|
||||||
|
Server.prototype._handleResponseSubscribe = function(message) {
|
||||||
|
if (~(Server.onlineStates.indexOf(message.server_status))) {
|
||||||
|
this._setState('online');
|
||||||
|
}
|
||||||
|
if (Server.isLoadStatus(message)) {
|
||||||
|
this._load_base = message.load_base || 256;
|
||||||
|
this._load_factor = message.load_factor || 256;
|
||||||
|
this._fee_ref = message.fee_ref;
|
||||||
|
this._fee_base = message.fee_base;
|
||||||
|
this._reserve_base = message.reserve_base;
|
||||||
|
this._reserve_inc = message.reserve_inc;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -484,27 +610,6 @@ Server.isLoadStatus = function(message) {
|
|||||||
&& (typeof message.load_factor === 'number');
|
&& (typeof message.load_factor === 'number');
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle subscription response messages. Subscription response
|
|
||||||
* messages indicate that a connection to the server is ready
|
|
||||||
*
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
Server.prototype._handleResponseSubscribe = function(message) {
|
|
||||||
if (~(Server.onlineStates.indexOf(message.server_status))) {
|
|
||||||
this._setState('online');
|
|
||||||
}
|
|
||||||
if (Server.isLoadStatus(message)) {
|
|
||||||
this._load_base = message.load_base || 256;
|
|
||||||
this._load_factor = message.load_factor || 256;
|
|
||||||
this._fee_ref = message.fee_ref;
|
|
||||||
this._fee_base = message.fee_base;
|
|
||||||
this._reserve_base = message.reserve_base;
|
|
||||||
this._reserve_inc = message.reserve_inc;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send JSON message to rippled WebSocket server
|
* Send JSON message to rippled WebSocket server
|
||||||
*
|
*
|
||||||
@@ -544,6 +649,7 @@ Server.prototype._request = function(request) {
|
|||||||
|
|
||||||
request.server = this;
|
request.server = this;
|
||||||
request.message.id = this._id;
|
request.message.id = this._id;
|
||||||
|
request.time = Date.now();
|
||||||
|
|
||||||
this._requests[request.message.id] = request;
|
this._requests[request.message.id] = request;
|
||||||
|
|
||||||
|
|||||||
@@ -1,227 +1,269 @@
|
|||||||
var AuthInfo = require('./authinfo').AuthInfo;
|
var async = require('async');
|
||||||
var blobClient = require('./blob').BlobClient;
|
var blobClient = require('./blob').BlobClient;
|
||||||
|
var AuthInfo = require('./authinfo').AuthInfo;
|
||||||
var crypt = require('./crypt').Crypt;
|
var crypt = require('./crypt').Crypt;
|
||||||
|
|
||||||
|
|
||||||
function VaultClient(opts) {
|
function VaultClient(opts) {
|
||||||
if (!opts) opts = {};
|
if (!opts) {
|
||||||
else if (typeof opts === "string") opts = {domain:opts};
|
opts = { };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof opts === 'string') {
|
||||||
|
opts = { domain: opts };
|
||||||
|
}
|
||||||
|
|
||||||
this.domain = opts.domain || 'ripple.com';
|
this.domain = opts.domain || 'ripple.com';
|
||||||
this.authInfo = new AuthInfo();
|
this.authInfo = new AuthInfo();
|
||||||
this.infos = { };
|
this.infos = { };
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reduce username to standardized form.
|
|
||||||
* Strips whitespace at beginning and end.
|
|
||||||
* @param {string} username - Username to normalize
|
|
||||||
*/
|
|
||||||
VaultClient.prototype.normalizeUsername = function (username) {
|
|
||||||
username = ""+username;
|
|
||||||
username = username.trim();
|
|
||||||
return username;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reduce password to standardized form.
|
|
||||||
* Strips whitespace at beginning and end.
|
|
||||||
* @param {string} password - password to normalize
|
|
||||||
*/
|
|
||||||
VaultClient.prototype.normalizePassword = function (password) {
|
|
||||||
password = ""+password;
|
|
||||||
password = password.trim();
|
|
||||||
return password;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a ripple name from a given account address, if it has one
|
* Get a ripple name from a given account address, if it has one
|
||||||
* @param {string} address - Account address to query
|
* @param {string} address - Account address to query
|
||||||
* @param {string} url - Url of blob vault
|
* @param {string} url - Url of blob vault
|
||||||
*/
|
*/
|
||||||
VaultClient.prototype.getRippleName = function(address, url, fn) {
|
|
||||||
|
|
||||||
|
VaultClient.prototype.getRippleName = function(address, url, callback) {
|
||||||
//use the url from previously retrieved authInfo, if necessary
|
//use the url from previously retrieved authInfo, if necessary
|
||||||
if (!url) return fn(new Error("Blob vault URL is required"));
|
if (!url) {
|
||||||
blobClient.getRippleName(url, address, fn);
|
callback(new Error('Blob vault URL is required'));
|
||||||
|
} else {
|
||||||
|
blobClient.getRippleName(url, address, callback);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authenticate and retrieve a decrypted blob using a ripple name and password
|
* Authenticate and retrieve a decrypted blob using a ripple name and password
|
||||||
|
*
|
||||||
* @param {string} username
|
* @param {string} username
|
||||||
* @param {string} password
|
* @param {string} password
|
||||||
* @param {function} fn - Callback function
|
* @param {function} fn - Callback function
|
||||||
*/
|
*/
|
||||||
VaultClient.prototype.login = function(username, password, fn) {
|
|
||||||
|
VaultClient.prototype.login = function(username, password, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
function getAuthInfo(callback) {
|
||||||
self.authInfo.get(self.domain, username, function(err, authInfo) {
|
self.authInfo.get(self.domain, username, function(err, authInfo) {
|
||||||
if (err) return fn(err);
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
if (authInfo.version !== 3) {
|
if (authInfo.version !== 3) {
|
||||||
return fn(new Error("This wallet is incompatible with this version of the vault-client."));
|
return callback(new Error('This wallet is incompatible with this version of the vault-client.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!authInfo.pakdf) {
|
if (!authInfo.pakdf) {
|
||||||
return fn(new Error("No settings for PAKDF in auth packet."));
|
return callback(new Error('No settings for PAKDF in auth packet.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!authInfo.exists) {
|
if (!authInfo.exists) {
|
||||||
return fn(new Error("User does not exist."));
|
return callback(new Error('User does not exist.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("string" !== typeof authInfo.blobvault) {
|
if (typeof authInfo.blobvault !== 'string') {
|
||||||
return fn(new Error("No blobvault specified in the authinfo."));
|
return callback(new Error('No blobvault specified in the authinfo.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
callback(null, authInfo);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function deriveLoginKeys(authInfo, callback) {
|
||||||
//derive login keys
|
//derive login keys
|
||||||
crypt.derive(authInfo.pakdf, 'login', username.toLowerCase(), password, function(err, keys) {
|
crypt.derive(authInfo.pakdf, 'login', username.toLowerCase(), password, function(err, keys) {
|
||||||
if (err) return fn(err);
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
} else {
|
||||||
|
callback(null, authInfo, keys);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function getBlob(authInfo, keys, callback) {
|
||||||
blobClient.get(authInfo.blobvault, keys.id, keys.crypt, function(err, blob) {
|
blobClient.get(authInfo.blobvault, keys.id, keys.crypt, function(err, blob) {
|
||||||
if (err) return fn(err);
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
self.infos[keys.id] = authInfo; //save for relogin
|
//save for relogin
|
||||||
|
self.infos[keys.id] = authInfo;
|
||||||
|
|
||||||
fn (null, {
|
callback(null, {
|
||||||
blob: blob,
|
blob: blob,
|
||||||
username: authInfo.username,
|
username: authInfo.username,
|
||||||
verified: authInfo.emailVerified
|
verified: authInfo.emailVerified
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var steps = [
|
||||||
|
getAuthInfo,
|
||||||
|
deriveLoginKeys,
|
||||||
|
getBlob
|
||||||
|
];
|
||||||
|
|
||||||
|
async.waterfall(steps, callback);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retreive and decrypt blob using a blob url, id and crypt derived previously.
|
* Retreive and decrypt blob using a blob url, id and crypt derived previously.
|
||||||
|
*
|
||||||
* @param {string} url - Blob vault url
|
* @param {string} url - Blob vault url
|
||||||
* @param {string} id - Blob id from previously retreived blob
|
* @param {string} id - Blob id from previously retreived blob
|
||||||
* @param {string} key - Blob decryption key
|
* @param {string} key - Blob decryption key
|
||||||
* @param {function} fn - Callback function
|
* @param {function} fn - Callback function
|
||||||
*/
|
*/
|
||||||
VaultClient.prototype.relogin = function(url, id, key, fn) {
|
|
||||||
|
|
||||||
|
VaultClient.prototype.relogin = function(url, id, key, callback) {
|
||||||
//use the url from previously retrieved authInfo, if necessary
|
//use the url from previously retrieved authInfo, if necessary
|
||||||
if (!url && this.infos[id]) url = this.infos[id].blobvault;
|
if (!url && this.infos[id]) {
|
||||||
|
url = this.infos[id].blobvault;
|
||||||
|
}
|
||||||
|
|
||||||
if (!url) return fn(new Error("Blob vault URL is required"));
|
if (!url) {
|
||||||
|
return callback(new Error('Blob vault URL is required'));
|
||||||
|
}
|
||||||
|
|
||||||
blobClient.get(url, id, key, function(err, blob) {
|
blobClient.get(url, id, key, function(err, blob) {
|
||||||
if (err) return fn(err);
|
if (err) {
|
||||||
|
callback(err);
|
||||||
fn (null, {
|
} else {
|
||||||
blob : blob,
|
callback (null, { blob: blob });
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt the secret key using a username and password
|
* Decrypt the secret key using a username and password
|
||||||
|
*
|
||||||
* @param {string} username
|
* @param {string} username
|
||||||
* @param {string} password
|
* @param {string} password
|
||||||
* @param {string} encryptSecret
|
* @param {string} encryptSecret
|
||||||
* @param {function} fn - Callback function
|
* @param {function} fn - Callback function
|
||||||
*/
|
*/
|
||||||
VaultClient.prototype.unlock = function(username, password, encryptSecret, fn) {
|
|
||||||
|
VaultClient.prototype.unlock = function(username, password, encryptSecret, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.authInfo.get(self.domain, username, function(err, authInfo){
|
function deriveUnlockKey(authInfo, callback) {
|
||||||
if (err) return fn(err);
|
|
||||||
|
|
||||||
//derive unlock key
|
//derive unlock key
|
||||||
crypt.derive(authInfo.pakdf, 'unlock', username.toLowerCase(), password, function(err, keys) {
|
crypt.derive(authInfo.pakdf, 'unlock', username.toLowerCase(), password, function(err, keys) {
|
||||||
if (err) return fn(err);
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
fn(null, {
|
callback(null, {
|
||||||
keys: keys,
|
keys: keys,
|
||||||
secret: crypt.decrypt(keys.unlock, encryptSecret)
|
secret: crypt.decrypt(keys.unlock, encryptSecret)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
self.authInfo.get(self.domain, username, function(err, authInfo) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
} else {
|
||||||
|
deriveUnlockKey(authInfo, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the decrypted blob and secret key in one step using
|
* Retrieve the decrypted blob and secret key in one step using
|
||||||
* the username and password
|
* the username and password
|
||||||
|
*
|
||||||
* @param {string} username
|
* @param {string} username
|
||||||
* @param {string} password
|
* @param {string} password
|
||||||
* @param {function} fn - Callback function
|
* @param {function} fn - Callback function
|
||||||
*/
|
*/
|
||||||
VaultClient.prototype.loginAndUnlock = function(username, password, fn) {
|
|
||||||
|
VaultClient.prototype.loginAndUnlock = function(username, password, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this.login(username, password, function(err, resp){
|
function deriveUnlockKey(authInfo, blob, callback) {
|
||||||
if (err) return fn(err);
|
|
||||||
|
|
||||||
if (!resp.blob || !resp.blob.encrypted_secret)
|
|
||||||
return fn(new Error("Unable to retrieve blob and secret."));
|
|
||||||
|
|
||||||
if (!resp.blob.id || !resp.blob.key)
|
|
||||||
return fn(new Error("Unable to retrieve keys."));
|
|
||||||
|
|
||||||
//get authInfo via id - would have been saved from login
|
|
||||||
var authInfo = self.infos[resp.blob.id];
|
|
||||||
if (!authInfo) return fn(new Error("Unable to find authInfo"));
|
|
||||||
|
|
||||||
//derive unlock key
|
//derive unlock key
|
||||||
crypt.derive(authInfo.pakdf, 'unlock', username.toLowerCase(), password, function(err, keys) {
|
crypt.derive(authInfo.pakdf, 'unlock', username.toLowerCase(), password, function(err, keys) {
|
||||||
if (err) return fn(err);
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
fn(null, {
|
callback(null, {
|
||||||
blob : resp.blob,
|
blob: blob,
|
||||||
unlock: keys.unlock,
|
unlock: keys.unlock,
|
||||||
secret : crypt.decrypt(keys.unlock, resp.blob.encrypted_secret),
|
secret: crypt.decrypt(keys.unlock, blob.encrypted_secret),
|
||||||
username: authInfo.username,
|
username: authInfo.username,
|
||||||
verified: authInfo.emailVerified
|
verified: authInfo.emailVerified
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.login(username, password, function(err, resp) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resp.blob || !resp.blob.encrypted_secret) {
|
||||||
|
return callback(new Error('Unable to retrieve blob and secret.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resp.blob.id || !resp.blob.key) {
|
||||||
|
return callback(new Error('Unable to retrieve keys.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
//get authInfo via id - would have been saved from login
|
||||||
|
var authInfo = self.infos[resp.blob.id];
|
||||||
|
|
||||||
|
if (!authInfo) {
|
||||||
|
return callback(new Error('Unable to find authInfo'));
|
||||||
|
}
|
||||||
|
|
||||||
|
deriveUnlockKey(authInfo, resp.blob, callback);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check blobvault for existance of username
|
* Check blobvault for existance of username
|
||||||
|
*
|
||||||
* @param {string} username
|
* @param {string} username
|
||||||
* @param {function} fn - Callback function
|
* @param {function} fn - Callback function
|
||||||
*/
|
*/
|
||||||
VaultClient.prototype.exists = function (username, fn) {
|
|
||||||
|
VaultClient.prototype.exists = function(username, callback) {
|
||||||
this.authInfo.get(this.domain, username.toLowerCase(), function(err, authInfo) {
|
this.authInfo.get(this.domain, username.toLowerCase(), function(err, authInfo) {
|
||||||
if (err) return fn(err);
|
if (err) {
|
||||||
return fn(null, !!authInfo.exists);
|
callback(err);
|
||||||
|
} else {
|
||||||
|
callback(null, !!authInfo.exists);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
/*
|
|
||||||
* Verify an email address for an existing user
|
* Verify an email address for an existing user
|
||||||
|
*
|
||||||
* @param {string} username
|
* @param {string} username
|
||||||
* @param {string} token - Verification token
|
* @param {string} token - Verification token
|
||||||
* @param {function} fn - Callback function
|
* @param {function} fn - Callback function
|
||||||
*/
|
*/
|
||||||
VaultClient.prototype.verify = function (username, token, fn) {
|
|
||||||
|
VaultClient.prototype.verify = function(username, token, callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
this.authInfo.get(this.domain, username.toLowerCase(), function(err, authInfo) {
|
this.authInfo.get(this.domain, username.toLowerCase(), function(err, authInfo) {
|
||||||
if (err) return fn(err);
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
if ("string" !== typeof authInfo.blobvault) {
|
|
||||||
return fn(new Error("No blobvault specified in the authinfo."));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
blobClient.verify(authInfo.blobvault, username.toLowerCase(), token, fn);
|
if (typeof authInfo.blobvault !== 'string') {
|
||||||
|
return callback(new Error('No blobvault specified in the authinfo.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
blobClient.verify(authInfo.blobvault, username.toLowerCase(), token, callback);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
/*
|
|
||||||
* Register a new user and save to the blob vault
|
* Register a new user and save to the blob vault
|
||||||
*
|
*
|
||||||
* @param {object} options
|
* @param {object} options
|
||||||
@@ -233,53 +275,77 @@ VaultClient.prototype.verify = function (username, token, fn) {
|
|||||||
* @param {object} options.oldUserBlob //optional
|
* @param {object} options.oldUserBlob //optional
|
||||||
* @param {function} fn
|
* @param {function} fn
|
||||||
*/
|
*/
|
||||||
|
|
||||||
VaultClient.prototype.register = function(options, fn) {
|
VaultClient.prototype.register = function(options, fn) {
|
||||||
var self = this,
|
var self = this;
|
||||||
username = this.normalizeUsername(options.username),
|
var username = String(options.username).trim();
|
||||||
password = this.normalizePassword(options.password);
|
var password = String(options.password).trim();
|
||||||
|
|
||||||
|
|
||||||
|
function getAuthInfo(callback) {
|
||||||
self.authInfo.get(self.domain, username, function(err, authInfo) {
|
self.authInfo.get(self.domain, username, function(err, authInfo) {
|
||||||
|
if (err) {
|
||||||
if (err) return fn(err);
|
return callback(err);
|
||||||
|
|
||||||
if ("string" !== typeof authInfo.blobvault) {
|
|
||||||
return fn(new Error("No blobvault specified in the authinfo."));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof authInfo.blobvault !== 'string') {
|
||||||
|
return callback(new Error('No blobvault specified in the authinfo.'));
|
||||||
|
}
|
||||||
|
|
||||||
if (!authInfo.pakdf) {
|
if (!authInfo.pakdf) {
|
||||||
return fn(new Error("No settings for PAKDF in auth packet."));
|
return callback(new Error('No settings for PAKDF in auth packet.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
//derive login keys
|
callback(null, authInfo);
|
||||||
crypt.derive(authInfo.pakdf, 'login', username.toLowerCase(), password, function(err, loginKeys){
|
});
|
||||||
if (err) return fn(err);
|
};
|
||||||
|
|
||||||
//derive unlock key
|
function deriveKeys(authInfo, callback) {
|
||||||
crypt.derive(authInfo.pakdf, 'unlock', username.toLowerCase(), password, function(err, unlockKeys){
|
// derive unlock and login keys
|
||||||
if (err) return fn(err);
|
var keys = { };
|
||||||
|
|
||||||
|
function deriveKey(keyType, callback) {
|
||||||
|
crypt.derive(authInfo.pakdf, keyType, username.toLowerCase(), password, function(err, key) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
} else {
|
||||||
|
keys[keyType] = key;
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
async.eachSeries([ 'login', 'unlock' ], deriveKey, function(err) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
} else {
|
||||||
|
callback(null, authInfo, keys);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function create(authInfo, keys) {
|
||||||
var params = {
|
var params = {
|
||||||
'url' : authInfo.blobvault,
|
url: authInfo.blobvault,
|
||||||
'id' : loginKeys.id,
|
id: keys.loginKeys.id,
|
||||||
'crypt' : loginKeys.crypt,
|
crypt: keys.loginKeys.crypt,
|
||||||
'unlock' : unlockKeys.unlock,
|
unlock: keys.unlockKeys.unlock,
|
||||||
'username' : username,
|
username: username,
|
||||||
'email' : options.email,
|
email: options.email,
|
||||||
'masterkey' : options.masterkey || crypt.createMaster(),
|
masterkey: options.masterkey || crypt.createMaster(),
|
||||||
'activateLink' : options.activateLink,
|
activateLink: options.activateLink,
|
||||||
'oldUserBlob' : options.oldUserBlob
|
oldUserBlob: options.oldUserBlob
|
||||||
};
|
};
|
||||||
|
|
||||||
blobClient.create(params, function(err, blob) {
|
blobClient.create(params, function(err, blob) {
|
||||||
if (err) return fn(err);
|
if (err) {
|
||||||
fn(null, blob, loginKeys, authInfo.username);
|
callback(err);
|
||||||
});
|
} else {
|
||||||
});
|
callback(null, blob, loginKeys, authInfo.username);
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async.waterfall([ getAuthInfo, deriveKeys ], create);
|
||||||
|
};
|
||||||
|
|
||||||
module.exports.VaultClient = VaultClient;
|
exports.VaultClient = VaultClient;
|
||||||
|
|||||||
@@ -184,14 +184,12 @@ describe('Message', function(){
|
|||||||
account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'
|
account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'
|
||||||
};
|
};
|
||||||
|
|
||||||
Remote.prototype.addServer = function(){};
|
//Remote.prototype.addServer = function(){};
|
||||||
var test_remote = new Remote({});
|
var test_remote = new Remote();
|
||||||
|
|
||||||
assert.throws(function(){
|
assert.throws(function(){
|
||||||
Message.verifyHashSignature(data);
|
Message.verifyHashSignature(data);
|
||||||
}, /(?=.*callback\ function).*/);
|
}, /(?=.*callback\ function).*/);
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should respond with an error if the hash is missing or invalid', function(done){
|
it('should respond with an error if the hash is missing or invalid', function(done){
|
||||||
@@ -202,8 +200,8 @@ describe('Message', function(){
|
|||||||
account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'
|
account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'
|
||||||
};
|
};
|
||||||
|
|
||||||
Remote.prototype.addServer = function(){};
|
//Remote.prototype.addServer = function(){};
|
||||||
var test_remote = new Remote({});
|
var test_remote = new Remote();
|
||||||
test_remote.state = 'online';
|
test_remote.state = 'online';
|
||||||
|
|
||||||
Message.verifyHashSignature(data, test_remote, function(err, valid){
|
Message.verifyHashSignature(data, test_remote, function(err, valid){
|
||||||
@@ -221,8 +219,8 @@ describe('Message', function(){
|
|||||||
signature: 'AAAAHOUJQzG/7BO82fGNt1TNE+GGVXKuQQ0N2nTO+iJETE69PiHnaAkkOzovM177OosxbKjpt3KvwuJflgUB2YGvgjk='
|
signature: 'AAAAHOUJQzG/7BO82fGNt1TNE+GGVXKuQQ0N2nTO+iJETE69PiHnaAkkOzovM177OosxbKjpt3KvwuJflgUB2YGvgjk='
|
||||||
};
|
};
|
||||||
|
|
||||||
Remote.prototype.addServer = function(){};
|
//Remote.prototype.addServer = function(){};
|
||||||
var test_remote = new Remote({});
|
var test_remote = new Remote();
|
||||||
test_remote.state = 'online';
|
test_remote.state = 'online';
|
||||||
|
|
||||||
Message.verifyHashSignature(data, test_remote, function(err, valid){
|
Message.verifyHashSignature(data, test_remote, function(err, valid){
|
||||||
@@ -240,8 +238,8 @@ describe('Message', function(){
|
|||||||
account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'
|
account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'
|
||||||
};
|
};
|
||||||
|
|
||||||
Remote.prototype.addServer = function(){};
|
//Remote.prototype.addServer = function(){};
|
||||||
var test_remote = new Remote({});
|
var test_remote = new Remote();
|
||||||
test_remote.state = 'online';
|
test_remote.state = 'online';
|
||||||
|
|
||||||
Message.verifyHashSignature(data, test_remote, function(err, valid){
|
Message.verifyHashSignature(data, test_remote, function(err, valid){
|
||||||
@@ -260,8 +258,8 @@ describe('Message', function(){
|
|||||||
signature: 'AAAAHMIPCQGLgdnpX1Ccv1wHb56H4NggxIM6U08Qkb9mUjN2Vn9pZ3CHvq1yWLBi6NqpW+7kedLnmfu4VG2+y43p4Xs='
|
signature: 'AAAAHMIPCQGLgdnpX1Ccv1wHb56H4NggxIM6U08Qkb9mUjN2Vn9pZ3CHvq1yWLBi6NqpW+7kedLnmfu4VG2+y43p4Xs='
|
||||||
};
|
};
|
||||||
|
|
||||||
Remote.prototype.addServer = function(){};
|
//Remote.prototype.addServer = function(){};
|
||||||
var test_remote = new Remote({});
|
var test_remote = new Remote();
|
||||||
test_remote.state = 'online';
|
test_remote.state = 'online';
|
||||||
test_remote.request_account_info = function(account, callback) {
|
test_remote.request_account_info = function(account, callback) {
|
||||||
if (account === data.account) {
|
if (account === data.account) {
|
||||||
@@ -296,8 +294,8 @@ describe('Message', function(){
|
|||||||
signature: 'AAAAG+dB/rAjZ5m8eQ/opcqQOJsFbKxOu9jq9KrOAlNO4OdcBDXyCBlkZqS9Xr8oZI2uh0boVsgYOS3pOLJz+Dh3Otk='
|
signature: 'AAAAG+dB/rAjZ5m8eQ/opcqQOJsFbKxOu9jq9KrOAlNO4OdcBDXyCBlkZqS9Xr8oZI2uh0boVsgYOS3pOLJz+Dh3Otk='
|
||||||
};
|
};
|
||||||
|
|
||||||
Remote.prototype.addServer = function(){};
|
//Remote.prototype.addServer = function(){};
|
||||||
var test_remote = new Remote({});
|
var test_remote = new Remote();
|
||||||
test_remote.state = 'online';
|
test_remote.state = 'online';
|
||||||
test_remote.request_account_info = function(account, callback) {
|
test_remote.request_account_info = function(account, callback) {
|
||||||
if (account === data.account) {
|
if (account === data.account) {
|
||||||
|
|||||||
@@ -5,15 +5,15 @@ var Remote = utils.load_module('remote').Remote;
|
|||||||
var Server = utils.load_module('server').Server;
|
var Server = utils.load_module('server').Server;
|
||||||
var Request = utils.load_module('request').Request;
|
var Request = utils.load_module('request').Request;
|
||||||
|
|
||||||
var options, spy, mock, stub, remote, callback;
|
var options, spy, mock, stub, remote, callback, database, tx;
|
||||||
|
|
||||||
describe('Remote', function () {
|
describe('Remote', function () {
|
||||||
describe('initialing a remote with options', function () {
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
options = {
|
options = {
|
||||||
trace : true,
|
trace : true,
|
||||||
trusted: true,
|
trusted: true,
|
||||||
local_signing: true,
|
local_signing: true,
|
||||||
|
|
||||||
servers: [
|
servers: [
|
||||||
{ host: 's-west.ripple.com', port: 443, secure: true },
|
{ host: 's-west.ripple.com', port: 443, secure: true },
|
||||||
{ host: 's-east.ripple.com', port: 443, secure: true }
|
{ host: 's-east.ripple.com', port: 443, secure: true }
|
||||||
@@ -32,13 +32,155 @@ describe('Remote', function () {
|
|||||||
|
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
it('should add a server for each specified', function (done) {
|
|
||||||
var remote = new Remote(options);
|
describe('remote server initialization - url object', function() {
|
||||||
|
it('should construct url', function (done) {
|
||||||
|
var remote = new Remote({
|
||||||
|
servers: [ { host: 's-west.ripple.com', port: 443, secure: true } ],
|
||||||
|
});
|
||||||
|
assert(Array.isArray(remote._servers));
|
||||||
|
assert(remote._servers[0] instanceof Server);
|
||||||
|
assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443');
|
||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
})
|
});
|
||||||
|
|
||||||
describe('functions that return request objects', function () {
|
describe('remote server initialization - url object - no secure property', function() {
|
||||||
|
it('should construct url', function (done) {
|
||||||
|
var remote = new Remote({
|
||||||
|
servers: [ { host: 's-west.ripple.com', port: 443 } ]
|
||||||
|
});
|
||||||
|
assert(Array.isArray(remote._servers));
|
||||||
|
assert(remote._servers[0] instanceof Server);
|
||||||
|
assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443');
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('remote server initialization - url object - secure: false', function() {
|
||||||
|
it('should construct url', function (done) {
|
||||||
|
var remote = new Remote({
|
||||||
|
servers: [ { host: 's-west.ripple.com', port: 443, secure: false } ]
|
||||||
|
});
|
||||||
|
assert(Array.isArray(remote._servers));
|
||||||
|
assert(remote._servers[0] instanceof Server);
|
||||||
|
assert.strictEqual(remote._servers[0]._url, 'ws://s-west.ripple.com:443');
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('remote server initialization - url object - string port', function() {
|
||||||
|
it('should construct url', function (done) {
|
||||||
|
var remote = new Remote({
|
||||||
|
servers: [ { host: 's-west.ripple.com', port: '443', secure: true } ]
|
||||||
|
});
|
||||||
|
assert(Array.isArray(remote._servers));
|
||||||
|
assert(remote._servers[0] instanceof Server);
|
||||||
|
assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443');
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('remote server initialization - url object - invalid host', function() {
|
||||||
|
it('should construct url', function (done) {
|
||||||
|
assert.throws(
|
||||||
|
function() {
|
||||||
|
var remote = new Remote({
|
||||||
|
servers: [ { host: '+', port: 443, secure: true } ]
|
||||||
|
});
|
||||||
|
}, Error);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('remote server initialization - url object - invalid port', function() {
|
||||||
|
it('should construct url', function (done) {
|
||||||
|
assert.throws(
|
||||||
|
function() {
|
||||||
|
var remote = new Remote({
|
||||||
|
servers: [ { host: 's-west.ripple.com', port: null, secure: true } ]
|
||||||
|
});
|
||||||
|
}, TypeError);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('remote server initialization - url object - port out of range', function() {
|
||||||
|
it('should construct url', function (done) {
|
||||||
|
assert.throws(
|
||||||
|
function() {
|
||||||
|
var remote = new Remote({
|
||||||
|
servers: [ { host: 's-west.ripple.com', port: 65537, secure: true } ]
|
||||||
|
});
|
||||||
|
}, Error);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('remote server initialization - url string', function() {
|
||||||
|
it('should construct url', function (done) {
|
||||||
|
var remote = new Remote({
|
||||||
|
servers: [ 'wss://s-west.ripple.com:443' ]
|
||||||
|
});
|
||||||
|
assert(Array.isArray(remote._servers));
|
||||||
|
assert(remote._servers[0] instanceof Server);
|
||||||
|
assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443');
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('remote server initialization - url string - ws://', function() {
|
||||||
|
it('should construct url', function (done) {
|
||||||
|
var remote = new Remote({
|
||||||
|
servers: [ 'ws://s-west.ripple.com:443' ]
|
||||||
|
});
|
||||||
|
assert(Array.isArray(remote._servers));
|
||||||
|
assert(remote._servers[0] instanceof Server);
|
||||||
|
assert.strictEqual(remote._servers[0]._url, 'ws://s-west.ripple.com:443');
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('remote server initialization - url string - invalid host', function() {
|
||||||
|
it('should construct url', function (done) {
|
||||||
|
assert.throws(
|
||||||
|
function() {
|
||||||
|
var remote = new Remote({
|
||||||
|
servers: [ 'ws://+:443' ]
|
||||||
|
});
|
||||||
|
}, Error
|
||||||
|
);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('remote server initialization - url string - invalid port', function() {
|
||||||
|
it('should construct url', function (done) {
|
||||||
|
assert.throws(
|
||||||
|
function() {
|
||||||
|
var remote = new Remote({
|
||||||
|
servers: [ 'ws://s-west.ripple.com:null' ]
|
||||||
|
});
|
||||||
|
}, Error
|
||||||
|
);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('remote server initialization - url string - port out of range', function() {
|
||||||
|
it('should construct url', function (done) {
|
||||||
|
assert.throws(
|
||||||
|
function() {
|
||||||
|
var remote = new Remote({
|
||||||
|
servers: [ 'ws://s-west.ripple.com:65537:' ]
|
||||||
|
});
|
||||||
|
}, Error
|
||||||
|
);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('request constructors', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
callback = function () {}
|
callback = function () {}
|
||||||
remote = new Remote(options);
|
remote = new Remote(options);
|
||||||
@@ -100,4 +242,91 @@ describe('Remote', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('create remote and get pending transactions', function() {
|
||||||
|
before(function() {
|
||||||
|
tx = [{
|
||||||
|
tx_json: {
|
||||||
|
Account : "r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS",
|
||||||
|
Amount : {
|
||||||
|
currency : "LTC",
|
||||||
|
issuer : "r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS",
|
||||||
|
value : "9.985"
|
||||||
|
},
|
||||||
|
Destination : "r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS",
|
||||||
|
Fee : "15",
|
||||||
|
Flags : 0,
|
||||||
|
Paths : [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
account : "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||||
|
currency : "USD",
|
||||||
|
issuer : "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||||
|
type : 49,
|
||||||
|
type_hex : "0000000000000031"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currency : "LTC",
|
||||||
|
issuer : "rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX",
|
||||||
|
type : 48,
|
||||||
|
type_hex : "0000000000000030"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
account : "rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX",
|
||||||
|
currency : "LTC",
|
||||||
|
issuer : "rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX",
|
||||||
|
type : 49,
|
||||||
|
type_hex : "0000000000000031"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
SendMax : {
|
||||||
|
currency : "USD",
|
||||||
|
issuer : "r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS",
|
||||||
|
value : "30.30993068"
|
||||||
|
},
|
||||||
|
Sequence : 415,
|
||||||
|
SigningPubKey : "02854B06CE8F3E65323F89260E9E19B33DA3E01B30EA4CA172612DE77973FAC58A",
|
||||||
|
TransactionType : "Payment",
|
||||||
|
TxnSignature : "304602210096C2F385530587DE573936CA51CB86B801A28F777C944E268212BE7341440B7F022100EBF0508A9145A56CDA7FAF314DF3BBE51C6EE450BA7E74D88516891A3608644E"
|
||||||
|
},
|
||||||
|
clientID: '48631',
|
||||||
|
state: 'pending',
|
||||||
|
submitIndex: 1,
|
||||||
|
submittedIDs: ["304602210096C2F385530587DE573936CA51CB86B801A28F777C944E268212BE7341440B7F022100EBF0508A9145A56CDA7FAF314DF3BBE51C6EE450BA7E74D88516891A3608644E"],
|
||||||
|
secret: 'mysecret'
|
||||||
|
}];
|
||||||
|
database = {
|
||||||
|
getPendingTransactions: function(callback) {
|
||||||
|
callback(null, tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should set transaction members correct ', function(done) {
|
||||||
|
remote = new Remote(options);
|
||||||
|
remote.storage = database;
|
||||||
|
remote.transaction = function() {
|
||||||
|
return {
|
||||||
|
clientID: function(id) {
|
||||||
|
if (typeof id === 'string') {
|
||||||
|
this._clientID = id;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
submit: function() {
|
||||||
|
assert.deepEqual(this._clientID, tx[0].clientID);
|
||||||
|
assert.deepEqual(this.submittedIDs,[tx[0].tx_json.TxnSignature]);
|
||||||
|
assert.equal(this.submitIndex, tx[0].submitIndex);
|
||||||
|
assert.equal(this.secret, tx[0].secret);
|
||||||
|
done();
|
||||||
|
|
||||||
|
},
|
||||||
|
parseJson: function(json) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
remote.getPendingTransactions();
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,44 +1,106 @@
|
|||||||
var assert = require('assert'),
|
var assert = require('assert');
|
||||||
RippleTxt = require('../src/js/ripple/rippletxt').RippleTxt,
|
var RippleTxt = require('../src/js/ripple/rippletxt').RippleTxt;
|
||||||
AuthInfo = require('../src/js/ripple/authinfo').AuthInfo,
|
var AuthInfo = require('../src/js/ripple/authinfo').AuthInfo;
|
||||||
VaultClient = require('../src/js/ripple/vaultclient').VaultClient,
|
var VaultClient = require('../src/js/ripple/vaultclient').VaultClient;
|
||||||
Blob = require('../src/js/ripple/blob').BlobClient.Blob,
|
var Blob = require('../src/js/ripple/blob').Blob;
|
||||||
UInt256 = require('../src/js/ripple/uint256').UInt256;
|
var UInt256 = require('../src/js/ripple/uint256').UInt256;
|
||||||
|
|
||||||
var exampleData = {
|
var exampleData = {
|
||||||
id : "ef203d3e76552c0592384f909e6f61f1d1f02f61f07643ce015d8b0c9710dd2f",
|
id: 'ef203d3e76552c0592384f909e6f61f1d1f02f61f07643ce015d8b0c9710dd2f',
|
||||||
crypt : "f0cc91a7c1091682c245cd8e13c246cc150b2cf98b17dd6ef092019c99dc9d82",
|
crypt: 'f0cc91a7c1091682c245cd8e13c246cc150b2cf98b17dd6ef092019c99dc9d82',
|
||||||
unlock : "3e15fe3218a9c664835a6f585582e14480112110ddbe50e5028d05fc5bd9b5f4",
|
unlock: '3e15fe3218a9c664835a6f585582e14480112110ddbe50e5028d05fc5bd9b5f4',
|
||||||
blobURL : "https://id.staging.ripple.com",
|
blobURL: 'https://id.staging.ripple.com',
|
||||||
username : "exampleUser",
|
username: 'exampleUser',
|
||||||
password : "pass word",
|
password: 'pass word',
|
||||||
domain : "staging.ripple.com",
|
domain: 'staging.ripple.com',
|
||||||
encrypted_secret : "APYqtqvjJk/J324rx2BGGzUiQ3mtmMMhMsbrUmgxb00W2aFVQzCC2mqd58Z17gzeUUcjtjAm"
|
encrypted_secret: 'APYqtqvjJk/J324rx2BGGzUiQ3mtmMMhMsbrUmgxb00W2aFVQzCC2mqd58Z17gzeUUcjtjAm'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var rippleTxtRes = {
|
||||||
|
address: [ 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV' ],
|
||||||
|
validation_public_key: [ 'n9KPnVLn7ewVzHvn218DcEYsnWLzKerTDwhpofhk4Ym1RUq4TeGw' ],
|
||||||
|
domain: [ 'ripple.com' ],
|
||||||
|
ips: [
|
||||||
|
'23.21.167.100 51235',
|
||||||
|
'23.23.201.55 51235',
|
||||||
|
'107.21.116.214 51235'
|
||||||
|
],
|
||||||
|
validators: [
|
||||||
|
'n9KPnVLn7ewVzHvn218DcEYsnWLzKerTDwhpofhk4Ym1RUq4TeGw',
|
||||||
|
'n9LFzWuhKNvXStHAuemfRKFVECLApowncMAM5chSCL9R5ECHGN4V',
|
||||||
|
'n94rSdgTyBNGvYg8pZXGuNt59Y5bGAZGxbxyvjDaqD9ceRAgD85P',
|
||||||
|
'redstem.com'
|
||||||
|
],
|
||||||
|
authinfo_url: [
|
||||||
|
'https://id.staging.ripple.com/v1/authinfo'
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; //must be set for self signed certs
|
var authInfoRes = {
|
||||||
|
body: {
|
||||||
|
version: 3,
|
||||||
|
blobvault: 'https://id.staging.ripple.com',
|
||||||
|
pakdf: {
|
||||||
|
modulus: 'c7f1bc1dfb1be82d244aef01228c1409c1988943ca9e21431f1669b4aa3864c9f37f3d51b2b4ba1ab9e80f59d267fda1521e88b05117993175e004543c6e3611242f24432ce8efa3b81f0ff660b4f91c5d52f2511a6f38181a7bf9abeef72db056508bbb4eeb5f65f161dd2d5b439655d2ae7081fcc62fdcb281520911d96700c85cdaf12e7d1f15b55ade867240722425198d4ce39019550c4c8a921fc231d3e94297688c2d77cd68ee8fdeda38b7f9a274701fef23b4eaa6c1a9c15b2d77f37634930386fc20ec291be95aed9956801e1c76601b09c413ad915ff03bfdc0b6b233686ae59e8caf11750b509ab4e57ee09202239baee3d6e392d1640185e1cd',
|
||||||
|
alpha: '7283d19e784f48a96062271a5fa6e2c3addf14e6ezf78a4bb61364856d580f13552008d7b9e3b60ebd9555e9f6c7778ec69f976757d206134e54d61ba9d588a7e37a77cf48060522478352d76db000366ef669a1b1ca93c5e3e05bc344afa1e8ccb15d3343da94180dccf590c2c32408c3f3f176c8885e95d988f1565ee9b80c12f72503ab49917792f907bbb9037487b0afed967fefc9ab090164597fcd391c43fab33029b38e66ff4af96cbf6d90a01b891f856ddd3d94e9c9b307fe01e1353a8c30edd5a94a0ebba5fe7161569000ad3b0d3568872d52b6fbdfce987a687e4b346ea702e8986b03b6b1b85536c813e46052a31ed64ec490d3ba38029544aa',
|
||||||
|
url: 'https://auth1.ripple.com/api/sign',
|
||||||
|
exponent: '010001',
|
||||||
|
host: 'auth1.ripple.com'
|
||||||
|
},
|
||||||
|
exists: true,
|
||||||
|
username: 'exampleUser',
|
||||||
|
address: 'raVUps4RghLYkVBcpMaRbVKRTTzhesPXd',
|
||||||
|
emailVerified: true,
|
||||||
|
reserved: false
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
//must be set for self signed certs
|
||||||
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
||||||
|
|
||||||
describe('Ripple Txt', function() {
|
describe('Ripple Txt', function() {
|
||||||
it('should get the context of a ripple.txt file from a given domain', function (done){
|
it('should get the content of a ripple.txt file from a given domain', function(done) {
|
||||||
var rt = new RippleTxt();
|
var rt = new RippleTxt();
|
||||||
rt.get(exampleData.domain, function (err, resp){
|
|
||||||
assert.ifError(err);
|
var requestedUrls = [ ];
|
||||||
assert.equal(typeof resp, 'object');
|
|
||||||
|
var testUrls = RippleTxt.urlTemplates.map(function(template) {
|
||||||
|
return template.replace('{{domain}}', 'localhost');
|
||||||
|
});
|
||||||
|
|
||||||
|
rt.request = function(url, callback) {
|
||||||
|
requestedUrls.push(url);
|
||||||
|
callback(new Error());
|
||||||
|
};
|
||||||
|
|
||||||
|
rt.get('localhost', function(err, resp) {
|
||||||
|
assert(err instanceof Error);
|
||||||
|
assert.strictEqual(resp, void(0));
|
||||||
|
assert.deepEqual(requestedUrls, testUrls);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('AuthInfo', function() {
|
describe('AuthInfo', function() {
|
||||||
|
it('should get auth info', function(done) {
|
||||||
var auth = new AuthInfo();
|
var auth = new AuthInfo();
|
||||||
|
|
||||||
it ('should get authinfo given a domain and username', function (done){
|
auth._getRippleTxt = function(domain, callback) {
|
||||||
auth.get(exampleData.domain, exampleData.user, function (err, resp){
|
assert.strictEqual(domain, 'staging.ripple.com');
|
||||||
|
callback(null, rippleTxtRes);
|
||||||
|
};
|
||||||
|
|
||||||
|
auth._getUser = function(url, callback) {
|
||||||
|
assert.strictEqual(url, 'https://id.staging.ripple.com/v1/authinfo?domain=staging.ripple.com&username=exampleUser');
|
||||||
|
callback(null, authInfoRes);
|
||||||
|
};
|
||||||
|
|
||||||
|
auth.get(exampleData.domain, exampleData.username, function(err, resp) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
assert.equal(typeof resp, 'object');
|
Object.keys(authInfoRes.body).forEach(function(prop) {
|
||||||
|
assert(resp.hasOwnProperty(prop));
|
||||||
|
});
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -63,9 +125,8 @@ describe('VaultClient', function () {
|
|||||||
it('should determine if a username exists on the domain', function(done) {
|
it('should determine if a username exists on the domain', function(done) {
|
||||||
this.timeout(10000);
|
this.timeout(10000);
|
||||||
client.exists(exampleData.username, function(err, resp) {
|
client.exists(exampleData.username, function(err, resp) {
|
||||||
|
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
assert.equal(typeof resp, 'boolean');
|
assert.strictEqual(typeof resp, 'boolean');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -75,7 +136,6 @@ describe('VaultClient', function () {
|
|||||||
it('with username and password should retrive the blob, crypt key, and id', function(done) {
|
it('with username and password should retrive the blob, crypt key, and id', function(done) {
|
||||||
this.timeout(10000);
|
this.timeout(10000);
|
||||||
client.login(exampleData.username, exampleData.password, function(err, resp) {
|
client.login(exampleData.username, exampleData.password, function(err, resp) {
|
||||||
|
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
assert.equal(typeof resp, 'object');
|
assert.equal(typeof resp, 'object');
|
||||||
assert(resp.blob instanceof Blob);
|
assert(resp.blob instanceof Blob);
|
||||||
@@ -90,12 +150,10 @@ describe('VaultClient', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('#relogin', function() {
|
describe('#relogin', function() {
|
||||||
it('should retrieve the decrypted blob with blob vault url, id, and crypt key', function(done) {
|
it('should retrieve the decrypted blob with blob vault url, id, and crypt key', function(done) {
|
||||||
this.timeout(10000);
|
this.timeout(10000);
|
||||||
client.relogin(exampleData.blobURL, exampleData.id, exampleData.crypt, function(err, resp) {
|
client.relogin(exampleData.blobURL, exampleData.id, exampleData.crypt, function(err, resp) {
|
||||||
|
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
assert.equal(typeof resp, 'object');
|
assert.equal(typeof resp, 'object');
|
||||||
assert(resp.blob instanceof Blob);
|
assert(resp.blob instanceof Blob);
|
||||||
@@ -108,7 +166,6 @@ describe('VaultClient', function () {
|
|||||||
it('should access the wallet secret using encryption secret, username and password', function (done) {
|
it('should access the wallet secret using encryption secret, username and password', function (done) {
|
||||||
this.timeout(10000);
|
this.timeout(10000);
|
||||||
client.unlock(exampleData.username, exampleData.password, exampleData.encrypted_secret, function(err, resp) {
|
client.unlock(exampleData.username, exampleData.password, exampleData.encrypted_secret, function(err, resp) {
|
||||||
|
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
assert.equal(typeof resp, 'object');
|
assert.equal(typeof resp, 'object');
|
||||||
assert.equal(typeof resp.keys, 'object');
|
assert.equal(typeof resp.keys, 'object');
|
||||||
@@ -123,7 +180,6 @@ describe('VaultClient', function () {
|
|||||||
it('should get the decrypted blob and decrypted secret given name and password', function (done) {
|
it('should get the decrypted blob and decrypted secret given name and password', function (done) {
|
||||||
this.timeout(10000);
|
this.timeout(10000);
|
||||||
client.loginAndUnlock(exampleData.username, exampleData.password, function(err, resp) {
|
client.loginAndUnlock(exampleData.username, exampleData.password, function(err, resp) {
|
||||||
|
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
assert.equal(typeof resp, 'object');
|
assert.equal(typeof resp, 'object');
|
||||||
assert(resp.blob instanceof Blob);
|
assert(resp.blob instanceof Blob);
|
||||||
@@ -147,16 +203,14 @@ describe('Blob', function () {
|
|||||||
var vaultClient;
|
var vaultClient;
|
||||||
|
|
||||||
vaultClient = new VaultClient({ domain: exampleData.domain });
|
vaultClient = new VaultClient({ domain: exampleData.domain });
|
||||||
|
|
||||||
vaultClient.login(exampleData.username, exampleData.password, function(err,resp) {
|
vaultClient.login(exampleData.username, exampleData.password, function(err,resp) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
var blob = resp.blob;
|
var blob = resp.blob;
|
||||||
|
|
||||||
|
|
||||||
describe('#set', function() {
|
describe('#set', function() {
|
||||||
it('should set a new property in the blob', function(done) {
|
it('should set a new property in the blob', function(done) {
|
||||||
this.timeout(10000);
|
this.timeout(10000)
|
||||||
|
blob.extend('/testObject', {
|
||||||
blob.extend("/testObject", {
|
|
||||||
foo: [],
|
foo: [],
|
||||||
}, function(err, resp){
|
}, function(err, resp){
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
@@ -168,10 +222,9 @@ describe('Blob', function () {
|
|||||||
|
|
||||||
describe('#extend', function() {
|
describe('#extend', function() {
|
||||||
it('should extend an object in the blob', function(done) {
|
it('should extend an object in the blob', function(done) {
|
||||||
this.timeout(10000);
|
this.timeout(10000)
|
||||||
|
blob.extend('/testObject', {
|
||||||
blob.extend("/testObject", {
|
foobar: 'baz',
|
||||||
foobar : "baz",
|
|
||||||
}, function(err, resp){
|
}, function(err, resp){
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
assert.equal(resp.result, 'success');
|
assert.equal(resp.result, 'success');
|
||||||
@@ -182,9 +235,8 @@ describe('Blob', function () {
|
|||||||
|
|
||||||
describe('#unset', function() {
|
describe('#unset', function() {
|
||||||
it('should remove a property from the blob', function(done) {
|
it('should remove a property from the blob', function(done) {
|
||||||
this.timeout(10000);
|
this.timeout(10000)
|
||||||
|
blob.unset('/testObject', function(err, resp){
|
||||||
blob.unset("/testObject", function(err, resp){
|
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
assert.equal(resp.result, 'success');
|
assert.equal(resp.result, 'success');
|
||||||
done();
|
done();
|
||||||
@@ -194,11 +246,10 @@ describe('Blob', function () {
|
|||||||
|
|
||||||
describe('#unshift', function() {
|
describe('#unshift', function() {
|
||||||
it('should prepend an item to an array in the blob', function(done) {
|
it('should prepend an item to an array in the blob', function(done) {
|
||||||
this.timeout(10000);
|
this.timeout(10000)
|
||||||
|
blob.unshift('/testArray', {
|
||||||
blob.unshift("/testArray", {
|
name: 'bob',
|
||||||
name : "bob",
|
address: '1234'
|
||||||
address : "1234"
|
|
||||||
}, function(err, resp){
|
}, function(err, resp){
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
assert.equal(resp.result, 'success');
|
assert.equal(resp.result, 'success');
|
||||||
@@ -209,9 +260,9 @@ describe('Blob', function () {
|
|||||||
|
|
||||||
describe('#filter', function() {
|
describe('#filter', function() {
|
||||||
it('should find a specific entity in an array and apply subcommands to it', function(done) {
|
it('should find a specific entity in an array and apply subcommands to it', function(done) {
|
||||||
this.timeout(10000);
|
this.timeout(10000)
|
||||||
|
|
||||||
blob.filter('/testArray', 'name', 'bob', 'extend', '', {description:"Alice"}, function(err, resp){
|
blob.filter('/testArray', 'name', 'bob', 'extend', '', {description:'Alice'}, function(err, resp){
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
assert.equal(resp.result, 'success');
|
assert.equal(resp.result, 'success');
|
||||||
done();
|
done();
|
||||||
@@ -221,12 +272,10 @@ describe('Blob', function () {
|
|||||||
|
|
||||||
describe('#consolidate', function() {
|
describe('#consolidate', function() {
|
||||||
it('should consolidate and save changes to the blob', function(done) {
|
it('should consolidate and save changes to the blob', function(done) {
|
||||||
this.timeout(10000);
|
this.timeout(10000)
|
||||||
|
|
||||||
blob.unset('/testArray', function(err, resp){
|
blob.unset('/testArray', function(err, resp){
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
assert.equal(resp.result, 'success');
|
assert.equal(resp.result, 'success');
|
||||||
|
|
||||||
blob.consolidate(function(err, resp){
|
blob.consolidate(function(err, resp){
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
assert.equal(resp.result, 'success');
|
assert.equal(resp.result, 'success');
|
||||||
@@ -236,7 +285,6 @@ describe('Blob', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
/********* Identity tests ***********/
|
/********* Identity tests ***********/
|
||||||
describe('#identity_set', function () {
|
describe('#identity_set', function () {
|
||||||
it('should set an identity property', function (done) {
|
it('should set an identity property', function (done) {
|
||||||
@@ -289,4 +337,3 @@ describe('Blob', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user