mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-28 16:15:49 +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
|
||||
/coverage.html
|
||||
/coverage
|
||||
|
||||
# Ignore IntelliJ files
|
||||
.idea
|
||||
@@ -593,8 +593,23 @@ Amount.prototype.invert = function() {
|
||||
* 25.2 XRP => 25200000/XRP
|
||||
* USD 100.40 => 100.4/USD/?
|
||||
* 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) {
|
||||
opts = opts || {};
|
||||
|
||||
@@ -1,36 +1,60 @@
|
||||
var RippleTxt = require('./rippletxt').RippleTxt;
|
||||
var request = require('superagent');
|
||||
var async = require('async');
|
||||
var superagent = require('superagent');
|
||||
var RippleTxt = require('./rippletxt').RippleTxt;
|
||||
|
||||
function AuthInfo () {
|
||||
function AuthInfo() {
|
||||
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
|
||||
*
|
||||
* @param {string} domain - Domain which hosts the user's info
|
||||
* @param {string} username - Username who's info we are retreiving
|
||||
* @param {function} fn - Callback function
|
||||
*/
|
||||
AuthInfo.prototype.get = function (domain, username, fn) {
|
||||
|
||||
AuthInfo.prototype.get = function(domain, username, callback) {
|
||||
var self = this;
|
||||
|
||||
self.rippleTxt.get(domain, function(err, txt){
|
||||
if (err) return fn(err);
|
||||
function getRippleTxt(callback) {
|
||||
self._getRippleTxt(domain, function(err, txt) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
processTxt(txt);
|
||||
});
|
||||
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;
|
||||
|
||||
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;
|
||||
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);
|
||||
callback(null, url);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
module.exports.AuthInfo = AuthInfo;
|
||||
exports.AuthInfo = AuthInfo;
|
||||
|
||||
@@ -1,45 +1,44 @@
|
||||
var crypt = require('./crypt').Crypt;
|
||||
var request = require('superagent');
|
||||
var async = require('async');
|
||||
var extend = require("extend");
|
||||
|
||||
var BlobClient = {};
|
||||
|
||||
//Blob object class
|
||||
var BlobObj = function (url, id, key) {
|
||||
this.url = url;
|
||||
this.id = id;
|
||||
this.key = key;
|
||||
this.data = {};
|
||||
function BlobObj(url, id, key) {
|
||||
this.url = url;
|
||||
this.id = id;
|
||||
this.key = key;
|
||||
this.identity = new Identity(this);
|
||||
this.data = { };
|
||||
};
|
||||
|
||||
// Blob operations
|
||||
// Do NOT change the mapping of existing ops
|
||||
BlobObj.ops = {
|
||||
// Special
|
||||
"noop" : 0,
|
||||
noop: 0,
|
||||
|
||||
// Simple ops
|
||||
"set" : 16,
|
||||
"unset" : 17,
|
||||
"extend" : 18,
|
||||
set: 16,
|
||||
unset: 17,
|
||||
extend: 18,
|
||||
|
||||
// Meta ops
|
||||
"push" : 32,
|
||||
"pop" : 33,
|
||||
"shift" : 34,
|
||||
"unshift" : 35,
|
||||
"filter" : 36
|
||||
push: 32,
|
||||
pop: 33,
|
||||
shift: 34,
|
||||
unshift: 35,
|
||||
filter: 36
|
||||
};
|
||||
|
||||
|
||||
BlobObj.opsReverseMap = [];
|
||||
BlobObj.opsReverseMap = [ ];
|
||||
|
||||
for (var name in BlobObj.ops) {
|
||||
BlobObj.opsReverseMap[BlobObj.ops[name]] = name;
|
||||
}
|
||||
|
||||
|
||||
//Identity fields
|
||||
var identityRoot = 'identityVault';
|
||||
var identityFields = [
|
||||
@@ -83,65 +82,74 @@ var idTypeFields = [
|
||||
'other'
|
||||
];
|
||||
|
||||
/*
|
||||
/**
|
||||
* Initialize a new blob object
|
||||
*
|
||||
* @param {function} fn - Callback function
|
||||
*/
|
||||
BlobObj.prototype.init = function (fn) {
|
||||
|
||||
BlobObj.prototype.init = function(fn) {
|
||||
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;
|
||||
|
||||
request.get(url, function(err, resp){
|
||||
request.get(url, function(err, resp) {
|
||||
if (err || !resp.body || resp.body.result !== 'success') {
|
||||
return fn(new Error('Could not retrieve blob'));
|
||||
}
|
||||
|
||||
if (err || !resp.body || resp.body.result !== 'success')
|
||||
return fn(new Error("Could not retrieve blob"));
|
||||
self.revision = resp.body.revision;
|
||||
self.encrypted_secret = resp.body.encrypted_secret;
|
||||
|
||||
self.revision = resp.body.revision;
|
||||
self.encrypted_secret = resp.body.encrypted_secret;
|
||||
if (!self.decrypt(resp.body.blob)) {
|
||||
return fn(new Error('Error while decrypting blob'));
|
||||
}
|
||||
|
||||
if (!self.decrypt(resp.body.blob)) {
|
||||
return fn(new Error("Error while decrypting blob"));
|
||||
//Apply patches
|
||||
if (resp.body.patches && resp.body.patches.length) {
|
||||
var successful = true;
|
||||
resp.body.patches.forEach(function(patch) {
|
||||
successful = successful && self.applyEncryptedPatch(patch);
|
||||
});
|
||||
|
||||
if (successful) {
|
||||
self.consolidate();
|
||||
}
|
||||
}
|
||||
|
||||
//Apply patches
|
||||
if (resp.body.patches && resp.body.patches.length) {
|
||||
var successful = true;
|
||||
resp.body.patches.forEach(function (patch) {
|
||||
successful = successful && self.applyEncryptedPatch(patch);
|
||||
});
|
||||
|
||||
if (successful) self.consolidate();
|
||||
}
|
||||
|
||||
fn(null, self);//return with newly decrypted blob
|
||||
|
||||
//return with newly decrypted blob
|
||||
fn(null, self);
|
||||
}).timeout(8000);
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
/**
|
||||
* Consolidate -
|
||||
* Consolidate patches as a new revision
|
||||
*
|
||||
* @param {function} fn - Callback function
|
||||
*/
|
||||
BlobObj.prototype.consolidate = function (fn) {
|
||||
|
||||
BlobObj.prototype.consolidate = function(fn) {
|
||||
// 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 config = {
|
||||
method : 'POST',
|
||||
url : this.url + '/v1/blob/consolidate',
|
||||
dataType : 'json',
|
||||
data : {
|
||||
blob_id : this.id,
|
||||
data : encrypted,
|
||||
revision : this.revision
|
||||
method: 'POST',
|
||||
url: this.url + '/v1/blob/consolidate',
|
||||
dataType: 'json',
|
||||
data: {
|
||||
blob_id: this.id,
|
||||
data: encrypted,
|
||||
revision: this.revision
|
||||
},
|
||||
};
|
||||
|
||||
@@ -150,80 +158,83 @@ BlobObj.prototype.consolidate = function (fn) {
|
||||
request.post(signed.url)
|
||||
.send(signed.data)
|
||||
.end(function(err, resp) {
|
||||
|
||||
// XXX Add better error information to exception
|
||||
if (err) return fn(new Error("Failed to consolidate blob - XHR error"));
|
||||
else if (resp.body && resp.body.result === 'success') return fn(null, resp.body);
|
||||
else return fn(new Error("Failed to consolidate blob"));
|
||||
// XXX Add better error information to exception
|
||||
if (err) {
|
||||
fn(new Error('Failed to consolidate blob - XHR error'));
|
||||
} else if (resp.body && resp.body.result === 'success') {
|
||||
fn(null, resp.body);
|
||||
} else {
|
||||
fn(new Error('Failed to consolidate blob'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
/**
|
||||
* ApplyEncryptedPatch -
|
||||
* save changes from a downloaded patch to the blob
|
||||
*
|
||||
* @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++;
|
||||
|
||||
return true;
|
||||
|
||||
} catch (err) {
|
||||
console.log("client: blob: failed to apply patch:", err.toString());
|
||||
console.log(err.stack);
|
||||
//console.log('client: blob: failed to apply patch:', err.toString());
|
||||
//console.log(err.stack);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Encrypt secret with unlock key
|
||||
*
|
||||
* @param {string} secretUnlockkey
|
||||
*/
|
||||
BlobObj.prototype.encryptSecret = function (secretUnlockKey, secret) {
|
||||
|
||||
BlobObj.prototype.encryptSecret = function(secretUnlockKey, secret) {
|
||||
return crypt.encrypt(secretUnlockKey, secret);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decrypt secret with unlock key
|
||||
*
|
||||
* @param {string} secretUnlockkey
|
||||
*/
|
||||
BlobObj.prototype.decryptSecret = function (secretUnlockKey) {
|
||||
|
||||
BlobObj.prototype.decryptSecret = function(secretUnlockKey) {
|
||||
return crypt.decrypt(secretUnlockKey, this.encrypted_secret);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Decrypt blob with crypt key
|
||||
*
|
||||
* @param {string} data - encrypted blob data
|
||||
*/
|
||||
BlobObj.prototype.decrypt = function (data) {
|
||||
|
||||
BlobObj.prototype.decrypt = function(data) {
|
||||
try {
|
||||
this.data = JSON.parse(crypt.decrypt(this.key, data));
|
||||
return this;
|
||||
} catch (e) {
|
||||
console.log("client: blob: decryption failed", e.toString());
|
||||
console.log(e.stack);
|
||||
//console.log('client: blob: decryption failed', e.toString());
|
||||
//console.log(e.stack);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Encrypt blob with crypt key
|
||||
*/
|
||||
BlobObj.prototype.encrypt = function()
|
||||
{
|
||||
|
||||
BlobObj.prototype.encrypt = function() {
|
||||
// Filter Angular metadata before encryption
|
||||
// if ('object' === typeof this.data &&
|
||||
// 'object' === typeof this.data.contacts)
|
||||
@@ -232,68 +243,67 @@ BlobObj.prototype.encrypt = function()
|
||||
return crypt.encrypt(this.key, JSON.stringify(this.data));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Encrypt recovery key
|
||||
*
|
||||
* @param {string} secret
|
||||
* @param {string} blobDecryptKey
|
||||
*/
|
||||
BlobObj.prototype.encryptBlobCrypt = function (secret, blobDecryptKey) {
|
||||
|
||||
BlobObj.prototype.encryptBlobCrypt = function(secret, blobDecryptKey) {
|
||||
var recoveryEncryptionKey = crypt.deriveRecoveryEncryptionKeyFromSecret(secret);
|
||||
return crypt.encrypt(recoveryEncryptionKey, blobDecryptKey);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decrypt recovery key
|
||||
*
|
||||
* @param {string} secret
|
||||
*/
|
||||
BlobObj.prototype.decryptBlobCrypt = function (secret) {
|
||||
|
||||
BlobObj.prototype.decryptBlobCrypt = function(secret) {
|
||||
var recoveryEncryptionKey = crypt.deriveRecoveryEncryptionKeyFromSecret(secret);
|
||||
return crypt.decrypt(recoveryEncryptionKey, this.encrypted_blobdecrypt_key);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/**** Blob updating functions ****/
|
||||
|
||||
|
||||
/**
|
||||
* Set blob element
|
||||
*/
|
||||
BlobObj.prototype.set = function (pointer, value, fn) {
|
||||
|
||||
BlobObj.prototype.set = function(pointer, value, fn) {
|
||||
this.applyUpdate('set', pointer, [value]);
|
||||
this.postUpdate('set', pointer, [value], fn);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Remove blob element
|
||||
*/
|
||||
BlobObj.prototype.unset = function (pointer, fn) {
|
||||
|
||||
BlobObj.prototype.unset = function(pointer, fn) {
|
||||
this.applyUpdate('unset', pointer, []);
|
||||
this.postUpdate('unset', pointer, [], fn);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Extend blob object
|
||||
*/
|
||||
BlobObj.prototype.extend = function (pointer, value, fn) {
|
||||
|
||||
BlobObj.prototype.extend = function(pointer, value, fn) {
|
||||
this.applyUpdate('extend', pointer, [value]);
|
||||
this.postUpdate('extend', pointer, [value], fn);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Prepend blob array
|
||||
*/
|
||||
BlobObj.prototype.unshift = function (pointer, value, fn) {
|
||||
|
||||
BlobObj.prototype.unshift = function(pointer, value, fn) {
|
||||
this.applyUpdate('unshift', pointer, [value]);
|
||||
this.postUpdate('unshift', pointer, [value], fn);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
BlobObj.prototype.filter = function (pointer, field, value, subcommands, callback) {
|
||||
var params = Array.prototype.slice.apply(arguments);
|
||||
if ("function" === typeof params[params.length-1]) {
|
||||
callback = params.pop();
|
||||
|
||||
BlobObj.prototype.filter = function(pointer, field, value, subcommands, callback) {
|
||||
var args = Array.prototype.slice.apply(arguments);
|
||||
|
||||
if (typeof args[args.length - 1] === 'function') {
|
||||
callback = args.pop();
|
||||
}
|
||||
params.shift();
|
||||
|
||||
args.shift();
|
||||
|
||||
// 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.postUpdate('filter', pointer, params, callback);
|
||||
this.applyUpdate('filter', pointer, args);
|
||||
this.postUpdate('filter', pointer, args, callback);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
if ("number" === typeof op) {
|
||||
if (typeof op === 'number') {
|
||||
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"
|
||||
var pointer = path.split("/");
|
||||
|
||||
// Separate each step in the 'pointer'
|
||||
var pointer = path.split('/');
|
||||
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);
|
||||
};
|
||||
|
||||
|
||||
//for applyUpdate function
|
||||
BlobObj.prototype._traverse = function (context, pointer,
|
||||
originalPointer, op, params) {
|
||||
BlobObj.prototype._traverse = function(context, pointer, originalPointer, op, params) {
|
||||
var _this = this;
|
||||
var part = _this.unescapeToken(pointer.shift());
|
||||
|
||||
@@ -352,16 +363,15 @@ BlobObj.prototype._traverse = function (context, pointer,
|
||||
if (part === '-') {
|
||||
part = context.length;
|
||||
} else if (part % 1 !== 0 && part >= 0) {
|
||||
throw new Error("Invalid pointer, array element segments must be " +
|
||||
"a positive integer, zero or '-'");
|
||||
throw new Error('Invalid pointer, array element segments must be a positive integer, zero or '-'');
|
||||
}
|
||||
} else if ("object" !== typeof context) {
|
||||
} else if (typeof context !== 'object') {
|
||||
return null;
|
||||
} else if (!context.hasOwnProperty(part)) {
|
||||
// Some opcodes create the path as they're going along
|
||||
if (op === "set") {
|
||||
if (op === 'set') {
|
||||
context[part] = {};
|
||||
} else if (op === "unshift") {
|
||||
} else if (op === 'unshift') {
|
||||
context[part] = [];
|
||||
} else {
|
||||
return null;
|
||||
@@ -369,139 +379,133 @@ BlobObj.prototype._traverse = function (context, pointer,
|
||||
}
|
||||
|
||||
if (pointer.length !== 0) {
|
||||
return this._traverse(context[part], pointer,
|
||||
originalPointer, op, params);
|
||||
return this._traverse(context[part], pointer, originalPointer, op, params);
|
||||
}
|
||||
|
||||
switch (op) {
|
||||
case "set":
|
||||
context[part] = params[0];
|
||||
break;
|
||||
case "unset":
|
||||
if (Array.isArray(context)) {
|
||||
context.splice(part, 1);
|
||||
} else {
|
||||
delete context[part];
|
||||
}
|
||||
break;
|
||||
case "extend":
|
||||
if ("object" !== typeof context[part]) {
|
||||
throw new Error("Tried to extend a non-object");
|
||||
}
|
||||
extend(true, context[part], params[0]);
|
||||
break;
|
||||
case "unshift":
|
||||
if ("undefined" === typeof context[part]) {
|
||||
context[part] = [];
|
||||
} else if (!Array.isArray(context[part])) {
|
||||
throw new Error("Operator 'unshift' must be applied to an array.");
|
||||
}
|
||||
context[part].unshift(params[0]);
|
||||
break;
|
||||
case "filter":
|
||||
if (Array.isArray(context[part])) {
|
||||
context[part].forEach(function (element, i) {
|
||||
if ("object" === typeof element &&
|
||||
element.hasOwnProperty(params[0]) &&
|
||||
element[params[0]] === params[1]) {
|
||||
var subpointer = originalPointer+"/"+i;
|
||||
var subcommands = normalizeSubcommands(params.slice(2));
|
||||
case 'set':
|
||||
context[part] = params[0];
|
||||
break;
|
||||
case 'unset':
|
||||
if (Array.isArray(context)) {
|
||||
context.splice(part, 1);
|
||||
} else {
|
||||
delete context[part];
|
||||
}
|
||||
break;
|
||||
case 'extend':
|
||||
if (typeof context[part] !== 'object') {
|
||||
throw new Error('Tried to extend a non-object');
|
||||
}
|
||||
extend(true, context[part], params[0]);
|
||||
break;
|
||||
case 'unshift':
|
||||
if (typeof context[part] === 'undefined') {
|
||||
context[part] = [ ];
|
||||
} else if (!Array.isArray(context[part])) {
|
||||
throw new Error('Operator "unshift" must be applied to an array.');
|
||||
}
|
||||
context[part].unshift(params[0]);
|
||||
break;
|
||||
case 'filter':
|
||||
if (Array.isArray(context[part])) {
|
||||
context[part].forEach(function(element, i) {
|
||||
if (typeof element === 'object' && element.hasOwnProperty(params[0]) && element[params[0]] === params[1]) {
|
||||
var subpointer = originalPointer + '/' + i;
|
||||
var subcommands = normalizeSubcommands(params.slice(2));
|
||||
|
||||
subcommands.forEach(function (subcommand) {
|
||||
var op = subcommand[0];
|
||||
var pointer = subpointer+subcommand[1];
|
||||
_this.applyUpdate(op, pointer, subcommand.slice(2));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error("Unsupported op "+op);
|
||||
subcommands.forEach(function(subcommand) {
|
||||
var op = subcommand[0];
|
||||
var pointer = subpointer + subcommand[1];
|
||||
_this.applyUpdate(op, pointer, subcommand.slice(2));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unsupported op '+op);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
BlobObj.prototype.escapeToken = function (token) {
|
||||
return token.replace(/[~\/]/g, function (key) { return key === "~" ? "~0" : "~1"; });
|
||||
BlobObj.prototype.escapeToken = function(token) {
|
||||
return token.replace(/[~\/]/g, function(key) {
|
||||
return key === '~' ? '~0' : '~1';
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
BlobObj.prototype.unescapeToken = function(str) {
|
||||
return str.replace(/~./g, function(m) {
|
||||
switch (m) {
|
||||
case "~0":
|
||||
return "~";
|
||||
case "~1":
|
||||
return "/";
|
||||
case '~0':
|
||||
return '~';
|
||||
case '~1':
|
||||
return '/';
|
||||
}
|
||||
throw("Invalid tilde escape: " + m);
|
||||
throw new Error('Invalid tilde escape: ' + m);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sumbit update to blob vault
|
||||
*/
|
||||
BlobObj.prototype.postUpdate = function (op, pointer, params, fn) {
|
||||
// Callback is optional
|
||||
if ("function" !== typeof fn) fn = function(){};
|
||||
|
||||
if ("string" === typeof op) {
|
||||
BlobObj.prototype.postUpdate = function(op, pointer, params, fn) {
|
||||
// Callback is optional
|
||||
if (typeof fn !== 'function') {
|
||||
fn = function(){};
|
||||
}
|
||||
|
||||
if (typeof op === 'string') {
|
||||
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 (op < 0 || op > 255) {
|
||||
throw new Error("Blob update op code out of bounds");
|
||||
|
||||
if (typeof op !== 'number') {
|
||||
throw new Error('Blob update op code must be a number or a valid op id string');
|
||||
}
|
||||
|
||||
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(op);
|
||||
|
||||
var config = {
|
||||
method : 'POST',
|
||||
url : this.url + '/v1/blob/patch',
|
||||
dataType : 'json',
|
||||
data : {
|
||||
blob_id : this.id,
|
||||
patch : crypt.encrypt(this.key, JSON.stringify(params))
|
||||
method: 'POST',
|
||||
url: this.url + '/v1/blob/patch',
|
||||
dataType: 'json',
|
||||
data: {
|
||||
blob_id: this.id,
|
||||
patch: crypt.encrypt(this.key, JSON.stringify(params))
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var signed = crypt.signRequestHmac(config, this.data.auth_secret, this.id);
|
||||
|
||||
request.post(signed.url)
|
||||
.send(signed.data)
|
||||
.end(function(err, resp) {
|
||||
if (err)
|
||||
return fn(new Error("Patch could not be saved - XHR error"));
|
||||
else if (!resp.body || resp.body.result !== 'success')
|
||||
return fn(new Error("Patch could not be saved - bad result"));
|
||||
|
||||
return fn(null, resp.body);
|
||||
.send(signed.data)
|
||||
.end(function(err, resp) {
|
||||
if (err) {
|
||||
fn(new Error('Patch could not be saved - XHR error'));
|
||||
} else if (!resp.body || resp.body.result !== 'success') {
|
||||
fn(new Error('Patch could not be saved - bad result'));
|
||||
} else {
|
||||
fn(null, resp.body);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
/***** helper functions *****/
|
||||
|
||||
|
||||
function normalizeSubcommands(subcommands, compress) {
|
||||
// Normalize parameter structure
|
||||
if ("number" === typeof subcommands[0] ||
|
||||
"string" === typeof subcommands[0]) {
|
||||
if (/(number|string)/.test(typeof subcommands[0])) {
|
||||
// Case 1: Single subcommand inline
|
||||
subcommands = [subcommands];
|
||||
} else if (subcommands.length === 1 &&
|
||||
Array.isArray(subcommands[0]) &&
|
||||
("number" === typeof subcommands[0][0] ||
|
||||
"string" === typeof subcommands[0][0])) {
|
||||
} else if (subcommands.length === 1 && Array.isArray(subcommands[0]) && /(number|string)/.test(typeof subcommands[0][0])) {
|
||||
// Case 2: Single subcommand as array
|
||||
// (nothing to do)
|
||||
} else if (Array.isArray(subcommands[0])) {
|
||||
@@ -510,16 +514,19 @@ function normalizeSubcommands(subcommands, compress) {
|
||||
}
|
||||
|
||||
// Normalize op name and convert strings to numeric codes
|
||||
subcommands = subcommands.map(function (subcommand) {
|
||||
if ("string" === typeof subcommand[0]) {
|
||||
subcommands = subcommands.map(function(subcommand) {
|
||||
if (typeof subcommand[0] === 'string') {
|
||||
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;
|
||||
});
|
||||
|
||||
@@ -542,6 +549,7 @@ function normalizeSubcommands(subcommands, compress) {
|
||||
* Identity class
|
||||
*
|
||||
*/
|
||||
|
||||
var Identity = function (blob) {
|
||||
var self = this;
|
||||
self.blob = blob;
|
||||
@@ -559,12 +567,12 @@ var Identity = function (blob) {
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* getFullAddress
|
||||
* returns the address formed into a text string
|
||||
* @param {string} key - Encryption key
|
||||
*/
|
||||
|
||||
Identity.prototype.getFullAddress = function (key) {
|
||||
if (!this.blob ||
|
||||
!this.blob.data ||
|
||||
@@ -592,6 +600,7 @@ Identity.prototype.getFullAddress = function (key) {
|
||||
* @param {string} key - Encryption key
|
||||
* @param {function} fn - Callback function
|
||||
*/
|
||||
|
||||
Identity.prototype.getAll = function (key) {
|
||||
|
||||
if (!this.blob || !this.blob.data || !this.blob.data[identityRoot]) {
|
||||
@@ -606,13 +615,13 @@ Identity.prototype.getAll = function (key) {
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* get
|
||||
* get and decrypt a single identity field
|
||||
* @param {string} pointer - Field to retrieve
|
||||
* @param {string} key - Encryption key
|
||||
*/
|
||||
|
||||
Identity.prototype.get = function (pointer, key) {
|
||||
if (!this.blob || !this.blob.data || !this.blob.data[identityRoot]) {
|
||||
return null;
|
||||
@@ -651,7 +660,6 @@ Identity.prototype.get = function (pointer, key) {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* set
|
||||
* set and encrypt a single identity field.
|
||||
@@ -660,6 +668,7 @@ Identity.prototype.get = function (pointer, key) {
|
||||
* @param {string} value - Unencrypted data
|
||||
* @param {function} fn - Callback function
|
||||
*/
|
||||
|
||||
Identity.prototype.set = function (pointer, key, value, fn) {
|
||||
var self = this;
|
||||
|
||||
@@ -732,7 +741,6 @@ Identity.prototype.set = function (pointer, key, value, fn) {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* unset
|
||||
* 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 {function} fn - Callback function
|
||||
*/
|
||||
|
||||
Identity.prototype.unset = function (pointer, key, fn) {
|
||||
|
||||
//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);
|
||||
};
|
||||
|
||||
|
||||
|
||||
/***** blob client methods ****/
|
||||
|
||||
/**
|
||||
* Blob object class
|
||||
*/
|
||||
BlobClient.Blob = BlobObj;
|
||||
|
||||
exports.Blob = BlobObj;
|
||||
|
||||
/**
|
||||
* 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){
|
||||
if (err) return fn(new Error("Unable to access vault sever"));
|
||||
else if (resp.body && resp.body.username) return fn(null, resp.body.username);
|
||||
else if (resp.body && resp.body.exists === false) return fn (new Error("No ripple name for this address"));
|
||||
else return fn(new Error("Unable to determine if ripple name exists"));
|
||||
if (err) {
|
||||
fn(new Error('Unable to access vault sever'));
|
||||
} else if (resp.body && resp.body.username) {
|
||||
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
|
||||
*/
|
||||
|
||||
BlobClient.get = function (url, id, crypt, fn) {
|
||||
var blob = new BlobObj(url, id, crypt);
|
||||
blob.init(fn);
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
/**
|
||||
* Verify email address
|
||||
*/
|
||||
BlobClient.verify = function (url, username, token, fn) {
|
||||
|
||||
BlobClient.verify = function(url, username, token, fn) {
|
||||
url += '/v1/user/' + username + '/verify/' + token;
|
||||
request.get(url, function(err, resp){
|
||||
if (err) return fn(err);
|
||||
else if (resp.body && resp.body.result === 'success') return fn(null, data);
|
||||
else return fn(new Error("Failed to verify the account"));
|
||||
if (err) {
|
||||
fn(err);
|
||||
} else if (resp.body && resp.body.result === 'success') {
|
||||
fn(null, data);
|
||||
} else {
|
||||
fn(new Error('Failed to verify the account'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create a blob object
|
||||
*
|
||||
@@ -812,17 +831,18 @@ BlobClient.verify = function (url, username, token, fn) {
|
||||
* @param {object} options.oldUserBlob
|
||||
* @param {function} fn
|
||||
*/
|
||||
BlobClient.create = function (options, fn) {
|
||||
|
||||
var blob = new BlobObj(options.url, options.id, options.crypt);
|
||||
BlobClient.create = function(options, fn) {
|
||||
var blob = new BlobObj(options.url, options.id, options.crypt);
|
||||
|
||||
blob.revision = 0;
|
||||
blob.data = {
|
||||
auth_secret : crypt.createSecret(8),
|
||||
account_id : crypt.getAddress(options.masterkey),
|
||||
email : options.email,
|
||||
contacts : [],
|
||||
created : (new Date()).toJSON()
|
||||
|
||||
blob.data = {
|
||||
auth_secret: crypt.createSecret(8),
|
||||
account_id: crypt.getAddress(options.masterkey),
|
||||
email: options.email,
|
||||
contacts: [],
|
||||
created: (new Date()).toJSON()
|
||||
};
|
||||
|
||||
blob.encrypted_secret = blob.encryptSecret(options.unlock, options.masterkey);
|
||||
@@ -834,18 +854,18 @@ BlobClient.create = function (options, fn) {
|
||||
|
||||
//post to the blob vault to create
|
||||
var config = {
|
||||
method : "POST",
|
||||
url : options.url + '/v1/user',
|
||||
data : {
|
||||
blob_id : options.id,
|
||||
username : options.username,
|
||||
address : blob.data.account_id,
|
||||
auth_secret : blob.data.auth_secret,
|
||||
data : blob.encrypt(),
|
||||
email : options.email,
|
||||
hostlink : options.activateLink,
|
||||
encrypted_blobdecrypt_key : blob.encryptBlobCrypt(options.masterkey, options.crypt),
|
||||
encrypted_secret : blob.encrypted_secret
|
||||
method: 'POST',
|
||||
url: options.url + '/v1/user',
|
||||
data: {
|
||||
blob_id: options.id,
|
||||
username: options.username,
|
||||
address: blob.data.account_id,
|
||||
auth_secret: blob.data.auth_secret,
|
||||
data: blob.encrypt(),
|
||||
email: options.email,
|
||||
hostlink: options.activateLink,
|
||||
encrypted_blobdecrypt_key: blob.encryptBlobCrypt(options.masterkey, options.crypt),
|
||||
encrypted_secret: blob.encrypted_secret
|
||||
}
|
||||
};
|
||||
|
||||
@@ -854,10 +874,14 @@ BlobClient.create = function (options, fn) {
|
||||
request.post(signed)
|
||||
.send(signed.data)
|
||||
.end(function(err, resp) {
|
||||
if (err) return fn(err);
|
||||
else if (resp.body && resp.body.result === 'success') return fn(null, blob,resp.body);
|
||||
else return fn(new Error("Could not create blob"));
|
||||
});
|
||||
if (err) {
|
||||
fn(err);
|
||||
} 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;
|
||||
|
||||
@@ -1,34 +1,35 @@
|
||||
var sjcl = require('./utils').sjcl;
|
||||
var base = require('./base').Base;
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var message = require('./message');
|
||||
var request = require('superagent');
|
||||
var extend = require("extend");
|
||||
var parser = require("url");
|
||||
var Crypt = {};
|
||||
var sjcl = require('./utils').sjcl;
|
||||
var base = require('./base').Base;
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var message = require('./message');
|
||||
var request = require('superagent');
|
||||
var querystring = require('querystring');
|
||||
var extend = require("extend");
|
||||
var parser = require("url");
|
||||
var Crypt = { };
|
||||
|
||||
var cryptConfig = {
|
||||
cipher : "aes",
|
||||
mode : "ccm",
|
||||
cipher : 'aes',
|
||||
mode : 'ccm',
|
||||
ts : 64, // tag length
|
||||
ks : 256, // key size
|
||||
iter : 1000 // iterations (key derivation)
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Full domain hash based on SHA512
|
||||
*/
|
||||
function fdh(data, bytelen)
|
||||
{
|
||||
|
||||
function fdh(data, bytelen) {
|
||||
var bitlen = bytelen << 3;
|
||||
|
||||
if (typeof data === "string") {
|
||||
if (typeof data === 'string') {
|
||||
data = sjcl.codec.utf8String.toBits(data);
|
||||
}
|
||||
|
||||
// Add hashing rounds until we exceed desired length in bits
|
||||
var counter = 0, output = [];
|
||||
|
||||
while (sjcl.bitArray.bitLength(output) < bitlen) {
|
||||
var hash = sjcl.hash.sha512.hash(sjcl.bitArray.concat([counter], data));
|
||||
output = sjcl.bitArray.concat(output, hash);
|
||||
@@ -39,69 +40,72 @@ function fdh(data, bytelen)
|
||||
output = sjcl.bitArray.clamp(output, bitlen);
|
||||
|
||||
return output;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a function to derive different hashes from the same key.
|
||||
* Each hash is derived as HMAC-SHA512HALF(key, token).
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {string} hash
|
||||
*/
|
||||
|
||||
function keyHash(key, token) {
|
||||
var hmac = new sjcl.misc.hmac(key, sjcl.hash.sha512);
|
||||
return sjcl.codec.hex.fromBits(sjcl.bitArray.bitSlice(hmac.encrypt(token), 0, 256));
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
/****** exposed functions ******/
|
||||
|
||||
|
||||
/**
|
||||
* KEY DERIVATION FUNCTION
|
||||
*
|
||||
* This service takes care of the key derivation, i.e. converting low-entropy
|
||||
* secret into higher entropy secret via either computationally expensive
|
||||
* processes or peer-assisted key derivation (PAKDF).
|
||||
*
|
||||
* @param {object} opts
|
||||
* @param {string} purpose - Key type/purpose
|
||||
* @param {string} username
|
||||
* @param {string} secret - Also known as passphrase/password
|
||||
* @param {function} fn
|
||||
*/
|
||||
Crypt.derive = function (opts, purpose, username, secret, fn) {
|
||||
|
||||
Crypt.derive = function(opts, purpose, username, secret, fn) {
|
||||
var tokens;
|
||||
if (purpose=='login') tokens = ['id', 'crypt'];
|
||||
else tokens = ['unlock'];
|
||||
|
||||
var iExponent = new sjcl.bn(String(opts.exponent)),
|
||||
iModulus = new sjcl.bn(String(opts.modulus)),
|
||||
iAlpha = new sjcl.bn(String(opts.alpha));
|
||||
if (purpose === 'login') {
|
||||
tokens = ['id', 'crypt'];
|
||||
} else {
|
||||
tokens = ['unlock'];
|
||||
}
|
||||
|
||||
var publicInfo = "PAKDF_1_0_0:"+opts.host.length+":"+opts.host+
|
||||
":"+username.length+":"+username+
|
||||
":"+purpose.length+":"+purpose+
|
||||
":",
|
||||
publicSize = Math.ceil(Math.min((7+iModulus.bitLength()) >>> 3, 256)/8),
|
||||
publicHash = fdh(publicInfo, publicSize),
|
||||
publicHex = sjcl.codec.hex.fromBits(publicHash),
|
||||
iPublic = new sjcl.bn(String(publicHex)).setBitM(0),
|
||||
secretInfo = publicInfo+":"+secret.length+":"+secret+":",
|
||||
secretSize = (7+iModulus.bitLength()) >>> 3,
|
||||
secretHash = fdh(secretInfo, secretSize),
|
||||
secretHex = sjcl.codec.hex.fromBits(secretHash),
|
||||
iSecret = new sjcl.bn(String(secretHex)).mod(iModulus);
|
||||
var iExponent = new sjcl.bn(String(opts.exponent));
|
||||
var iModulus = new sjcl.bn(String(opts.modulus));
|
||||
var iAlpha = new sjcl.bn(String(opts.alpha));
|
||||
|
||||
var publicInfo = [ 'PAKDF_1_0_0', opts.host.length, opts.host, username.length, username, purpose.length, purpose ].join(':') + ':';
|
||||
var publicSize = Math.ceil(Math.min((7 + iModulus.bitLength()) >>> 3, 256) / 8);
|
||||
var publicHash = fdh(publicInfo, publicSize);
|
||||
var publicHex = sjcl.codec.hex.fromBits(publicHash);
|
||||
var iPublic = new sjcl.bn(String(publicHex)).setBitM(0);
|
||||
var secretInfo = [ publicInfo, secret.length, secret ].join(':') + ':';
|
||||
var secretSize = (7 + iModulus.bitLength()) >>> 3;
|
||||
var secretHash = fdh(secretInfo, secretSize);
|
||||
var secretHex = sjcl.codec.hex.fromBits(secretHash);
|
||||
var iSecret = new sjcl.bn(String(secretHex)).mod(iModulus);
|
||||
|
||||
if (iSecret.jacobi(iModulus) !== 1) {
|
||||
iSecret = iSecret.mul(iAlpha).mod(iModulus);
|
||||
}
|
||||
|
||||
var iRandom;
|
||||
|
||||
for (;;) {
|
||||
iRandom = sjcl.bn.random(iModulus, 0);
|
||||
if (iRandom.jacobi(iModulus) === 1)
|
||||
if (iRandom.jacobi(iModulus) === 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var iBlind = iRandom.powermodMontgomery(iPublic.mul(iExponent), iModulus);
|
||||
@@ -109,36 +113,38 @@ Crypt.derive = function (opts, purpose, username, secret, fn) {
|
||||
var signreq = sjcl.codec.hex.fromBits(iSignreq.toBits());
|
||||
|
||||
request.post(opts.url)
|
||||
.send({
|
||||
info : publicInfo,
|
||||
signreq : signreq
|
||||
}).end(function(err, resp) {
|
||||
.send({ info: publicInfo, signreq: signreq })
|
||||
.end(function(err, resp) {
|
||||
if (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 iRandomInv = iRandom.inverseMod(iModulus);
|
||||
var iSigned = iSignres.mulmod(iRandomInv, iModulus);
|
||||
var key = iSigned.toBits();
|
||||
var result = { };
|
||||
|
||||
var iSignres = new sjcl.bn(String(data.signres));
|
||||
var iRandomInv = iRandom.inverseMod(iModulus);
|
||||
var iSigned = iSignres.mulmod(iRandomInv, iModulus);
|
||||
var key = iSigned.toBits();
|
||||
var result = {};
|
||||
tokens.forEach(function(token) {
|
||||
result[token] = keyHash(key, token);
|
||||
});
|
||||
|
||||
tokens.forEach(function (token) {
|
||||
result[token] = keyHash(key, token);
|
||||
fn(null, result);
|
||||
});
|
||||
|
||||
fn (null, result);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Imported from ripple-client
|
||||
*
|
||||
*/
|
||||
Crypt.RippleAddress = (function () {
|
||||
|
||||
Crypt.RippleAddress = (function() {
|
||||
|
||||
function append_int(a, i) {
|
||||
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)),
|
||||
0, 256
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
function SHA256_RIPEMD160(bits) {
|
||||
return sjcl.hash.ripemd160.hash(sjcl.hash.sha256.hash(bits));
|
||||
}
|
||||
};
|
||||
|
||||
return function (seed) {
|
||||
return function(seed) {
|
||||
this.seed = base.decode_check(33, seed);
|
||||
|
||||
if (!this.seed) {
|
||||
throw "Invalid seed.";
|
||||
throw new Error('Invalid seed.');
|
||||
}
|
||||
|
||||
this.getAddress = function (seq) {
|
||||
this.getAddress = function(seq) {
|
||||
seq = seq || 0;
|
||||
|
||||
var private_gen, public_gen, i = 0;
|
||||
|
||||
do {
|
||||
private_gen = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(this.seed, i)));
|
||||
i++;
|
||||
@@ -174,6 +181,7 @@ Crypt.RippleAddress = (function () {
|
||||
|
||||
var sec;
|
||||
i = 0;
|
||||
|
||||
do {
|
||||
sec = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(append_int(public_gen.toBytesCompressed(), seq), i)));
|
||||
i++;
|
||||
@@ -188,11 +196,12 @@ Crypt.RippleAddress = (function () {
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
var opts = extend(true, {}, cryptConfig);
|
||||
@@ -208,12 +217,13 @@ Crypt.encrypt = function(key, data)
|
||||
return sjcl.codec.base64.fromBits(encryptedBits);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Decrypt data
|
||||
* @params {string} key
|
||||
* @params {string} data
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {string} data
|
||||
*/
|
||||
|
||||
Crypt.decrypt = function (key, data) {
|
||||
|
||||
key = sjcl.codec.hex.toBits(key);
|
||||
@@ -222,7 +232,7 @@ Crypt.decrypt = function (key, data) {
|
||||
var version = sjcl.bitArray.extract(encryptedBits, 0, 8);
|
||||
|
||||
if (version !== 0) {
|
||||
throw new Error("Unsupported encryption version: "+version);
|
||||
throw new Error('Unsupported encryption version: '+version);
|
||||
}
|
||||
|
||||
var encrypted = extend(true, {}, cryptConfig, {
|
||||
@@ -236,25 +246,28 @@ Crypt.decrypt = function (key, data) {
|
||||
|
||||
/**
|
||||
* Validate a ripple address
|
||||
*
|
||||
* @param {string} address
|
||||
*/
|
||||
|
||||
Crypt.isValidAddress = function (address) {
|
||||
return UInt160.is_valid(address);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Validate a ripple address
|
||||
*
|
||||
* @param {integer} nWords - number of words
|
||||
*/
|
||||
|
||||
Crypt.createSecret = function (nWords) {
|
||||
return sjcl.codec.hex.fromBits(sjcl.random.randomWords(nWords));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create a new master key
|
||||
*/
|
||||
|
||||
Crypt.createMaster = function () {
|
||||
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
|
||||
*
|
||||
* @param {string} masterkey
|
||||
*/
|
||||
|
||||
Crypt.getAddress = function (masterkey) {
|
||||
return new Crypt.RippleAddress(masterkey).getAddress();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Hash data
|
||||
*
|
||||
* @param {string} data
|
||||
*/
|
||||
|
||||
Crypt.hashSha512 = function (data) {
|
||||
return sjcl.codec.hex.fromBits(sjcl.hash.sha512.hash(data));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sign a data string with a secret key
|
||||
*
|
||||
* @param {string} secret
|
||||
* @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);
|
||||
return sjcl.codec.hex.fromBits(hmac.mac(data));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create an an accout recovery key
|
||||
*
|
||||
* @param {string} secret
|
||||
*/
|
||||
|
||||
Crypt.deriveRecoveryEncryptionKeyFromSecret = function(secret) {
|
||||
var seed = ripple.Seed.from_json(secret).to_bits();
|
||||
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);
|
||||
return sjcl.codec.hex.fromBits(key);
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert base64 encoded data into base64url encoded data.
|
||||
*
|
||||
* @param {String} base64 Data
|
||||
*/
|
||||
Crypt.base64ToBase64Url = function (encodedData) {
|
||||
|
||||
Crypt.base64ToBase64Url = function(encodedData) {
|
||||
return encodedData.replace(/\+/g, '-').replace(/\//g, '_').replace(/[=]+$/, '');
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert base64url encoded data into base64 encoded data.
|
||||
*
|
||||
* @param {String} base64 Data
|
||||
*/
|
||||
Crypt.base64UrlToBase64 = function (encodedData) {
|
||||
|
||||
Crypt.base64UrlToBase64 = function(encodedData) {
|
||||
encodedData = encodedData.replace(/-/g, '+').replace(/_/g, '/');
|
||||
|
||||
while (encodedData.length % 4) {
|
||||
encodedData += '=';
|
||||
}
|
||||
|
||||
return encodedData;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create a string from request parameters that
|
||||
* will be used to sign a request
|
||||
*
|
||||
* @param {Object} config - request params
|
||||
* @param {Object} parsed - parsed url
|
||||
* @param {Object} date
|
||||
* @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
|
||||
// field will be merged into the search string, not the request body.
|
||||
|
||||
// Sort the properties of the JSON object into canonical form
|
||||
var canonicalData = JSON.stringify(copyObjectWithSortedKeys(config.data));
|
||||
|
||||
@@ -362,14 +386,15 @@ Crypt.getStringToSign = function (config, parsed, date, mechanism) {
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* HMAC signed request
|
||||
*
|
||||
* @param {Object} config
|
||||
* @param {Object} auth_secret
|
||||
* @param {Object} blob_id
|
||||
*/
|
||||
Crypt.signRequestHmac = function (config, auth_secret, blob_id) {
|
||||
|
||||
Crypt.signRequestHmac = function(config, auth_secret, blob_id) {
|
||||
config = extend(true, {}, config);
|
||||
|
||||
// Parse URL
|
||||
@@ -379,23 +404,27 @@ Crypt.signRequestHmac = function (config, auth_secret, blob_id) {
|
||||
var stringToSign = Crypt.getStringToSign(config, parsed, date, signatureType);
|
||||
var signature = Crypt.signString(auth_secret, stringToSign);
|
||||
|
||||
config.url += (parsed.search ? "&" : "?") +
|
||||
'signature='+Crypt.base64ToBase64Url(signature)+
|
||||
'&signature_date='+date+
|
||||
'&signature_blob_id='+blob_id+
|
||||
'&signature_type='+signatureType;
|
||||
var query = querystring.stringify({
|
||||
signature: Crypt.base64ToBase64Url(signature),
|
||||
signature_date: date,
|
||||
signature_blob_id: blob_id,
|
||||
signature_type: signatureType
|
||||
});
|
||||
|
||||
config.url += (parsed.search ? '&' : '?') + query;
|
||||
return config;
|
||||
};
|
||||
|
||||
/**
|
||||
* Asymmetric signed request
|
||||
*
|
||||
* @param {Object} config
|
||||
* @param {Object} secretKey
|
||||
* @param {Object} account
|
||||
* @param {Object} blob_id
|
||||
*/
|
||||
Crypt.signRequestAsymmetric = function (config, secretKey, account, blob_id) {
|
||||
|
||||
Crypt.signRequestAsymmetric = function(config, secretKey, account, blob_id) {
|
||||
config = extend(true, {}, config);
|
||||
|
||||
// Parse URL
|
||||
@@ -405,65 +434,71 @@ Crypt.signRequestAsymmetric = function (config, secretKey, account, blob_id) {
|
||||
var stringToSign = Crypt.getStringToSign(config, parsed, date, signatureType);
|
||||
var signature = message.signMessage(stringToSign, secretKey);
|
||||
|
||||
config.url += (parsed.search ? "&" : "?") +
|
||||
'signature='+Crypt.base64ToBase64Url(signature)+
|
||||
'&signature_date='+date+
|
||||
'&signature_blob_id='+blob_id+
|
||||
'&signature_account='+account+
|
||||
'&signature_type='+signatureType;
|
||||
var query = querystring.stringify({
|
||||
signature: Crypt.base64ToBase64Url(signature),
|
||||
signature_date: date,
|
||||
signature_blob_id: blob_id,
|
||||
signature_account: account,
|
||||
signature_type: signatureType
|
||||
})
|
||||
|
||||
config.url += (parsed.search ? '&' : '?') + query;
|
||||
|
||||
return config;
|
||||
};
|
||||
|
||||
|
||||
//prepare for signing
|
||||
function copyObjectWithSortedKeys(object) {
|
||||
if (isPlainObject(object)) {
|
||||
var newObj = {};
|
||||
var keysSorted = Object.keys(object).sort();
|
||||
var key;
|
||||
|
||||
for (var i in keysSorted) {
|
||||
key = keysSorted[i];
|
||||
if (Object.prototype.hasOwnProperty.call(object, key)) {
|
||||
newObj[key] = copyObjectWithSortedKeys(object[key]);
|
||||
}
|
||||
}
|
||||
|
||||
return newObj;
|
||||
} else if (Array.isArray(object)) {
|
||||
return object.map(copyObjectWithSortedKeys);
|
||||
} else {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
//from npm extend
|
||||
function isPlainObject(obj) {
|
||||
var hasOwn = Object.prototype.hasOwnProperty;
|
||||
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;
|
||||
}
|
||||
|
||||
var has_own_constructor = hasOwn.call(obj, 'constructor');
|
||||
var has_is_property_of_method = hasOwn.call(obj.constructor.prototype, 'isPrototypeOf');
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Own properties are enumerated firstly, so to speed up,
|
||||
// if last one is own, then all properties are own.
|
||||
var key;
|
||||
|
||||
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) {
|
||||
return (n < 0 || n > 9 ? "" : "0") + n;
|
||||
}
|
||||
return (n < 0 || n > 9 ? '' : '0') + n;
|
||||
};
|
||||
|
||||
return function dateAsIso8601() {
|
||||
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,65 +1,82 @@
|
||||
var request = require('superagent');
|
||||
|
||||
var superagent = require('superagent');
|
||||
|
||||
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
|
||||
*
|
||||
* @param {string} domain - Domain to retrieve file from
|
||||
* @param {function} fn - Callback function
|
||||
*/
|
||||
RippleTxt.prototype.get = function (domain, fn) {
|
||||
|
||||
RippleTxt.prototype.get = function(domain, fn) {
|
||||
var self = this;
|
||||
|
||||
if (self.txts[domain]) {
|
||||
return fn(null, self.txts[domain]);
|
||||
}
|
||||
|
||||
var urls = [
|
||||
'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'
|
||||
].reverse();
|
||||
;(function nextUrl(i) {
|
||||
var url = RippleTxt.urlTemplates[i];
|
||||
|
||||
next();
|
||||
function next () {
|
||||
if (!urls.length) return fn(new Error("No ripple.txt found"));
|
||||
var url = urls.pop();
|
||||
if (!url) {
|
||||
return fn(new Error('No ripple.txt found'));
|
||||
}
|
||||
|
||||
request.get(url, function(err, resp) {
|
||||
if (err || !resp.text) return next();
|
||||
url = url.replace('{{domain}}', domain);
|
||||
|
||||
var sections = self.parse(resp.text);
|
||||
self.request(url, function(err, resp) {
|
||||
if (err || !resp.text) {
|
||||
return nextUrl(++i);
|
||||
}
|
||||
|
||||
var sections = self.parse(resp.text);
|
||||
self.txts[domain] = sections;
|
||||
|
||||
fn(null, sections);
|
||||
});
|
||||
}
|
||||
})(0);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parse a ripple.txt file
|
||||
*
|
||||
* @param {string} txt - Unparsed ripple.txt data
|
||||
*/
|
||||
RippleTxt.prototype.parse = function (txt) {
|
||||
|
||||
txt = txt.replace('\r\n', '\n');
|
||||
txt = txt.replace('\r', '\n');
|
||||
txt = txt.split('\n');
|
||||
RippleTxt.prototype.parse = function(txt) {
|
||||
var txt = txt.replace(/\r?\n/g, '\n').split('\n')
|
||||
var currentSection = '';
|
||||
var sections = { };
|
||||
|
||||
var currentSection = "", sections = {};
|
||||
for (var i = 0, l = txt.length; i < l; i++) {
|
||||
var line = txt[i];
|
||||
|
||||
if (!line.length || line[0] === '#') {
|
||||
continue;
|
||||
} else if (line[0] === '[' && line[line.length-1] === ']') {
|
||||
currentSection = line.slice(1, line.length-1);
|
||||
}
|
||||
|
||||
if (line[0] === '[' && line[line.length - 1] === ']') {
|
||||
currentSection = line.slice(1, line.length - 1);
|
||||
sections[currentSection] = [];
|
||||
} else {
|
||||
line = line.replace(/^\s+|\s+$/g, '');
|
||||
@@ -72,4 +89,4 @@ RippleTxt.prototype.parse = function (txt) {
|
||||
return sections;
|
||||
};
|
||||
|
||||
module.exports.RippleTxt = RippleTxt;
|
||||
exports.RippleTxt = RippleTxt;
|
||||
|
||||
@@ -7,17 +7,19 @@ var log = require('./log').internal.sub('server');
|
||||
|
||||
/**
|
||||
* @constructor Server
|
||||
*
|
||||
* @param {Remote} Reference to a Remote object
|
||||
* @param {Object} Options
|
||||
*
|
||||
* host: String
|
||||
* port: String or Number
|
||||
* secure: Boolean
|
||||
* @param {String} host
|
||||
* @param {Number|String} port
|
||||
* @param [Boolean] securec
|
||||
*/
|
||||
|
||||
function Server(remote, opts) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
var self = this;
|
||||
|
||||
if (typeof opts === 'string') {
|
||||
var parsedUrl = url.parse(opts);
|
||||
opts = {
|
||||
@@ -31,16 +33,6 @@ function Server(remote, opts) {
|
||||
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)) {
|
||||
throw new TypeError('Server port must be a number');
|
||||
}
|
||||
@@ -50,7 +42,7 @@ function Server(remote, opts) {
|
||||
}
|
||||
|
||||
if (typeof opts.secure !== 'boolean') {
|
||||
opts.secure = false;
|
||||
opts.secure = true;
|
||||
}
|
||||
|
||||
// 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');
|
||||
}
|
||||
|
||||
var self = this;
|
||||
this._remote = remote;
|
||||
this._opts = opts;
|
||||
this._ws = void(0);
|
||||
|
||||
this._remote = remote;
|
||||
this._opts = opts;
|
||||
this._host = opts.host;
|
||||
this._port = opts.port;
|
||||
this._secure = opts.secure;
|
||||
this._ws = void(0);
|
||||
this._connected = false;
|
||||
this._shouldConnect = false;
|
||||
this._state = 'offline';
|
||||
this._id = 0;
|
||||
this._retry = 0;
|
||||
this._requests = { };
|
||||
this._load_base = 256;
|
||||
this._load_factor = 256;
|
||||
|
||||
this._id = 0;
|
||||
this._retry = 0;
|
||||
this._requests = { };
|
||||
|
||||
this._load_base = 256;
|
||||
this._load_factor = 256;
|
||||
|
||||
this._fee = 10;
|
||||
this._fee_ref = 10;
|
||||
this._fee_base = 10;
|
||||
this._reserve_base = void(0);
|
||||
this._reserve_inc = void(0);
|
||||
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);
|
||||
});
|
||||
};
|
||||
|
||||
this.on('response_subscribe', function(message) {
|
||||
this.on('response_subscribe', onSubscribeResponse);
|
||||
|
||||
function onSubscribeResponse(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() {
|
||||
self._activityInterval = setInterval(checkServerActivity, 1000);
|
||||
var interval = self._checkActivity.bind(self);
|
||||
self._activityInterval = setInterval(interval, 1000);
|
||||
};
|
||||
|
||||
this.on('disconnect', function onDisconnect() {
|
||||
@@ -119,8 +115,18 @@ function Server(remote, opts) {
|
||||
//self.once('ledger_closed', setActivityInterval);
|
||||
});
|
||||
|
||||
this.once('ledger_closed', function() {
|
||||
//setActiviyInterval();
|
||||
//this.once('ledger_closed', setActivityInterval);
|
||||
|
||||
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'
|
||||
];
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
@@ -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.
|
||||
* Incompatible with ripple-lib client build
|
||||
@@ -186,22 +279,6 @@ Server.prototype._remoteAddress = function() {
|
||||
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
|
||||
*
|
||||
@@ -274,7 +351,6 @@ Server.prototype.connect = function() {
|
||||
};
|
||||
|
||||
ws.onopen = function onOpen() {
|
||||
// If we are no longer the active socket, simply ignore any event
|
||||
if (ws === self._ws) {
|
||||
self.emit('socket_open');
|
||||
// Subscribe to events
|
||||
@@ -283,7 +359,6 @@ Server.prototype.connect = function() {
|
||||
};
|
||||
|
||||
ws.onerror = function onError(e) {
|
||||
// If we are no longer the active socket, simply ignore any event
|
||||
if (ws === self._ws) {
|
||||
self.emit('socket_error');
|
||||
|
||||
@@ -309,9 +384,7 @@ Server.prototype.connect = function() {
|
||||
}
|
||||
};
|
||||
|
||||
// Failure to open.
|
||||
ws.onclose = function onClose() {
|
||||
// If we are no longer the active socket, simply ignore any event
|
||||
if (ws === self._ws) {
|
||||
if (self._remote.trace) {
|
||||
log.info('onclose:', self._opts.url, ws.readyState);
|
||||
@@ -333,12 +406,16 @@ Server.prototype._retryConnect = function() {
|
||||
this._retry += 1;
|
||||
|
||||
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)
|
||||
? (1000) // Then, for 1 minute: once per second
|
||||
// Then, for 1 minute: once per second
|
||||
? (1000)
|
||||
: (this._retry < 40 + 60 + 60)
|
||||
? (10 * 1000) // Then, for 10 minutes: once every 10 seconds
|
||||
: (30 * 1000); // Then: once every 30 seconds
|
||||
// Then, for 10 minutes: once every 10 seconds
|
||||
? (10 * 1000)
|
||||
// Then: once every 30 seconds
|
||||
: (30 * 1000);
|
||||
|
||||
function connectionRetry() {
|
||||
if (self._shouldConnect) {
|
||||
@@ -365,8 +442,10 @@ Server.prototype._handleClose = function() {
|
||||
this.emit('socket_close');
|
||||
this._setState('offline');
|
||||
|
||||
function noOp() {};
|
||||
|
||||
// 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) {
|
||||
this._retryConnect();
|
||||
@@ -389,78 +468,125 @@ Server.prototype._handleMessage = function(message) {
|
||||
}
|
||||
|
||||
if (!Server.isValidMessage(message)) {
|
||||
this.emit('unexpected', message);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (message.type) {
|
||||
case 'ledgerClosed':
|
||||
this._lastLedgerClose = Date.now();
|
||||
this.emit('ledger_closed', message);
|
||||
this._handleLedgerClosed(message);
|
||||
break;
|
||||
|
||||
case 'serverStatus':
|
||||
// This message is only received when online.
|
||||
// As we are connected, it is the definitive final state.
|
||||
|
||||
this._setState(~(Server.onlineStates.indexOf(message.server_status)) ? 'online' : 'offline');
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
this._handleServerStatus(message);
|
||||
break;
|
||||
|
||||
case 'response':
|
||||
// A response to a request.
|
||||
var request = self._requests[message.id];
|
||||
delete self._requests[message.id];
|
||||
|
||||
if (!request) {
|
||||
if (this._remote.trace) {
|
||||
log.info('UNEXPECTED:', self._opts.url, message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.status === 'success') {
|
||||
if (this._remote.trace) {
|
||||
log.info('response:', self._opts.url, message);
|
||||
}
|
||||
|
||||
request.emit('success', message.result);
|
||||
|
||||
[ self, self._remote ].forEach(function(emitter) {
|
||||
emitter.emit('response_' + request.message.command, message.result, request, message);
|
||||
});
|
||||
} else if (message.error) {
|
||||
if (this._remote.trace) {
|
||||
log.info('error:', self._opts.url, message);
|
||||
}
|
||||
|
||||
request.emit('error', {
|
||||
error: 'remoteError',
|
||||
error_message: 'Remote reported an error.',
|
||||
remote: message
|
||||
});
|
||||
}
|
||||
this._handleResponse(message);
|
||||
break;
|
||||
|
||||
case 'path_find':
|
||||
if (this._remote.trace) {
|
||||
log.info('path_find:', self._opts.url, message);
|
||||
}
|
||||
this._handlePathFind(message);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
Server.prototype._handleLedgerClosed = function(message) {
|
||||
this._lastLedgerIndex = message.ledger_index;
|
||||
this._lastLedgerClose = Date.now();
|
||||
this.emit('ledger_closed', message);
|
||||
};
|
||||
|
||||
Server.prototype._handleServerStatus = function(message) {
|
||||
// This message is only received when online.
|
||||
// As we are connected, it is the definitive final state.
|
||||
var isOnline = ~Server.onlineStates.indexOf(message.server_status);
|
||||
this._setState(isOnline ? 'online' : 'offline');
|
||||
|
||||
if (!Server.isLoadStatus(message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
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.
|
||||
var request = this._requests[message.id];
|
||||
|
||||
delete this._requests[message.id];
|
||||
|
||||
if (!request) {
|
||||
if (this._remote.trace) {
|
||||
log.info('UNEXPECTED:', this._opts.url, message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.status === 'success') {
|
||||
if (this._remote.trace) {
|
||||
log.info('response:', this._opts.url, message);
|
||||
}
|
||||
|
||||
var command = request.message.command;
|
||||
var result = message.result;
|
||||
var responseEvent = 'response_' + command;
|
||||
|
||||
request.emit('success', result);
|
||||
|
||||
[ this, this._remote ].forEach(function(emitter) {
|
||||
emitter.emit(responseEvent, result, request, message);
|
||||
});
|
||||
} else if (message.error) {
|
||||
if (this._remote.trace) {
|
||||
log.info('error:', this._opts.url, message);
|
||||
}
|
||||
|
||||
var error = {
|
||||
error: 'remoteError',
|
||||
error_message: 'Remote reported an error.',
|
||||
remote: message
|
||||
};
|
||||
|
||||
request.emit('error', error);
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check that received message from rippled is valid
|
||||
*
|
||||
@@ -484,27 +610,6 @@ Server.isLoadStatus = function(message) {
|
||||
&& (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
|
||||
*
|
||||
@@ -544,6 +649,7 @@ Server.prototype._request = function(request) {
|
||||
|
||||
request.server = this;
|
||||
request.message.id = this._id;
|
||||
request.time = Date.now();
|
||||
|
||||
this._requests[request.message.id] = request;
|
||||
|
||||
@@ -564,8 +670,8 @@ Server.prototype._request = function(request) {
|
||||
|
||||
Server.prototype._isConnected = function(request) {
|
||||
var isSubscribeRequest = request
|
||||
&& request.message.command === 'subscribe'
|
||||
&& this._ws.readyState === 1;
|
||||
&& request.message.command === 'subscribe'
|
||||
&& this._ws.readyState === 1;
|
||||
|
||||
return this._connected || (this._ws && isSubscribeRequest);
|
||||
};
|
||||
|
||||
@@ -1,227 +1,269 @@
|
||||
var AuthInfo = require('./authinfo').AuthInfo;
|
||||
var async = require('async');
|
||||
var blobClient = require('./blob').BlobClient;
|
||||
var AuthInfo = require('./authinfo').AuthInfo;
|
||||
var crypt = require('./crypt').Crypt;
|
||||
|
||||
|
||||
function VaultClient(opts) {
|
||||
if (!opts) opts = {};
|
||||
else if (typeof opts === "string") opts = {domain:opts};
|
||||
if (!opts) {
|
||||
opts = { };
|
||||
}
|
||||
|
||||
this.domain = opts.domain || 'ripple.com';
|
||||
this.authInfo = new AuthInfo();
|
||||
this.infos = {};
|
||||
}
|
||||
if (typeof opts === 'string') {
|
||||
opts = { domain: opts };
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
this.domain = opts.domain || 'ripple.com';
|
||||
this.authInfo = new AuthInfo();
|
||||
this.infos = { };
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param {string} address - Account address to query
|
||||
* @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
|
||||
if (!url) return fn(new Error("Blob vault URL is required"));
|
||||
blobClient.getRippleName(url, address, fn);
|
||||
if (!url) {
|
||||
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
|
||||
*
|
||||
* @param {string} username
|
||||
* @param {string} password
|
||||
* @param {function} fn - Callback function
|
||||
*/
|
||||
VaultClient.prototype.login = function(username, password, fn) {
|
||||
|
||||
VaultClient.prototype.login = function(username, password, callback) {
|
||||
var self = this;
|
||||
|
||||
self.authInfo.get(self.domain, username, function(err, authInfo){
|
||||
if (err) return fn(err);
|
||||
function getAuthInfo(callback) {
|
||||
self.authInfo.get(self.domain, username, function(err, authInfo) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (authInfo.version !== 3) {
|
||||
return fn(new Error("This wallet is incompatible with this version of the vault-client."));
|
||||
}
|
||||
if (authInfo.version !== 3) {
|
||||
return callback(new Error('This wallet is incompatible with this version of the vault-client.'));
|
||||
}
|
||||
|
||||
if (!authInfo.pakdf) {
|
||||
return fn(new Error("No settings for PAKDF in auth packet."));
|
||||
}
|
||||
if (!authInfo.pakdf) {
|
||||
return callback(new Error('No settings for PAKDF in auth packet.'));
|
||||
}
|
||||
|
||||
if (!authInfo.exists) {
|
||||
return fn(new Error("User does not exist."));
|
||||
}
|
||||
if (!authInfo.exists) {
|
||||
return callback(new Error('User does not exist.'));
|
||||
}
|
||||
|
||||
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.'));
|
||||
}
|
||||
|
||||
callback(null, authInfo);
|
||||
});
|
||||
};
|
||||
|
||||
function deriveLoginKeys(authInfo, callback) {
|
||||
//derive login keys
|
||||
crypt.derive(authInfo.pakdf, 'login', username.toLowerCase(), password, function(err, keys){
|
||||
if (err) return fn(err);
|
||||
crypt.derive(authInfo.pakdf, 'login', username.toLowerCase(), password, function(err, keys) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
callback(null, authInfo, keys);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
blobClient.get(authInfo.blobvault, keys.id, keys.crypt, function (err, blob) {
|
||||
if (err) return fn(err);
|
||||
function getBlob(authInfo, keys, callback) {
|
||||
blobClient.get(authInfo.blobvault, keys.id, keys.crypt, function(err, blob) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
self.infos[keys.id] = authInfo; //save for relogin
|
||||
//save for relogin
|
||||
self.infos[keys.id] = authInfo;
|
||||
|
||||
fn (null, {
|
||||
blob : blob,
|
||||
username : authInfo.username,
|
||||
verified : authInfo.emailVerified
|
||||
});
|
||||
callback(null, {
|
||||
blob: blob,
|
||||
username: authInfo.username,
|
||||
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.
|
||||
*
|
||||
* @param {string} url - Blob vault url
|
||||
* @param {string} id - Blob id from previously retreived blob
|
||||
* @param {string} key - Blob decryption key
|
||||
* @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
|
||||
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) {
|
||||
if (err) return fn(err);
|
||||
|
||||
fn (null, {
|
||||
blob : blob,
|
||||
});
|
||||
blobClient.get(url, id, key, function(err, blob) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
callback (null, { blob: blob });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Decrypt the secret key using a username and password
|
||||
*
|
||||
* @param {string} username
|
||||
* @param {string} password
|
||||
* @param {string} encryptSecret
|
||||
* @param {function} fn - Callback function
|
||||
*/
|
||||
VaultClient.prototype.unlock = function(username, password, encryptSecret, fn) {
|
||||
|
||||
VaultClient.prototype.unlock = function(username, password, encryptSecret, callback) {
|
||||
var self = this;
|
||||
|
||||
self.authInfo.get(self.domain, username, function(err, authInfo){
|
||||
if (err) return fn(err);
|
||||
|
||||
function deriveUnlockKey(authInfo, callback) {
|
||||
//derive unlock key
|
||||
crypt.derive(authInfo.pakdf, 'unlock', username.toLowerCase(), password, function(err, keys){
|
||||
if (err) return fn(err);
|
||||
crypt.derive(authInfo.pakdf, 'unlock', username.toLowerCase(), password, function(err, keys) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
fn(null, {
|
||||
keys : keys,
|
||||
secret : crypt.decrypt(keys.unlock, encryptSecret)
|
||||
callback(null, {
|
||||
keys: keys,
|
||||
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
|
||||
* the username and password
|
||||
*
|
||||
* @param {string} username
|
||||
* @param {string} password
|
||||
* @param {function} fn - Callback function
|
||||
*/
|
||||
VaultClient.prototype.loginAndUnlock = function(username, password, fn) {
|
||||
|
||||
VaultClient.prototype.loginAndUnlock = function(username, password, callback) {
|
||||
var self = this;
|
||||
|
||||
this.login(username, password, function(err, resp){
|
||||
if (err) return fn(err);
|
||||
function deriveUnlockKey(authInfo, blob, callback) {
|
||||
//derive unlock key
|
||||
crypt.derive(authInfo.pakdf, 'unlock', username.toLowerCase(), password, function(err, keys) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (!resp.blob || !resp.blob.encrypted_secret)
|
||||
return fn(new Error("Unable to retrieve blob and secret."));
|
||||
callback(null, {
|
||||
blob: blob,
|
||||
unlock: keys.unlock,
|
||||
secret: crypt.decrypt(keys.unlock, blob.encrypted_secret),
|
||||
username: authInfo.username,
|
||||
verified: authInfo.emailVerified
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
if (!resp.blob.id || !resp.blob.key)
|
||||
return fn(new Error("Unable to retrieve keys."));
|
||||
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 fn(new Error("Unable to find authInfo"));
|
||||
|
||||
//derive unlock key
|
||||
crypt.derive(authInfo.pakdf, 'unlock', username.toLowerCase(), password, function(err, keys){
|
||||
if (err) return fn(err);
|
||||
if (!authInfo) {
|
||||
return callback(new Error('Unable to find authInfo'));
|
||||
}
|
||||
|
||||
fn(null, {
|
||||
blob : resp.blob,
|
||||
unlock : keys.unlock,
|
||||
secret : crypt.decrypt(keys.unlock, resp.blob.encrypted_secret),
|
||||
username : authInfo.username,
|
||||
verified : authInfo.emailVerified
|
||||
});
|
||||
});
|
||||
deriveUnlockKey(authInfo, resp.blob, callback);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check blobvault for existance of username
|
||||
*
|
||||
* @param {string} username
|
||||
* @param {function} fn - Callback function
|
||||
*/
|
||||
VaultClient.prototype.exists = function (username, fn) {
|
||||
this.authInfo.get(this.domain, username.toLowerCase(), function(err, authInfo){
|
||||
if (err) return fn(err);
|
||||
return fn(null, !!authInfo.exists);
|
||||
|
||||
VaultClient.prototype.exists = function(username, callback) {
|
||||
this.authInfo.get(this.domain, username.toLowerCase(), function(err, authInfo) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
callback(null, !!authInfo.exists);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
/**
|
||||
* Verify an email address for an existing user
|
||||
*
|
||||
* @param {string} username
|
||||
* @param {string} token - Verification token
|
||||
* @param {function} fn - Callback function
|
||||
*/
|
||||
VaultClient.prototype.verify = function (username, token, fn) {
|
||||
|
||||
this.authInfo.get(this.domain, username.toLowerCase(), function (err, authInfo) {
|
||||
if (err) return fn(err);
|
||||
VaultClient.prototype.verify = function(username, token, callback) {
|
||||
var self = this;
|
||||
|
||||
if ("string" !== typeof authInfo.blobvault) {
|
||||
return fn(new Error("No blobvault specified in the authinfo."));
|
||||
this.authInfo.get(this.domain, username.toLowerCase(), function(err, authInfo) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
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
|
||||
*
|
||||
* @param {object} options
|
||||
@@ -233,53 +275,77 @@ VaultClient.prototype.verify = function (username, token, fn) {
|
||||
* @param {object} options.oldUserBlob //optional
|
||||
* @param {function} fn
|
||||
*/
|
||||
VaultClient.prototype.register = function (options, fn) {
|
||||
var self = this,
|
||||
username = this.normalizeUsername(options.username),
|
||||
password = this.normalizePassword(options.password);
|
||||
|
||||
VaultClient.prototype.register = function(options, fn) {
|
||||
var self = this;
|
||||
var username = String(options.username).trim();
|
||||
var password = String(options.password).trim();
|
||||
|
||||
self.authInfo.get(self.domain, username, function(err, authInfo){
|
||||
function getAuthInfo(callback) {
|
||||
self.authInfo.get(self.domain, username, function(err, authInfo) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (err) return fn(err);
|
||||
if (typeof authInfo.blobvault !== 'string') {
|
||||
return callback(new Error('No blobvault specified in the authinfo.'));
|
||||
}
|
||||
|
||||
if ("string" !== typeof authInfo.blobvault) {
|
||||
return fn(new Error("No blobvault specified in the authinfo."));
|
||||
}
|
||||
if (!authInfo.pakdf) {
|
||||
return callback(new Error('No settings for PAKDF in auth packet.'));
|
||||
}
|
||||
|
||||
|
||||
if (!authInfo.pakdf) {
|
||||
return fn(new Error("No settings for PAKDF in auth packet."));
|
||||
}
|
||||
|
||||
//derive login keys
|
||||
crypt.derive(authInfo.pakdf, 'login', username.toLowerCase(), password, function(err, loginKeys){
|
||||
if (err) return fn(err);
|
||||
|
||||
//derive unlock key
|
||||
crypt.derive(authInfo.pakdf, 'unlock', username.toLowerCase(), password, function(err, unlockKeys){
|
||||
if (err) return fn(err);
|
||||
|
||||
var params = {
|
||||
'url' : authInfo.blobvault,
|
||||
'id' : loginKeys.id,
|
||||
'crypt' : loginKeys.crypt,
|
||||
'unlock' : unlockKeys.unlock,
|
||||
'username' : username,
|
||||
'email' : options.email,
|
||||
'masterkey' : options.masterkey || crypt.createMaster(),
|
||||
'activateLink' : options.activateLink,
|
||||
'oldUserBlob' : options.oldUserBlob
|
||||
};
|
||||
|
||||
blobClient.create(params, function(err, blob){
|
||||
if (err) return fn(err);
|
||||
fn(null, blob, loginKeys, authInfo.username);
|
||||
});
|
||||
});
|
||||
callback(null, authInfo);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function deriveKeys(authInfo, callback) {
|
||||
// derive unlock and login keys
|
||||
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 = {
|
||||
url: authInfo.blobvault,
|
||||
id: keys.loginKeys.id,
|
||||
crypt: keys.loginKeys.crypt,
|
||||
unlock: keys.unlockKeys.unlock,
|
||||
username: username,
|
||||
email: options.email,
|
||||
masterkey: options.masterkey || crypt.createMaster(),
|
||||
activateLink: options.activateLink,
|
||||
oldUserBlob: options.oldUserBlob
|
||||
};
|
||||
|
||||
blobClient.create(params, function(err, blob) {
|
||||
if (err) {
|
||||
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'
|
||||
};
|
||||
|
||||
Remote.prototype.addServer = function(){};
|
||||
var test_remote = new Remote({});
|
||||
//Remote.prototype.addServer = function(){};
|
||||
var test_remote = new Remote();
|
||||
|
||||
assert.throws(function(){
|
||||
Message.verifyHashSignature(data);
|
||||
}, /(?=.*callback\ function).*/);
|
||||
|
||||
|
||||
});
|
||||
|
||||
it('should respond with an error if the hash is missing or invalid', function(done){
|
||||
@@ -202,8 +200,8 @@ describe('Message', function(){
|
||||
account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'
|
||||
};
|
||||
|
||||
Remote.prototype.addServer = function(){};
|
||||
var test_remote = new Remote({});
|
||||
//Remote.prototype.addServer = function(){};
|
||||
var test_remote = new Remote();
|
||||
test_remote.state = 'online';
|
||||
|
||||
Message.verifyHashSignature(data, test_remote, function(err, valid){
|
||||
@@ -221,8 +219,8 @@ describe('Message', function(){
|
||||
signature: 'AAAAHOUJQzG/7BO82fGNt1TNE+GGVXKuQQ0N2nTO+iJETE69PiHnaAkkOzovM177OosxbKjpt3KvwuJflgUB2YGvgjk='
|
||||
};
|
||||
|
||||
Remote.prototype.addServer = function(){};
|
||||
var test_remote = new Remote({});
|
||||
//Remote.prototype.addServer = function(){};
|
||||
var test_remote = new Remote();
|
||||
test_remote.state = 'online';
|
||||
|
||||
Message.verifyHashSignature(data, test_remote, function(err, valid){
|
||||
@@ -240,8 +238,8 @@ describe('Message', function(){
|
||||
account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'
|
||||
};
|
||||
|
||||
Remote.prototype.addServer = function(){};
|
||||
var test_remote = new Remote({});
|
||||
//Remote.prototype.addServer = function(){};
|
||||
var test_remote = new Remote();
|
||||
test_remote.state = 'online';
|
||||
|
||||
Message.verifyHashSignature(data, test_remote, function(err, valid){
|
||||
@@ -260,8 +258,8 @@ describe('Message', function(){
|
||||
signature: 'AAAAHMIPCQGLgdnpX1Ccv1wHb56H4NggxIM6U08Qkb9mUjN2Vn9pZ3CHvq1yWLBi6NqpW+7kedLnmfu4VG2+y43p4Xs='
|
||||
};
|
||||
|
||||
Remote.prototype.addServer = function(){};
|
||||
var test_remote = new Remote({});
|
||||
//Remote.prototype.addServer = function(){};
|
||||
var test_remote = new Remote();
|
||||
test_remote.state = 'online';
|
||||
test_remote.request_account_info = function(account, callback) {
|
||||
if (account === data.account) {
|
||||
@@ -296,8 +294,8 @@ describe('Message', function(){
|
||||
signature: 'AAAAG+dB/rAjZ5m8eQ/opcqQOJsFbKxOu9jq9KrOAlNO4OdcBDXyCBlkZqS9Xr8oZI2uh0boVsgYOS3pOLJz+Dh3Otk='
|
||||
};
|
||||
|
||||
Remote.prototype.addServer = function(){};
|
||||
var test_remote = new Remote({});
|
||||
//Remote.prototype.addServer = function(){};
|
||||
var test_remote = new Remote();
|
||||
test_remote.state = 'online';
|
||||
test_remote.request_account_info = function(account, callback) {
|
||||
if (account === data.account) {
|
||||
|
||||
@@ -5,40 +5,182 @@ var Remote = utils.load_module('remote').Remote;
|
||||
var Server = utils.load_module('server').Server;
|
||||
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('initialing a remote with options', function () {
|
||||
beforeEach(function () {
|
||||
options = {
|
||||
trace : true,
|
||||
trusted: true,
|
||||
local_signing: true,
|
||||
servers: [
|
||||
{ host: 's-west.ripple.com', port: 443, secure: true },
|
||||
{ host: 's-east.ripple.com', port: 443, secure: true }
|
||||
],
|
||||
beforeEach(function () {
|
||||
options = {
|
||||
trace : true,
|
||||
trusted: true,
|
||||
local_signing: true,
|
||||
|
||||
blobvault : 'https://blobvault.payward.com',
|
||||
persistent_auth : false,
|
||||
transactions_per_page: 50,
|
||||
servers: [
|
||||
{ host: 's-west.ripple.com', port: 443, secure: true },
|
||||
{ host: 's-east.ripple.com', port: 443, secure: true }
|
||||
],
|
||||
|
||||
bridge: {
|
||||
out: {
|
||||
// 'bitcoin': 'localhost:3000'
|
||||
// 'bitcoin': 'https://www.bitstamp.net/ripple/bridge/out/bitcoin/'
|
||||
}
|
||||
},
|
||||
blobvault : 'https://blobvault.payward.com',
|
||||
persistent_auth : false,
|
||||
transactions_per_page: 50,
|
||||
|
||||
};
|
||||
})
|
||||
it('should add a server for each specified', function (done) {
|
||||
var remote = new Remote(options);
|
||||
done();
|
||||
})
|
||||
bridge: {
|
||||
out: {
|
||||
// 'bitcoin': 'localhost:3000'
|
||||
// 'bitcoin': 'https://www.bitstamp.net/ripple/bridge/out/bitcoin/'
|
||||
}
|
||||
},
|
||||
|
||||
};
|
||||
})
|
||||
|
||||
describe('functions that return request objects', function () {
|
||||
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();
|
||||
})
|
||||
});
|
||||
|
||||
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 () {
|
||||
callback = function () {}
|
||||
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'),
|
||||
RippleTxt = require('../src/js/ripple/rippletxt').RippleTxt,
|
||||
AuthInfo = require('../src/js/ripple/authinfo').AuthInfo,
|
||||
VaultClient = require('../src/js/ripple/vaultclient').VaultClient,
|
||||
Blob = require('../src/js/ripple/blob').BlobClient.Blob,
|
||||
UInt256 = require('../src/js/ripple/uint256').UInt256;
|
||||
var assert = require('assert');
|
||||
var RippleTxt = require('../src/js/ripple/rippletxt').RippleTxt;
|
||||
var AuthInfo = require('../src/js/ripple/authinfo').AuthInfo;
|
||||
var VaultClient = require('../src/js/ripple/vaultclient').VaultClient;
|
||||
var Blob = require('../src/js/ripple/blob').Blob;
|
||||
var UInt256 = require('../src/js/ripple/uint256').UInt256;
|
||||
|
||||
var exampleData = {
|
||||
id : "ef203d3e76552c0592384f909e6f61f1d1f02f61f07643ce015d8b0c9710dd2f",
|
||||
crypt : "f0cc91a7c1091682c245cd8e13c246cc150b2cf98b17dd6ef092019c99dc9d82",
|
||||
unlock : "3e15fe3218a9c664835a6f585582e14480112110ddbe50e5028d05fc5bd9b5f4",
|
||||
blobURL : "https://id.staging.ripple.com",
|
||||
username : "exampleUser",
|
||||
password : "pass word",
|
||||
domain : "staging.ripple.com",
|
||||
encrypted_secret : "APYqtqvjJk/J324rx2BGGzUiQ3mtmMMhMsbrUmgxb00W2aFVQzCC2mqd58Z17gzeUUcjtjAm"
|
||||
id: 'ef203d3e76552c0592384f909e6f61f1d1f02f61f07643ce015d8b0c9710dd2f',
|
||||
crypt: 'f0cc91a7c1091682c245cd8e13c246cc150b2cf98b17dd6ef092019c99dc9d82',
|
||||
unlock: '3e15fe3218a9c664835a6f585582e14480112110ddbe50e5028d05fc5bd9b5f4',
|
||||
blobURL: 'https://id.staging.ripple.com',
|
||||
username: 'exampleUser',
|
||||
password: 'pass word',
|
||||
domain: 'staging.ripple.com',
|
||||
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 () {
|
||||
it('should get the context of a ripple.txt file from a given domain', function (done){
|
||||
describe('Ripple Txt', function() {
|
||||
it('should get the content of a ripple.txt file from a given domain', function(done) {
|
||||
var rt = new RippleTxt();
|
||||
rt.get(exampleData.domain, function (err, resp){
|
||||
assert.ifError(err);
|
||||
assert.equal(typeof resp, 'object');
|
||||
|
||||
var requestedUrls = [ ];
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('AuthInfo', function() {
|
||||
it('should get auth info', function(done) {
|
||||
var auth = new AuthInfo();
|
||||
|
||||
describe('AuthInfo', function () {
|
||||
var auth = new AuthInfo();
|
||||
auth._getRippleTxt = function(domain, callback) {
|
||||
assert.strictEqual(domain, 'staging.ripple.com');
|
||||
callback(null, rippleTxtRes);
|
||||
};
|
||||
|
||||
it ('should get authinfo given a domain and username', function (done){
|
||||
auth.get(exampleData.domain, exampleData.user, function (err, resp){
|
||||
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.equal(typeof resp, 'object');
|
||||
Object.keys(authInfoRes.body).forEach(function(prop) {
|
||||
assert(resp.hasOwnProperty(prop));
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -47,8 +109,8 @@ describe('AuthInfo', function () {
|
||||
describe('VaultClient', function () {
|
||||
var client = new VaultClient(exampleData.domain);
|
||||
|
||||
describe('#initialization', function () {
|
||||
it('should be initialized with a domain', function () {
|
||||
describe('#initialization', function() {
|
||||
it('should be initialized with a domain', function() {
|
||||
var client = new VaultClient({ domain: exampleData.domain });
|
||||
assert.strictEqual(client.domain, exampleData.domain);
|
||||
});
|
||||
@@ -59,23 +121,21 @@ describe('VaultClient', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#exists', function () {
|
||||
it('should determine if a username exists on the domain', function (done) {
|
||||
describe('#exists', function() {
|
||||
it('should determine if a username exists on the domain', function(done) {
|
||||
this.timeout(10000);
|
||||
client.exists(exampleData.username, function(err, resp) {
|
||||
|
||||
assert.ifError(err);
|
||||
assert.equal(typeof resp, 'boolean');
|
||||
assert.strictEqual(typeof resp, 'boolean');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#login', function () {
|
||||
it('with username and password should retrive the blob, crypt key, and id', function (done) {
|
||||
describe('#login', function() {
|
||||
it('with username and password should retrive the blob, crypt key, and id', function(done) {
|
||||
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.equal(typeof resp, 'object');
|
||||
assert(resp.blob instanceof Blob);
|
||||
@@ -90,12 +150,10 @@ describe('VaultClient', function () {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('#relogin', function () {
|
||||
it('should retrieve the decrypted blob with blob vault url, id, and crypt key', function (done) {
|
||||
describe('#relogin', function() {
|
||||
it('should retrieve the decrypted blob with blob vault url, id, and crypt key', function(done) {
|
||||
this.timeout(10000);
|
||||
client.relogin(exampleData.blobURL, exampleData.id, exampleData.crypt, function(err, resp) {
|
||||
|
||||
assert.ifError(err);
|
||||
assert.equal(typeof resp, 'object');
|
||||
assert(resp.blob instanceof Blob);
|
||||
@@ -107,8 +165,7 @@ describe('VaultClient', function () {
|
||||
describe('#unlock', function() {
|
||||
it('should access the wallet secret using encryption secret, username and password', function (done) {
|
||||
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.equal(typeof resp, 'object');
|
||||
assert.equal(typeof resp.keys, 'object');
|
||||
@@ -122,8 +179,7 @@ describe('VaultClient', function () {
|
||||
describe('#loginAndUnlock', function () {
|
||||
it('should get the decrypted blob and decrypted secret given name and password', function (done) {
|
||||
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.equal(typeof resp, 'object');
|
||||
assert(resp.blob instanceof Blob);
|
||||
@@ -147,17 +203,15 @@ describe('Blob', function () {
|
||||
var vaultClient;
|
||||
|
||||
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);
|
||||
var blob = resp.blob;
|
||||
|
||||
|
||||
describe('#set', function () {
|
||||
it('should set a new property in the blob', function (done) {
|
||||
this.timeout(10000);
|
||||
|
||||
blob.extend("/testObject", {
|
||||
foo : [],
|
||||
describe('#set', function() {
|
||||
it('should set a new property in the blob', function(done) {
|
||||
this.timeout(10000)
|
||||
blob.extend('/testObject', {
|
||||
foo: [],
|
||||
}, function(err, resp){
|
||||
assert.ifError(err);
|
||||
assert.equal(resp.result, 'success');
|
||||
@@ -166,12 +220,11 @@ describe('Blob', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#extend', function () {
|
||||
it('should extend an object in the blob', function (done) {
|
||||
this.timeout(10000);
|
||||
|
||||
blob.extend("/testObject", {
|
||||
foobar : "baz",
|
||||
describe('#extend', function() {
|
||||
it('should extend an object in the blob', function(done) {
|
||||
this.timeout(10000)
|
||||
blob.extend('/testObject', {
|
||||
foobar: 'baz',
|
||||
}, function(err, resp){
|
||||
assert.ifError(err);
|
||||
assert.equal(resp.result, 'success');
|
||||
@@ -180,11 +233,10 @@ describe('Blob', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#unset', function () {
|
||||
it('should remove a property from the blob', function (done) {
|
||||
this.timeout(10000);
|
||||
|
||||
blob.unset("/testObject", function(err, resp){
|
||||
describe('#unset', function() {
|
||||
it('should remove a property from the blob', function(done) {
|
||||
this.timeout(10000)
|
||||
blob.unset('/testObject', function(err, resp){
|
||||
assert.ifError(err);
|
||||
assert.equal(resp.result, 'success');
|
||||
done();
|
||||
@@ -192,13 +244,12 @@ describe('Blob', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#unshift', function () {
|
||||
it('should prepend an item to an array in the blob', function (done) {
|
||||
this.timeout(10000);
|
||||
|
||||
blob.unshift("/testArray", {
|
||||
name : "bob",
|
||||
address : "1234"
|
||||
describe('#unshift', function() {
|
||||
it('should prepend an item to an array in the blob', function(done) {
|
||||
this.timeout(10000)
|
||||
blob.unshift('/testArray', {
|
||||
name: 'bob',
|
||||
address: '1234'
|
||||
}, function(err, resp){
|
||||
assert.ifError(err);
|
||||
assert.equal(resp.result, 'success');
|
||||
@@ -207,11 +258,11 @@ 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) {
|
||||
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.equal(resp.result, 'success');
|
||||
done();
|
||||
@@ -219,14 +270,12 @@ describe('Blob', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#consolidate', function () {
|
||||
it('should consolidate and save changes to the blob', function (done) {
|
||||
this.timeout(10000);
|
||||
|
||||
describe('#consolidate', function() {
|
||||
it('should consolidate and save changes to the blob', function(done) {
|
||||
this.timeout(10000)
|
||||
blob.unset('/testArray', function(err, resp){
|
||||
assert.ifError(err);
|
||||
assert.equal(resp.result, 'success');
|
||||
|
||||
blob.consolidate(function(err, resp){
|
||||
assert.ifError(err);
|
||||
assert.equal(resp.result, 'success');
|
||||
@@ -236,7 +285,6 @@ describe('Blob', function () {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/********* Identity tests ***********/
|
||||
describe('#identity_set', function () {
|
||||
it('should set an identity property', function (done) {
|
||||
@@ -289,4 +337,3 @@ describe('Blob', function () {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user