mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-25 22:55:49 +00:00
Cleanup
This commit is contained in:
@@ -1,36 +1,45 @@
|
|||||||
var RippleTxt = require('./rippletxt');
|
|
||||||
var request = require('superagent');
|
var request = require('superagent');
|
||||||
|
var RippleTxt = require('./rippletxt').RippleTxt;
|
||||||
|
|
||||||
function AuthInfo () {
|
function AuthInfo() {
|
||||||
this.rippleTxt = new RippleTxt;
|
this.rippleTxt = new RippleTxt;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get auth info for a given username
|
* Get auth info for a given username
|
||||||
|
*
|
||||||
* @param {string} domain - Domain which hosts the user's info
|
* @param {string} domain - Domain which hosts the user's info
|
||||||
* @param {string} username - Username who's info we are retreiving
|
* @param {string} username - Username who's info we are retreiving
|
||||||
* @param {function} fn - Callback function
|
* @param {function} fn - Callback function
|
||||||
*/
|
*/
|
||||||
AuthInfo.prototype.get = function (domain, username, fn) {
|
|
||||||
|
AuthInfo.prototype.get = function(domain, username, fn) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.rippleTxt.get(domain, function(err, txt){
|
self.rippleTxt.get(domain, function(err, txt) {
|
||||||
if (err) return fn(err);
|
if (err) {
|
||||||
|
fn(err);
|
||||||
processTxt(txt)
|
} else {
|
||||||
|
processTxt(txt)
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
function processTxt(txt) {
|
function processTxt(txt) {
|
||||||
if (!txt.authinfo_url) return fn(new Error("Authentication is not supported on "+domain));
|
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;
|
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){
|
request.get(url, function(err, resp){
|
||||||
if (err || resp.error) return fn(new Error("Authentication info server unreachable"));
|
if (err || resp.error) {
|
||||||
fn(null, resp.body);
|
fn(new Error('Authentication info server unreachable'));
|
||||||
|
} else {
|
||||||
|
fn(null, resp.body);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
module.exports = AuthInfo;
|
exports.AuthInfo = AuthInfo;
|
||||||
|
|||||||
@@ -1,100 +1,109 @@
|
|||||||
var crypt = require('./crypt'),
|
var request = require('superagent');
|
||||||
request = require('superagent'),
|
var extend = require('extend');
|
||||||
extend = require("extend");
|
var crypt = require('./crypt').Crypt;
|
||||||
|
|
||||||
//Blob object class
|
//Blob object class
|
||||||
var BlobObj = function (url, id, key) {
|
function BlobObj(url, id, key) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.data = {};
|
this.data = { };
|
||||||
};
|
};
|
||||||
|
|
||||||
// Blob operations
|
// Blob operations
|
||||||
// Do NOT change the mapping of existing ops
|
// Do NOT change the mapping of existing ops
|
||||||
BlobObj.ops = {
|
BlobObj.ops = {
|
||||||
// Special
|
// Special
|
||||||
"noop" : 0,
|
noop: 0,
|
||||||
|
|
||||||
// Simple ops
|
// Simple ops
|
||||||
"set" : 16,
|
set: 16,
|
||||||
"unset" : 17,
|
unset: 17,
|
||||||
"extend" : 18,
|
extend: 18,
|
||||||
|
|
||||||
// Meta ops
|
// Meta ops
|
||||||
"push" : 32,
|
push: 32,
|
||||||
"pop" : 33,
|
pop: 33,
|
||||||
"shift" : 34,
|
shift: 34,
|
||||||
"unshift" : 35,
|
unshift: 35,
|
||||||
"filter" : 36
|
filter: 36
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
BlobObj.opsReverseMap = [];
|
BlobObj.opsReverseMap = [ ];
|
||||||
|
|
||||||
for (var name in BlobObj.ops) {
|
for (var name in BlobObj.ops) {
|
||||||
BlobObj.opsReverseMap[BlobObj.ops[name]] = name;
|
BlobObj.opsReverseMap[BlobObj.ops[name]] = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
/*
|
|
||||||
* Initialize a new blob object
|
* Initialize a new blob object
|
||||||
|
*
|
||||||
* @param {function} fn - Callback function
|
* @param {function} fn - Callback function
|
||||||
*/
|
*/
|
||||||
BlobObj.prototype.init = function (fn) {
|
|
||||||
|
BlobObj.prototype.init = function(fn) {
|
||||||
var self = this, url;
|
var self = this, url;
|
||||||
if (self.url.indexOf("://") === -1) self.url = "http://" + url;
|
|
||||||
|
if (self.url.indexOf('://') === -1) {
|
||||||
|
self.url = 'http://' + url;
|
||||||
|
}
|
||||||
|
|
||||||
url = self.url + '/v1/blob/' + self.id;
|
url = self.url + '/v1/blob/' + self.id;
|
||||||
|
|
||||||
request.get(url, function(err, resp){
|
request.get(url, function(err, resp) {
|
||||||
|
if (err || !resp.body || resp.body.result !== 'success') {
|
||||||
|
return fn(new Error('Could not retrieve blob'));
|
||||||
|
}
|
||||||
|
|
||||||
if (err || !resp.body || resp.body.result !== 'success')
|
self.revision = resp.body.revision;
|
||||||
return fn(new Error("Could not retrieve blob"));
|
self.encrypted_secret = resp.body.encrypted_secret;
|
||||||
|
|
||||||
self.revision = resp.body.revision;
|
if (!self.decrypt(resp.body.blob)) {
|
||||||
self.encrypted_secret = resp.body.encrypted_secret;
|
return fn(new Error('Error while decrypting blob'));
|
||||||
|
}
|
||||||
|
|
||||||
if (!self.decrypt(resp.body.blob)) {
|
//Apply patches
|
||||||
return fn(new Error("Error while decrypting blob"));
|
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
|
//return with newly decrypted blob
|
||||||
if (resp.body.patches && resp.body.patches.length) {
|
fn(null, self);
|
||||||
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
|
|
||||||
|
|
||||||
}).timeout(8000);
|
}).timeout(8000);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
/*
|
|
||||||
* Consolidate -
|
* Consolidate -
|
||||||
* Consolidate patches as a new revision
|
* Consolidate patches as a new revision
|
||||||
|
*
|
||||||
* @param {function} fn - Callback function
|
* @param {function} fn - Callback function
|
||||||
*/
|
*/
|
||||||
BlobObj.prototype.consolidate = function (fn) {
|
|
||||||
|
|
||||||
|
BlobObj.prototype.consolidate = function(fn) {
|
||||||
// Callback is optional
|
// Callback is optional
|
||||||
if ("function" !== typeof fn) fn = function(){};
|
if (typeof fn !== 'function') {
|
||||||
|
fn = function(){};
|
||||||
|
}
|
||||||
|
|
||||||
console.log("client: blob: consolidation at revision", this.revision);
|
//console.log('client: blob: consolidation at revision', this.revision);
|
||||||
var encrypted = this.encrypt();
|
var encrypted = this.encrypt();
|
||||||
|
|
||||||
var config = {
|
var config = {
|
||||||
method : 'POST',
|
method: 'POST',
|
||||||
url : this.url + '/v1/blob/consolidate',
|
url: this.url + '/v1/blob/consolidate',
|
||||||
dataType : 'json',
|
dataType: 'json',
|
||||||
data : {
|
data: {
|
||||||
blob_id : this.id,
|
blob_id: this.id,
|
||||||
data : encrypted,
|
data: encrypted,
|
||||||
revision : this.revision
|
revision: this.revision
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -103,22 +112,25 @@ BlobObj.prototype.consolidate = function (fn) {
|
|||||||
request.post(signed.url)
|
request.post(signed.url)
|
||||||
.send(signed.data)
|
.send(signed.data)
|
||||||
.end(function(err, resp) {
|
.end(function(err, resp) {
|
||||||
|
// XXX Add better error information to exception
|
||||||
// XXX Add better error information to exception
|
if (err) {
|
||||||
if (err) return fn(new Error("Failed to consolidate blob - XHR error"));
|
fn(new Error('Failed to consolidate blob - XHR error'));
|
||||||
else if (resp.body && resp.body.result === 'success') return fn(null, resp.body);
|
} else if (resp.body && resp.body.result === 'success') {
|
||||||
else return fn(new Error("Failed to consolidate blob"));
|
fn(null, resp.body);
|
||||||
|
} else {
|
||||||
|
fn(new Error('Failed to consolidate blob'));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
/*
|
|
||||||
* ApplyEncryptedPatch -
|
* ApplyEncryptedPatch -
|
||||||
* save changes from a downloaded patch to the blob
|
* save changes from a downloaded patch to the blob
|
||||||
|
*
|
||||||
* @param {string} patch - encrypted patch string
|
* @param {string} patch - encrypted patch string
|
||||||
*/
|
*/
|
||||||
BlobObj.prototype.applyEncryptedPatch = function (patch)
|
|
||||||
{
|
BlobObj.prototype.applyEncryptedPatch = function(patch) {
|
||||||
try {
|
try {
|
||||||
var params = JSON.parse(crypt.decrypt(this.key, patch));
|
var params = JSON.parse(crypt.decrypt(this.key, patch));
|
||||||
var op = params.shift();
|
var op = params.shift();
|
||||||
@@ -128,55 +140,55 @@ BlobObj.prototype.applyEncryptedPatch = function (patch)
|
|||||||
this.revision++;
|
this.revision++;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("client: blob: failed to apply patch:", err.toString());
|
//console.log('client: blob: failed to apply patch:', err.toString());
|
||||||
console.log(err.stack);
|
//console.log(err.stack);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt secret with unlock key
|
* Encrypt secret with unlock key
|
||||||
|
*
|
||||||
* @param {string} secretUnlockkey
|
* @param {string} secretUnlockkey
|
||||||
*/
|
*/
|
||||||
BlobObj.prototype.encryptSecret = function (secretUnlockKey, secret) {
|
|
||||||
|
BlobObj.prototype.encryptSecret = function(secretUnlockKey, secret) {
|
||||||
return crypt.encrypt(secretUnlockKey, secret);
|
return crypt.encrypt(secretUnlockKey, secret);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt secret with unlock key
|
* Decrypt secret with unlock key
|
||||||
|
*
|
||||||
* @param {string} secretUnlockkey
|
* @param {string} secretUnlockkey
|
||||||
*/
|
*/
|
||||||
BlobObj.prototype.decryptSecret = function (secretUnlockKey) {
|
|
||||||
|
BlobObj.prototype.decryptSecret = function(secretUnlockKey) {
|
||||||
return crypt.decrypt(secretUnlockKey, this.encrypted_secret);
|
return crypt.decrypt(secretUnlockKey, this.encrypted_secret);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt blob with crypt key
|
* Decrypt blob with crypt key
|
||||||
|
*
|
||||||
* @param {string} data - encrypted blob data
|
* @param {string} data - encrypted blob data
|
||||||
*/
|
*/
|
||||||
BlobObj.prototype.decrypt = function (data) {
|
|
||||||
|
|
||||||
|
BlobObj.prototype.decrypt = function(data) {
|
||||||
try {
|
try {
|
||||||
this.data = JSON.parse(crypt.decrypt(this.key, data));
|
this.data = JSON.parse(crypt.decrypt(this.key, data));
|
||||||
return this;
|
return this;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("client: blob: decryption failed", e.toString());
|
//console.log('client: blob: decryption failed', e.toString());
|
||||||
console.log(e.stack);
|
//console.log(e.stack);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt blob with crypt key
|
* Encrypt blob with crypt key
|
||||||
*/
|
*/
|
||||||
BlobObj.prototype.encrypt = function()
|
|
||||||
{
|
|
||||||
|
|
||||||
|
BlobObj.prototype.encrypt = function() {
|
||||||
// Filter Angular metadata before encryption
|
// Filter Angular metadata before encryption
|
||||||
// if ('object' === typeof this.data &&
|
// if ('object' === typeof this.data &&
|
||||||
// 'object' === typeof this.data.contacts)
|
// 'object' === typeof this.data.contacts)
|
||||||
@@ -185,68 +197,67 @@ BlobObj.prototype.encrypt = function()
|
|||||||
return crypt.encrypt(this.key, JSON.stringify(this.data));
|
return crypt.encrypt(this.key, JSON.stringify(this.data));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt recovery key
|
* Encrypt recovery key
|
||||||
|
*
|
||||||
* @param {string} secret
|
* @param {string} secret
|
||||||
* @param {string} blobDecryptKey
|
* @param {string} blobDecryptKey
|
||||||
*/
|
*/
|
||||||
BlobObj.prototype.encryptBlobCrypt = function (secret, blobDecryptKey) {
|
|
||||||
|
BlobObj.prototype.encryptBlobCrypt = function(secret, blobDecryptKey) {
|
||||||
var recoveryEncryptionKey = crypt.deriveRecoveryEncryptionKeyFromSecret(secret);
|
var recoveryEncryptionKey = crypt.deriveRecoveryEncryptionKeyFromSecret(secret);
|
||||||
return crypt.encrypt(recoveryEncryptionKey, blobDecryptKey);
|
return crypt.encrypt(recoveryEncryptionKey, blobDecryptKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt recovery key
|
* Decrypt recovery key
|
||||||
|
*
|
||||||
* @param {string} secret
|
* @param {string} secret
|
||||||
*/
|
*/
|
||||||
BlobObj.prototype.decryptBlobCrypt = function (secret) {
|
|
||||||
|
BlobObj.prototype.decryptBlobCrypt = function(secret) {
|
||||||
var recoveryEncryptionKey = crypt.deriveRecoveryEncryptionKeyFromSecret(secret);
|
var recoveryEncryptionKey = crypt.deriveRecoveryEncryptionKeyFromSecret(secret);
|
||||||
return crypt.decrypt(recoveryEncryptionKey, this.encrypted_blobdecrypt_key);
|
return crypt.decrypt(recoveryEncryptionKey, this.encrypted_blobdecrypt_key);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**** Blob updating functions ****/
|
/**** Blob updating functions ****/
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set blob element
|
* Set blob element
|
||||||
*/
|
*/
|
||||||
BlobObj.prototype.set = function (pointer, value, fn) {
|
|
||||||
|
BlobObj.prototype.set = function(pointer, value, fn) {
|
||||||
this.applyUpdate('set', pointer, [value]);
|
this.applyUpdate('set', pointer, [value]);
|
||||||
this.postUpdate('set', pointer, [value], fn);
|
this.postUpdate('set', pointer, [value], fn);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove blob element
|
* Remove blob element
|
||||||
*/
|
*/
|
||||||
BlobObj.prototype.unset = function (pointer, fn) {
|
|
||||||
|
BlobObj.prototype.unset = function(pointer, fn) {
|
||||||
this.applyUpdate('unset', pointer, []);
|
this.applyUpdate('unset', pointer, []);
|
||||||
this.postUpdate('unset', pointer, [], fn);
|
this.postUpdate('unset', pointer, [], fn);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extend blob object
|
* Extend blob object
|
||||||
*/
|
*/
|
||||||
BlobObj.prototype.extend = function (pointer, value, fn) {
|
|
||||||
|
BlobObj.prototype.extend = function(pointer, value, fn) {
|
||||||
this.applyUpdate('extend', pointer, [value]);
|
this.applyUpdate('extend', pointer, [value]);
|
||||||
this.postUpdate('extend', pointer, [value], fn);
|
this.postUpdate('extend', pointer, [value], fn);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepend blob array
|
* Prepend blob array
|
||||||
*/
|
*/
|
||||||
BlobObj.prototype.unshift = function (pointer, value, fn) {
|
|
||||||
|
BlobObj.prototype.unshift = function(pointer, value, fn) {
|
||||||
this.applyUpdate('unshift', pointer, [value]);
|
this.applyUpdate('unshift', pointer, [value]);
|
||||||
this.postUpdate('unshift', pointer, [value], fn);
|
this.postUpdate('unshift', pointer, [value], fn);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter the row(s) from an array.
|
* Filter the row(s) from an array.
|
||||||
*
|
*
|
||||||
@@ -255,11 +266,12 @@ BlobObj.prototype.unshift = function (pointer, value, fn) {
|
|||||||
*
|
*
|
||||||
* The subcommands can be any commands with the pointer parameter left out.
|
* The subcommands can be any commands with the pointer parameter left out.
|
||||||
*/
|
*/
|
||||||
BlobObj.prototype.filter = function (pointer, field, value, subcommands, callback) {
|
BlobObj.prototype.filter = function(pointer, field, value, subcommands, callback) {
|
||||||
var params = Array.prototype.slice.apply(arguments);
|
var params = Array.prototype.slice.apply(arguments);
|
||||||
if ("function" === typeof params[params.length-1]) {
|
if (typeof params[params.length - 1] === 'function') {
|
||||||
callback = params.pop();
|
callback = params.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
params.shift();
|
params.shift();
|
||||||
|
|
||||||
// Normalize subcommands to minimize the patch size
|
// Normalize subcommands to minimize the patch size
|
||||||
@@ -269,35 +281,33 @@ BlobObj.prototype.filter = function (pointer, field, value, subcommands, callbac
|
|||||||
this.postUpdate('filter', pointer, params, callback);
|
this.postUpdate('filter', pointer, params, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply udpdate to the blob
|
* Apply udpdate to the blob
|
||||||
*/
|
*/
|
||||||
BlobObj.prototype.applyUpdate = function (op, path, params) {
|
|
||||||
|
|
||||||
|
BlobObj.prototype.applyUpdate = function(op, path, params) {
|
||||||
// Exchange from numeric op code to string
|
// Exchange from numeric op code to string
|
||||||
if ("number" === typeof op) {
|
if (typeof op === 'number') {
|
||||||
op = BlobObj.opsReverseMap[op];
|
op = BlobObj.opsReverseMap[op];
|
||||||
}
|
}
|
||||||
if ("string" !== typeof op) {
|
|
||||||
throw new Error("Blob update op code must be a number or a valid op id string");
|
if (typeof op !== 'string') {
|
||||||
|
throw new Error('Blob update op code must be a number or a valid op id string');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Separate each step in the "pointer"
|
// Separate each step in the 'pointer'
|
||||||
var pointer = path.split("/");
|
var pointer = path.split('/');
|
||||||
|
|
||||||
var first = pointer.shift();
|
var first = pointer.shift();
|
||||||
if (first !== "") {
|
|
||||||
throw new Error("Invalid JSON pointer: "+path);
|
if (first !== '') {
|
||||||
|
throw new Error('Invalid JSON pointer: '+path);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._traverse(this.data, pointer, path, op, params);
|
this._traverse(this.data, pointer, path, op, params);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
//for applyUpdate function
|
//for applyUpdate function
|
||||||
BlobObj.prototype._traverse = function (context, pointer,
|
BlobObj.prototype._traverse = function(context, pointer, originalPointer, op, params) {
|
||||||
originalPointer, op, params) {
|
|
||||||
var _this = this;
|
var _this = this;
|
||||||
var part = _this.unescapeToken(pointer.shift());
|
var part = _this.unescapeToken(pointer.shift());
|
||||||
|
|
||||||
@@ -305,16 +315,15 @@ BlobObj.prototype._traverse = function (context, pointer,
|
|||||||
if (part === '-') {
|
if (part === '-') {
|
||||||
part = context.length;
|
part = context.length;
|
||||||
} else if (part % 1 !== 0 && part >= 0) {
|
} else if (part % 1 !== 0 && part >= 0) {
|
||||||
throw new Error("Invalid pointer, array element segments must be " +
|
throw new Error('Invalid pointer, array element segments must be a positive integer, zero or '-'');
|
||||||
"a positive integer, zero or '-'");
|
|
||||||
}
|
}
|
||||||
} else if ("object" !== typeof context) {
|
} else if (typeof context !== 'object') {
|
||||||
return null;
|
return null;
|
||||||
} else if (!context.hasOwnProperty(part)) {
|
} else if (!context.hasOwnProperty(part)) {
|
||||||
// Some opcodes create the path as they're going along
|
// Some opcodes create the path as they're going along
|
||||||
if (op === "set") {
|
if (op === 'set') {
|
||||||
context[part] = {};
|
context[part] = {};
|
||||||
} else if (op === "unshift") {
|
} else if (op === 'unshift') {
|
||||||
context[part] = [];
|
context[part] = [];
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
@@ -322,139 +331,133 @@ BlobObj.prototype._traverse = function (context, pointer,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (pointer.length !== 0) {
|
if (pointer.length !== 0) {
|
||||||
return this._traverse(context[part], pointer,
|
return this._traverse(context[part], pointer, originalPointer, op, params);
|
||||||
originalPointer, op, params);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (op) {
|
switch (op) {
|
||||||
case "set":
|
case 'set':
|
||||||
context[part] = params[0];
|
context[part] = params[0];
|
||||||
break;
|
break;
|
||||||
case "unset":
|
case 'unset':
|
||||||
if (Array.isArray(context)) {
|
if (Array.isArray(context)) {
|
||||||
context.splice(part, 1);
|
context.splice(part, 1);
|
||||||
} else {
|
} else {
|
||||||
delete context[part];
|
delete context[part];
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "extend":
|
case 'extend':
|
||||||
if ("object" !== typeof context[part]) {
|
if (typeof context[part] !== 'object') {
|
||||||
throw new Error("Tried to extend a non-object");
|
throw new Error('Tried to extend a non-object');
|
||||||
}
|
}
|
||||||
extend(true, context[part], params[0]);
|
extend(true, context[part], params[0]);
|
||||||
break;
|
break;
|
||||||
case "unshift":
|
case 'unshift':
|
||||||
if ("undefined" === typeof context[part]) {
|
if (typeof context[part] === 'undefined') {
|
||||||
context[part] = [];
|
context[part] = [ ];
|
||||||
} else if (!Array.isArray(context[part])) {
|
} else if (!Array.isArray(context[part])) {
|
||||||
throw new Error("Operator 'unshift' must be applied to an array.");
|
throw new Error('Operator "unshift" must be applied to an array.');
|
||||||
}
|
}
|
||||||
context[part].unshift(params[0]);
|
context[part].unshift(params[0]);
|
||||||
break;
|
break;
|
||||||
case "filter":
|
case 'filter':
|
||||||
if (Array.isArray(context[part])) {
|
if (Array.isArray(context[part])) {
|
||||||
context[part].forEach(function (element, i) {
|
context[part].forEach(function(element, i) {
|
||||||
if ("object" === typeof element &&
|
if (typeof element === 'object' && element.hasOwnProperty(params[0]) && element[params[0]] === params[1]) {
|
||||||
element.hasOwnProperty(params[0]) &&
|
var subpointer = originalPointer + '/' + i;
|
||||||
element[params[0]] === params[1]) {
|
var subcommands = normalizeSubcommands(params.slice(2));
|
||||||
var subpointer = originalPointer+"/"+i;
|
|
||||||
var subcommands = normalizeSubcommands(params.slice(2));
|
|
||||||
|
|
||||||
subcommands.forEach(function (subcommand) {
|
subcommands.forEach(function(subcommand) {
|
||||||
var op = subcommand[0];
|
var op = subcommand[0];
|
||||||
var pointer = subpointer+subcommand[1];
|
var pointer = subpointer + subcommand[1];
|
||||||
_this.applyUpdate(op, pointer, subcommand.slice(2));
|
_this.applyUpdate(op, pointer, subcommand.slice(2));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error("Unsupported op "+op);
|
throw new Error('Unsupported op '+op);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
BlobObj.prototype.escapeToken = function(token) {
|
||||||
BlobObj.prototype.escapeToken = function (token) {
|
return token.replace(/[~\/]/g, function(key) {
|
||||||
return token.replace(/[~\/]/g, function (key) { return key === "~" ? "~0" : "~1"; });
|
return key === '~' ? '~0' : '~1';
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
BlobObj.prototype.unescapeToken = function(str) {
|
BlobObj.prototype.unescapeToken = function(str) {
|
||||||
return str.replace(/~./g, function(m) {
|
return str.replace(/~./g, function(m) {
|
||||||
switch (m) {
|
switch (m) {
|
||||||
case "~0":
|
case '~0':
|
||||||
return "~";
|
return '~';
|
||||||
case "~1":
|
case '~1':
|
||||||
return "/";
|
return '/';
|
||||||
}
|
}
|
||||||
throw("Invalid tilde escape: " + m);
|
throw new Error('Invalid tilde escape: ' + m);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sumbit update to blob vault
|
* Sumbit update to blob vault
|
||||||
*/
|
*/
|
||||||
BlobObj.prototype.postUpdate = function (op, pointer, params, fn) {
|
|
||||||
// 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];
|
op = BlobObj.ops[op];
|
||||||
}
|
}
|
||||||
if ("number" !== typeof op) {
|
|
||||||
throw new Error("Blob update op code must be a number or a valid op id string");
|
if (typeof op !== 'number') {
|
||||||
}
|
throw new Error('Blob update op code must be a number or a valid op id string');
|
||||||
if (op < 0 || op > 255) {
|
|
||||||
throw new Error("Blob update op code out of bounds");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("client: blob: submitting update", BlobObj.opsReverseMap[op], pointer, params);
|
if (op < 0 || op > 255) {
|
||||||
|
throw new Error('Blob update op code out of bounds');
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log('client: blob: submitting update', BlobObj.opsReverseMap[op], pointer, params);
|
||||||
|
|
||||||
params.unshift(pointer);
|
params.unshift(pointer);
|
||||||
params.unshift(op);
|
params.unshift(op);
|
||||||
|
|
||||||
var config = {
|
var config = {
|
||||||
method : 'POST',
|
method: 'POST',
|
||||||
url : this.url + '/v1/blob/patch',
|
url: this.url + '/v1/blob/patch',
|
||||||
dataType : 'json',
|
dataType: 'json',
|
||||||
data : {
|
data: {
|
||||||
blob_id : this.id,
|
blob_id: this.id,
|
||||||
patch : crypt.encrypt(this.key, JSON.stringify(params))
|
patch: crypt.encrypt(this.key, JSON.stringify(params))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
var signed = crypt.signRequestHmac(config, this.data.auth_secret, this.id);
|
var signed = crypt.signRequestHmac(config, this.data.auth_secret, this.id);
|
||||||
|
|
||||||
request.post(signed.url)
|
request.post(signed.url)
|
||||||
.send(signed.data)
|
.send(signed.data)
|
||||||
.end(function(err, resp) {
|
.end(function(err, resp) {
|
||||||
if (err)
|
if (err) {
|
||||||
return fn(new Error("Patch could not be saved - XHR error"));
|
fn(new Error('Patch could not be saved - XHR error'));
|
||||||
else if (!resp.body || resp.body.result !== 'success')
|
} else if (!resp.body || resp.body.result !== 'success') {
|
||||||
return fn(new Error("Patch could not be saved - bad result"));
|
fn(new Error('Patch could not be saved - bad result'));
|
||||||
|
} else {
|
||||||
return fn(null, resp.body);
|
fn(null, resp.body);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/***** helper functions *****/
|
/***** helper functions *****/
|
||||||
|
|
||||||
|
|
||||||
function normalizeSubcommands(subcommands, compress) {
|
function normalizeSubcommands(subcommands, compress) {
|
||||||
// Normalize parameter structure
|
// Normalize parameter structure
|
||||||
if ("number" === typeof subcommands[0] ||
|
if (/(number|string)/.test(typeof subcommands[0])) {
|
||||||
"string" === typeof subcommands[0]) {
|
|
||||||
// Case 1: Single subcommand inline
|
// Case 1: Single subcommand inline
|
||||||
subcommands = [subcommands];
|
subcommands = [subcommands];
|
||||||
} else if (subcommands.length === 1 &&
|
} else if (subcommands.length === 1 && Array.isArray(subcommands[0]) && /(number|string)/.test(subcommands[0][0])) {
|
||||||
Array.isArray(subcommands[0]) &&
|
|
||||||
("number" === typeof subcommands[0][0] ||
|
|
||||||
"string" === typeof subcommands[0][0])) {
|
|
||||||
// Case 2: Single subcommand as array
|
// Case 2: Single subcommand as array
|
||||||
// (nothing to do)
|
// (nothing to do)
|
||||||
} else if (Array.isArray(subcommands[0])) {
|
} else if (Array.isArray(subcommands[0])) {
|
||||||
@@ -463,16 +466,19 @@ function normalizeSubcommands(subcommands, compress) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Normalize op name and convert strings to numeric codes
|
// Normalize op name and convert strings to numeric codes
|
||||||
subcommands = subcommands.map(function (subcommand) {
|
subcommands = subcommands.map(function(subcommand) {
|
||||||
if ("string" === typeof subcommand[0]) {
|
if (typeof subcommand[0] === 'string') {
|
||||||
subcommand[0] = BlobObj.ops[subcommand[0]];
|
subcommand[0] = BlobObj.ops[subcommand[0]];
|
||||||
}
|
}
|
||||||
if ("number" !== typeof subcommand[0]) {
|
|
||||||
throw new Error("Invalid op in subcommand");
|
if (typeof subcommand[0] !== 'number') {
|
||||||
|
throw new Error('Invalid op in subcommand');
|
||||||
}
|
}
|
||||||
if ("string" !== typeof subcommand[1]) {
|
|
||||||
throw new Error("Invalid path in subcommand");
|
if (typeof subcommand[1] !== 'string') {
|
||||||
|
throw new Error('Invalid path in subcommand');
|
||||||
}
|
}
|
||||||
|
|
||||||
return subcommand;
|
return subcommand;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -486,53 +492,63 @@ function normalizeSubcommands(subcommands, compress) {
|
|||||||
} else {
|
} else {
|
||||||
return subcommands;
|
return subcommands;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
/***** blob client methods ****/
|
/***** blob client methods ****/
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Blob object class
|
* Blob object class
|
||||||
*/
|
*/
|
||||||
module.exports.Blob = BlobObj
|
|
||||||
|
exports.Blob = BlobObj
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get ripple name for a given address
|
* Get ripple name for a given address
|
||||||
*/
|
*/
|
||||||
module.exports.getRippleName = function (url, address, fn) {
|
|
||||||
|
|
||||||
if (!crypt.isValidAddress(address)) return fn (new Error("Invalid ripple address"));
|
exports.getRippleName = function(url, address, fn) {
|
||||||
|
if (!crypt.isValidAddress(address)) {
|
||||||
|
return fn (new Error('Invalid ripple address'));
|
||||||
|
}
|
||||||
|
|
||||||
request.get(url + '/v1/user/' + address, function(err, resp){
|
request.get(url + '/v1/user/' + address, function(err, resp){
|
||||||
if (err) return fn(new Error("Unable to access vault sever"));
|
if (err) {
|
||||||
else if (resp.body && resp.body.username) return fn(null, resp.body.username);
|
fn(new Error('Unable to access vault sever'));
|
||||||
else if (resp.body && resp.body.exists === false) return fn (new Error("No ripple name for this address"));
|
} else if (resp.body && resp.body.username) {
|
||||||
else return fn(new Error("Unable to determine if ripple name exists"));
|
fn(null, resp.body.username);
|
||||||
|
} else if (resp.body && resp.body.exists === false) {
|
||||||
|
fn (new Error('No ripple name for this address'));
|
||||||
|
} else {
|
||||||
|
fn(new Error('Unable to determine if ripple name exists'));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
/*
|
|
||||||
* Retrive a blob with url, id and key
|
* Retrive a blob with url, id and key
|
||||||
*/
|
*/
|
||||||
module.exports.get = function (url, id, crypt, fn) {
|
|
||||||
|
exports.get = function(url, id, crypt, fn) {
|
||||||
var blob = new BlobObj(url, id, crypt);
|
var blob = new BlobObj(url, id, crypt);
|
||||||
blob.init(fn);
|
blob.init(fn);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
/*
|
|
||||||
* Verify email address
|
* Verify email address
|
||||||
*/
|
*/
|
||||||
module.exports.verify = function (url, username, token, fn) {
|
|
||||||
|
exports.verify = function(url, username, token, fn) {
|
||||||
url += '/v1/user/' + username + '/verify/' + token;
|
url += '/v1/user/' + username + '/verify/' + token;
|
||||||
request.get(url, function(err, resp){
|
request.get(url, function(err, resp){
|
||||||
if (err) return fn(err);
|
if (err) {
|
||||||
else if (resp.body && resp.body.result === 'success') return fn(null, data);
|
fn(err);
|
||||||
else return fn(new Error("Failed to verify the account"));
|
} else if (resp.body && resp.body.result === 'success') {
|
||||||
|
fn(null, data);
|
||||||
|
} else {
|
||||||
|
fn(new Error('Failed to verify the account'));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a blob object
|
* Create a blob object
|
||||||
@@ -547,18 +563,18 @@ module.exports.verify = function (url, username, token, fn) {
|
|||||||
* @param {object} options.oldUserBlob
|
* @param {object} options.oldUserBlob
|
||||||
* @param {function} fn
|
* @param {function} fn
|
||||||
*/
|
*/
|
||||||
module.exports.create = function (options, fn)
|
|
||||||
{
|
|
||||||
|
|
||||||
var blob = new BlobObj(options.url, options.id, options.crypt);
|
exports.create = function(options, fn) {
|
||||||
|
var blob = new BlobObj(options.url, options.id, options.crypt);
|
||||||
|
|
||||||
blob.revision = 0;
|
blob.revision = 0;
|
||||||
blob.data = {
|
|
||||||
auth_secret : crypt.createSecret(8),
|
blob.data = {
|
||||||
account_id : crypt.getAddress(options.masterkey),
|
auth_secret: crypt.createSecret(8),
|
||||||
email : options.email,
|
account_id: crypt.getAddress(options.masterkey),
|
||||||
contacts : [],
|
email: options.email,
|
||||||
created : (new Date()).toJSON()
|
contacts: [],
|
||||||
|
created: (new Date()).toJSON()
|
||||||
};
|
};
|
||||||
|
|
||||||
blob.encrypted_secret = blob.encryptSecret(options.unlock, options.masterkey);
|
blob.encrypted_secret = blob.encryptSecret(options.unlock, options.masterkey);
|
||||||
@@ -570,18 +586,18 @@ module.exports.create = function (options, fn)
|
|||||||
|
|
||||||
//post to the blob vault to create
|
//post to the blob vault to create
|
||||||
var config = {
|
var config = {
|
||||||
method : "POST",
|
method: 'POST',
|
||||||
url : options.url + '/v1/user',
|
url: options.url + '/v1/user',
|
||||||
data : {
|
data: {
|
||||||
blob_id : options.id,
|
blob_id: options.id,
|
||||||
username : options.username,
|
username: options.username,
|
||||||
address : blob.data.account_id,
|
address: blob.data.account_id,
|
||||||
auth_secret : blob.data.auth_secret,
|
auth_secret: blob.data.auth_secret,
|
||||||
data : blob.encrypt(),
|
data: blob.encrypt(),
|
||||||
email : options.email,
|
email: options.email,
|
||||||
hostlink : options.activateLink,
|
hostlink: options.activateLink,
|
||||||
encrypted_blobdecrypt_key : blob.encryptBlobCrypt(options.masterkey, options.crypt),
|
encrypted_blobdecrypt_key: blob.encryptBlobCrypt(options.masterkey, options.crypt),
|
||||||
encrypted_secret : blob.encrypted_secret
|
encrypted_secret: blob.encrypted_secret
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -590,8 +606,12 @@ module.exports.create = function (options, fn)
|
|||||||
request.post(signed)
|
request.post(signed)
|
||||||
.send(signed.data)
|
.send(signed.data)
|
||||||
.end(function(err, resp) {
|
.end(function(err, resp) {
|
||||||
if (err) return fn(err);
|
if (err) {
|
||||||
else if (resp.body && resp.body.result === 'success') return fn(null, blob,resp.body);
|
fn(err);
|
||||||
else return fn(new Error("Could not create blob"));
|
} else if (resp.body && resp.body.result === 'success') {
|
||||||
});
|
fn(null, blob,resp.body);
|
||||||
}
|
} else {
|
||||||
|
fn(new Error('Could not create blob'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,34 +1,36 @@
|
|||||||
var sjcl = require('./utils').sjcl,
|
var parser = require('url');
|
||||||
base = require('./base').Base,
|
var querystring = require('querystring');
|
||||||
UInt160 = require('./uint160').UInt160,
|
var extend = require('extend');
|
||||||
message = require('./message'),
|
var request = require('superagent');
|
||||||
request = require('superagent'),
|
var sjcl = require('./utils').sjcl;
|
||||||
extend = require("extend"),
|
var base = require('./base').Base;
|
||||||
parser = require("url");
|
var UInt160 = require('./uint160').UInt160;
|
||||||
|
var message = require('./message');
|
||||||
|
|
||||||
var cryptConfig = {
|
var cryptConfig = {
|
||||||
cipher : "aes",
|
cipher: 'aes',
|
||||||
mode : "ccm",
|
mode: 'ccm',
|
||||||
ts : 64, // tag length
|
ts: 64, // tag length
|
||||||
ks : 256, // key size
|
ks: 256, // key size
|
||||||
iter : 1000 // iterations (key derivation)
|
iter: 1000 // iterations (key derivation)
|
||||||
};
|
};
|
||||||
|
|
||||||
var Crypt = {};
|
var Crypt = { };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Full domain hash based on SHA512
|
* Full domain hash based on SHA512
|
||||||
*/
|
*/
|
||||||
function fdh(data, bytelen)
|
|
||||||
{
|
function fdh(data, bytelen) {
|
||||||
var bitlen = bytelen << 3;
|
var bitlen = bytelen << 3;
|
||||||
|
|
||||||
if (typeof data === "string") {
|
if (typeof data === 'string') {
|
||||||
data = sjcl.codec.utf8String.toBits(data);
|
data = sjcl.codec.utf8String.toBits(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add hashing rounds until we exceed desired length in bits
|
// Add hashing rounds until we exceed desired length in bits
|
||||||
var counter = 0, output = [];
|
var counter = 0, output = [];
|
||||||
|
|
||||||
while (sjcl.bitArray.bitLength(output) < bitlen) {
|
while (sjcl.bitArray.bitLength(output) < bitlen) {
|
||||||
var hash = sjcl.hash.sha512.hash(sjcl.bitArray.concat([counter], data));
|
var hash = sjcl.hash.sha512.hash(sjcl.bitArray.concat([counter], data));
|
||||||
output = sjcl.bitArray.concat(output, hash);
|
output = sjcl.bitArray.concat(output, hash);
|
||||||
@@ -39,132 +41,137 @@ function fdh(data, bytelen)
|
|||||||
output = sjcl.bitArray.clamp(output, bitlen);
|
output = sjcl.bitArray.clamp(output, bitlen);
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a function to derive different hashes from the same key.
|
* This is a function to derive different hashes from the same key.
|
||||||
* Each hash is derived as HMAC-SHA512HALF(key, token).
|
* Each hash is derived as HMAC-SHA512HALF(key, token).
|
||||||
|
*
|
||||||
* @param {string} key
|
* @param {string} key
|
||||||
* @param {string} hash
|
* @param {string} hash
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function keyHash(key, token) {
|
function keyHash(key, token) {
|
||||||
var hmac = new sjcl.misc.hmac(key, sjcl.hash.sha512);
|
var hmac = new sjcl.misc.hmac(key, sjcl.hash.sha512);
|
||||||
return sjcl.codec.hex.fromBits(sjcl.bitArray.bitSlice(hmac.encrypt(token), 0, 256));
|
return sjcl.codec.hex.fromBits(sjcl.bitArray.bitSlice(hmac.encrypt(token), 0, 256));
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/****** exposed functions ******/
|
/****** exposed functions ******/
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* KEY DERIVATION FUNCTION
|
* KEY DERIVATION FUNCTION
|
||||||
*
|
*
|
||||||
* This service takes care of the key derivation, i.e. converting low-entropy
|
* This service takes care of the key derivation, i.e. converting low-entropy
|
||||||
* secret into higher entropy secret via either computationally expensive
|
* secret into higher entropy secret via either computationally expensive
|
||||||
* processes or peer-assisted key derivation (PAKDF).
|
* processes or peer-assisted key derivation (PAKDF).
|
||||||
|
*
|
||||||
* @param {object} opts
|
* @param {object} opts
|
||||||
* @param {string} purpose - Key type/purpose
|
* @param {string} purpose - Key type/purpose
|
||||||
* @param {string} username
|
* @param {string} username
|
||||||
* @param {string} secret - Also known as passphrase/password
|
* @param {string} secret - Also known as passphrase/password
|
||||||
* @param {function} fn
|
* @param {function} fn
|
||||||
*/
|
*/
|
||||||
Crypt.derive = function (opts, purpose, username, secret, fn) {
|
|
||||||
|
|
||||||
|
Crypt.derive = function(opts, purpose, username, secret, fn) {
|
||||||
var tokens;
|
var tokens;
|
||||||
if (purpose=='login') tokens = ['id', 'crypt'];
|
|
||||||
else tokens = ['unlock'];
|
|
||||||
|
|
||||||
var iExponent = new sjcl.bn(String(opts.exponent)),
|
if (purpose === 'login') {
|
||||||
iModulus = new sjcl.bn(String(opts.modulus)),
|
tokens = ['id', 'crypt'];
|
||||||
iAlpha = new sjcl.bn(String(opts.alpha));
|
} else {
|
||||||
|
tokens = ['unlock'];
|
||||||
|
}
|
||||||
|
|
||||||
var publicInfo = "PAKDF_1_0_0:"+opts.host.length+":"+opts.host+
|
var iExponent = new sjcl.bn(String(opts.exponent));
|
||||||
":"+username.length+":"+username+
|
var iModulus = new sjcl.bn(String(opts.modulus));
|
||||||
":"+purpose.length+":"+purpose+
|
var iAlpha = new sjcl.bn(String(opts.alpha));
|
||||||
":",
|
|
||||||
publicSize = Math.ceil(Math.min((7+iModulus.bitLength()) >>> 3, 256)/8),
|
var publicInfo = [ 'PAKDF_1_0_0', opts.host.length, opts.host, username.length, username, purpose.length, purpose ].join(':') + ':';
|
||||||
publicHash = fdh(publicInfo, publicSize),
|
var publicSize = Math.ceil(Math.min((7 + iModulus.bitLength()) >>> 3, 256) / 8);
|
||||||
publicHex = sjcl.codec.hex.fromBits(publicHash),
|
var publicHash = fdh(publicInfo, publicSize);
|
||||||
iPublic = new sjcl.bn(String(publicHex)).setBitM(0),
|
var publicHex = sjcl.codec.hex.fromBits(publicHash);
|
||||||
secretInfo = publicInfo+":"+secret.length+":"+secret+":",
|
var iPublic = new sjcl.bn(String(publicHex)).setBitM(0);
|
||||||
secretSize = (7+iModulus.bitLength()) >>> 3,
|
var secretInfo = [ publicInfo, secret.length, secret ].join(':') + ':';
|
||||||
secretHash = fdh(secretInfo, secretSize),
|
var secretSize = (7 + iModulus.bitLength()) >>> 3;
|
||||||
secretHex = sjcl.codec.hex.fromBits(secretHash),
|
var secretHash = fdh(secretInfo, secretSize);
|
||||||
iSecret = new sjcl.bn(String(secretHex)).mod(iModulus);
|
var secretHex = sjcl.codec.hex.fromBits(secretHash);
|
||||||
|
var iSecret = new sjcl.bn(String(secretHex)).mod(iModulus);
|
||||||
|
|
||||||
if (iSecret.jacobi(iModulus) !== 1) {
|
if (iSecret.jacobi(iModulus) !== 1) {
|
||||||
iSecret = iSecret.mul(iAlpha).mod(iModulus);
|
iSecret = iSecret.mul(iAlpha).mod(iModulus);
|
||||||
}
|
}
|
||||||
|
|
||||||
var iRandom;
|
var iRandom;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
iRandom = sjcl.bn.random(iModulus, 0);
|
iRandom = sjcl.bn.random(iModulus, 0);
|
||||||
if (iRandom.jacobi(iModulus) === 1)
|
if (iRandom.jacobi(iModulus) === 1) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var iBlind = iRandom.powermodMontgomery(iPublic.mul(iExponent), iModulus),
|
var iBlind = iRandom.powermodMontgomery(iPublic.mul(iExponent), iModulus);
|
||||||
iSignreq = iSecret.mulmod(iBlind, iModulus),
|
var iSignreq = iSecret.mulmod(iBlind, iModulus);
|
||||||
signreq = sjcl.codec.hex.fromBits(iSignreq.toBits());
|
var signreq = sjcl.codec.hex.fromBits(iSignreq.toBits());
|
||||||
|
|
||||||
request.post(opts.url)
|
request.post(opts.url)
|
||||||
.send({
|
.send({ info: publicInfo, signreq: signreq })
|
||||||
info : publicInfo,
|
.end(function(err, resp) {
|
||||||
signreq : signreq
|
if (err || !resp) {
|
||||||
}).end(function(err, resp) {
|
return fn(new Error('Could not query PAKDF server ' + opts.host));
|
||||||
|
}
|
||||||
|
|
||||||
if (err || !resp) return fn(new Error("Could not query PAKDF server "+opts.host));
|
var data = resp.body || resp.text ? JSON.parse(resp.text) : {};
|
||||||
|
|
||||||
var data = resp.body || resp.text ? JSON.parse(resp.text) : {};
|
if (data.result !== 'success') {
|
||||||
|
return fn(new Error('Could not query PAKDF server '+opts.host));
|
||||||
|
}
|
||||||
|
|
||||||
if (!data.result=='success') return fn(new Error("Could not query PAKDF server "+opts.host));
|
var iSignres = new sjcl.bn(String(data.signres));
|
||||||
|
var iRandomInv = iRandom.inverseMod(iModulus);
|
||||||
|
var iSigned = iSignres.mulmod(iRandomInv, iModulus);
|
||||||
|
var key = iSigned.toBits();
|
||||||
|
var result = { };
|
||||||
|
|
||||||
var iSignres = new sjcl.bn(String(data.signres));
|
tokens.forEach(function(token) {
|
||||||
iRandomInv = iRandom.inverseMod(iModulus),
|
result[token] = keyHash(key, token);
|
||||||
iSigned = iSignres.mulmod(iRandomInv, iModulus),
|
});
|
||||||
key = iSigned.toBits(),
|
|
||||||
result = {};
|
|
||||||
|
|
||||||
tokens.forEach(function (token) {
|
fn(null, result);
|
||||||
result[token] = keyHash(key, token);
|
|
||||||
});
|
});
|
||||||
|
};
|
||||||
fn (null, result);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Imported from ripple-client
|
* Imported from ripple-client
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
Crypt.RippleAddress = (function () {
|
|
||||||
|
Crypt.RippleAddress = (function() {
|
||||||
function append_int(a, i) {
|
function append_int(a, i) {
|
||||||
return [].concat(a, i >> 24, (i >> 16) & 0xff, (i >> 8) & 0xff, i & 0xff)
|
return [].concat(a, i >> 24, (i >> 16) & 0xff, (i >> 8) & 0xff, i & 0xff)
|
||||||
}
|
};
|
||||||
|
|
||||||
function firstHalfOfSHA512(bytes) {
|
function firstHalfOfSHA512(bytes) {
|
||||||
return sjcl.bitArray.bitSlice(
|
return sjcl.bitArray.bitSlice(
|
||||||
sjcl.hash.sha512.hash(sjcl.codec.bytes.toBits(bytes)),
|
sjcl.hash.sha512.hash(sjcl.codec.bytes.toBits(bytes)),
|
||||||
0, 256
|
0, 256
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
function SHA256_RIPEMD160(bits) {
|
function SHA256_RIPEMD160(bits) {
|
||||||
return sjcl.hash.ripemd160.hash(sjcl.hash.sha256.hash(bits));
|
return sjcl.hash.ripemd160.hash(sjcl.hash.sha256.hash(bits));
|
||||||
}
|
};
|
||||||
|
|
||||||
return function (seed) {
|
return function(seed) {
|
||||||
this.seed = base.decode_check(33, seed);
|
this.seed = base.decode_check(33, seed);
|
||||||
|
|
||||||
if (!this.seed) {
|
if (!this.seed) {
|
||||||
throw "Invalid seed."
|
throw new Error('Invalid seed.');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.getAddress = function (seq) {
|
this.getAddress = function(seq) {
|
||||||
seq = seq || 0;
|
seq = seq || 0;
|
||||||
|
|
||||||
var private_gen, public_gen, i = 0;
|
var private_gen, public_gen, i = 0;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
private_gen = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(this.seed, i)));
|
private_gen = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(this.seed, i)));
|
||||||
i++;
|
i++;
|
||||||
@@ -174,6 +181,7 @@ Crypt.RippleAddress = (function () {
|
|||||||
|
|
||||||
var sec;
|
var sec;
|
||||||
i = 0;
|
i = 0;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
sec = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(append_int(public_gen.toBytesCompressed(), seq), i)));
|
sec = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(append_int(public_gen.toBytesCompressed(), seq), i)));
|
||||||
i++;
|
i++;
|
||||||
@@ -188,11 +196,12 @@ Crypt.RippleAddress = (function () {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt data
|
* Encrypt data
|
||||||
* @params {string} key
|
*
|
||||||
* @params {string} data
|
* @param {string} key
|
||||||
|
* @param {string} data
|
||||||
*/
|
*/
|
||||||
Crypt.encrypt = function(key, data)
|
|
||||||
{
|
Crypt.encrypt = function(key, data) {
|
||||||
key = sjcl.codec.hex.toBits(key);
|
key = sjcl.codec.hex.toBits(key);
|
||||||
|
|
||||||
var opts = extend(true, {}, cryptConfig);
|
var opts = extend(true, {}, cryptConfig);
|
||||||
@@ -206,23 +215,23 @@ Crypt.encrypt = function(key, data)
|
|||||||
encryptedBits = sjcl.bitArray.concat(encryptedBits, ciphertext);
|
encryptedBits = sjcl.bitArray.concat(encryptedBits, ciphertext);
|
||||||
|
|
||||||
return sjcl.codec.base64.fromBits(encryptedBits);
|
return sjcl.codec.base64.fromBits(encryptedBits);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt data
|
* Decrypt data
|
||||||
* @params {string} key
|
*
|
||||||
* @params {string} data
|
* @param {string} key
|
||||||
|
* @param {string} data
|
||||||
*/
|
*/
|
||||||
Crypt.decrypt = function(key, data)
|
|
||||||
{
|
Crypt.decrypt = function(key, data) {
|
||||||
key = sjcl.codec.hex.toBits(key);
|
key = sjcl.codec.hex.toBits(key);
|
||||||
var encryptedBits = sjcl.codec.base64.toBits(data);
|
var encryptedBits = sjcl.codec.base64.toBits(data);
|
||||||
|
|
||||||
var version = sjcl.bitArray.extract(encryptedBits, 0, 8);
|
var version = sjcl.bitArray.extract(encryptedBits, 0, 8);
|
||||||
|
|
||||||
if (version !== 0) {
|
if (version !== 0) {
|
||||||
throw new Error("Unsupported encryption version: "+version);
|
throw new Error('Unsupported encryption version: '+version);
|
||||||
}
|
}
|
||||||
|
|
||||||
var encrypted = extend(true, {}, cryptConfig, {
|
var encrypted = extend(true, {}, cryptConfig, {
|
||||||
@@ -231,109 +240,121 @@ Crypt.decrypt = function(key, data)
|
|||||||
});
|
});
|
||||||
|
|
||||||
return sjcl.decrypt(key, JSON.stringify(encrypted));
|
return sjcl.decrypt(key, JSON.stringify(encrypted));
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate a ripple address
|
* Validate a ripple address
|
||||||
|
*
|
||||||
* @param {string} address
|
* @param {string} address
|
||||||
*/
|
*/
|
||||||
Crypt.isValidAddress = function (address) {
|
|
||||||
return UInt160.is_valid(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
Crypt.isValidAddress = function(address) {
|
||||||
|
return UInt160.is_valid(address);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate a ripple address
|
* Validate a ripple address
|
||||||
|
*
|
||||||
* @param {integer} nWords - number of words
|
* @param {integer} nWords - number of words
|
||||||
*/
|
*/
|
||||||
Crypt.createSecret = function (nWords) {
|
|
||||||
return sjcl.codec.hex.fromBits(sjcl.random.randomWords(nWords));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
Crypt.createSecret = function(nWords) {
|
||||||
|
return sjcl.codec.hex.fromBits(sjcl.random.randomWords(nWords));
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new master key
|
* Create a new master key
|
||||||
*/
|
*/
|
||||||
Crypt.createMaster = function () {
|
|
||||||
return base.encode_check(33, sjcl.codec.bytes.fromBits(sjcl.random.randomWords(4)));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
Crypt.createMaster = function() {
|
||||||
|
return base.encode_check(33, sjcl.codec.bytes.fromBits(sjcl.random.randomWords(4)));
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a ripple address from a master key
|
* Create a ripple address from a master key
|
||||||
|
*
|
||||||
* @param {string} masterkey
|
* @param {string} masterkey
|
||||||
*/
|
*/
|
||||||
Crypt.getAddress = function (masterkey) {
|
|
||||||
return new Crypt.RippleAddress(masterkey).getAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
Crypt.getAddress = function(masterkey) {
|
||||||
|
return new Crypt.RippleAddress(masterkey).getAddress();
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hash data
|
* Hash data
|
||||||
|
*
|
||||||
* @param {string} data
|
* @param {string} data
|
||||||
*/
|
*/
|
||||||
Crypt.hashSha512 = function (data) {
|
|
||||||
return sjcl.codec.hex.fromBits(sjcl.hash.sha512.hash(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
Crypt.hashSha512 = function(data) {
|
||||||
|
return sjcl.codec.hex.fromBits(sjcl.hash.sha512.hash(data));
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sign a data string with a secret key
|
* Sign a data string with a secret key
|
||||||
|
*
|
||||||
* @param {string} secret
|
* @param {string} secret
|
||||||
* @param {string} data
|
* @param {string} data
|
||||||
*/
|
*/
|
||||||
Crypt.signString = function (secret, data) {
|
|
||||||
|
Crypt.signString = function(secret, data) {
|
||||||
var hmac = new sjcl.misc.hmac(sjcl.codec.hex.toBits(secret), sjcl.hash.sha512);
|
var hmac = new sjcl.misc.hmac(sjcl.codec.hex.toBits(secret), sjcl.hash.sha512);
|
||||||
return sjcl.codec.hex.fromBits(hmac.mac(data));
|
return sjcl.codec.hex.fromBits(hmac.mac(data));
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an an accout recovery key
|
* Create an an accout recovery key
|
||||||
|
*
|
||||||
* @param {string} secret
|
* @param {string} secret
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Crypt.deriveRecoveryEncryptionKeyFromSecret = function(secret) {
|
Crypt.deriveRecoveryEncryptionKeyFromSecret = function(secret) {
|
||||||
var seed = ripple.Seed.from_json(secret).to_bits();
|
var seed = ripple.Seed.from_json(secret).to_bits();
|
||||||
var hmac = new sjcl.misc.hmac(seed, sjcl.hash.sha512);
|
var hmac = new sjcl.misc.hmac(seed, sjcl.hash.sha512);
|
||||||
var key = hmac.mac("ripple/hmac/recovery_encryption_key/v1");
|
var key = hmac.mac('ripple/hmac/recovery_encryption_key/v1');
|
||||||
key = sjcl.bitArray.bitSlice(key, 0, 256);
|
key = sjcl.bitArray.bitSlice(key, 0, 256);
|
||||||
return sjcl.codec.hex.fromBits(key);
|
return sjcl.codec.hex.fromBits(key);
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert base64 encoded data into base64url encoded data.
|
* Convert base64 encoded data into base64url encoded data.
|
||||||
|
*
|
||||||
* @param {String} base64 Data
|
* @param {String} base64 Data
|
||||||
*/
|
*/
|
||||||
Crypt.base64ToBase64Url = function (encodedData) {
|
|
||||||
|
Crypt.base64ToBase64Url = function(encodedData) {
|
||||||
return encodedData.replace(/\+/g, '-').replace(/\//g, '_').replace(/[=]+$/, '');
|
return encodedData.replace(/\+/g, '-').replace(/\//g, '_').replace(/[=]+$/, '');
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert base64url encoded data into base64 encoded data.
|
* Convert base64url encoded data into base64 encoded data.
|
||||||
|
*
|
||||||
* @param {String} base64 Data
|
* @param {String} base64 Data
|
||||||
*/
|
*/
|
||||||
Crypt.base64UrlToBase64 = function (encodedData) {
|
|
||||||
|
Crypt.base64UrlToBase64 = function(encodedData) {
|
||||||
encodedData = encodedData.replace(/-/g, '+').replace(/_/g, '/');
|
encodedData = encodedData.replace(/-/g, '+').replace(/_/g, '/');
|
||||||
|
|
||||||
while (encodedData.length % 4) {
|
while (encodedData.length % 4) {
|
||||||
encodedData += '=';
|
encodedData += '=';
|
||||||
}
|
}
|
||||||
|
|
||||||
return encodedData;
|
return encodedData;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a string from request parameters that
|
* Create a string from request parameters that
|
||||||
* will be used to sign a request
|
* will be used to sign a request
|
||||||
|
*
|
||||||
* @param {Object} config - request params
|
* @param {Object} config - request params
|
||||||
* @param {Object} parsed - parsed url
|
* @param {Object} parsed - parsed url
|
||||||
* @param {Object} date
|
* @param {Object} date
|
||||||
* @param {Object} mechanism - type of signing
|
* @param {Object} mechanism - type of signing
|
||||||
*/
|
*/
|
||||||
Crypt.getStringToSign = function (config, parsed, date, mechanism) {
|
|
||||||
|
Crypt.getStringToSign = function(config, parsed, date, mechanism) {
|
||||||
// XXX This method doesn't handle signing GET requests correctly. The data
|
// XXX This method doesn't handle signing GET requests correctly. The data
|
||||||
// field will be merged into the search string, not the request body.
|
// field will be merged into the search string, not the request body.
|
||||||
|
|
||||||
// Sort the properties of the JSON object into canonical form
|
// Sort the properties of the JSON object into canonical form
|
||||||
var canonicalData = JSON.stringify(copyObjectWithSortedKeys(config.data));
|
var canonicalData = JSON.stringify(copyObjectWithSortedKeys(config.data));
|
||||||
|
|
||||||
@@ -360,16 +381,17 @@ Crypt.getStringToSign = function (config, parsed, date, mechanism) {
|
|||||||
date,
|
date,
|
||||||
Crypt.hashSha512(canonicalRequest).toLowerCase()
|
Crypt.hashSha512(canonicalRequest).toLowerCase()
|
||||||
].join('\n');
|
].join('\n');
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HMAC signed request
|
* HMAC signed request
|
||||||
|
*
|
||||||
* @param {Object} config
|
* @param {Object} config
|
||||||
* @param {Object} auth_secret
|
* @param {Object} auth_secret
|
||||||
* @param {Object} blob_id
|
* @param {Object} blob_id
|
||||||
*/
|
*/
|
||||||
Crypt.signRequestHmac = function (config, auth_secret, blob_id) {
|
|
||||||
|
Crypt.signRequestHmac = function(config, auth_secret, blob_id) {
|
||||||
config = extend(true, {}, config);
|
config = extend(true, {}, config);
|
||||||
|
|
||||||
// Parse URL
|
// Parse URL
|
||||||
@@ -379,23 +401,28 @@ Crypt.signRequestHmac = function (config, auth_secret, blob_id) {
|
|||||||
var stringToSign = Crypt.getStringToSign(config, parsed, date, signatureType);
|
var stringToSign = Crypt.getStringToSign(config, parsed, date, signatureType);
|
||||||
var signature = Crypt.signString(auth_secret, stringToSign);
|
var signature = Crypt.signString(auth_secret, stringToSign);
|
||||||
|
|
||||||
config.url += (parsed.search ? "&" : "?") +
|
var query = querystring.stringify({
|
||||||
'signature='+Crypt.base64ToBase64Url(signature)+
|
signature: Crypt.base64ToBase64Url(signature),
|
||||||
'&signature_date='+date+
|
signature_date: date,
|
||||||
'&signature_blob_id='+blob_id+
|
signature_blob_id: blob_id,
|
||||||
'&signature_type='+signatureType
|
signature_type: signatureType
|
||||||
|
});
|
||||||
|
|
||||||
|
config.url += (parsed.search ? '&' : '?') + query;
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asymmetric signed request
|
* Asymmetric signed request
|
||||||
|
*
|
||||||
* @param {Object} config
|
* @param {Object} config
|
||||||
* @param {Object} secretKey
|
* @param {Object} secretKey
|
||||||
* @param {Object} account
|
* @param {Object} account
|
||||||
* @param {Object} blob_id
|
* @param {Object} blob_id
|
||||||
*/
|
*/
|
||||||
Crypt.signRequestAsymmetric = function (config, secretKey, account, blob_id) {
|
|
||||||
|
Crypt.signRequestAsymmetric = function(config, secretKey, account, blob_id) {
|
||||||
config = extend(true, {}, config);
|
config = extend(true, {}, config);
|
||||||
|
|
||||||
// Parse URL
|
// Parse URL
|
||||||
@@ -405,76 +432,81 @@ Crypt.signRequestAsymmetric = function (config, secretKey, account, blob_id) {
|
|||||||
var stringToSign = Crypt.getStringToSign(config, parsed, date, signatureType);
|
var stringToSign = Crypt.getStringToSign(config, parsed, date, signatureType);
|
||||||
var signature = message.signMessage(stringToSign, secretKey);
|
var signature = message.signMessage(stringToSign, secretKey);
|
||||||
|
|
||||||
config.url += (parsed.search ? "&" : "?") +
|
var query = querystring.stringify({
|
||||||
'signature='+Crypt.base64ToBase64Url(signature)+
|
signature: Crypt.base64ToBase64Url(signature),
|
||||||
'&signature_date='+date+
|
signature_date: date,
|
||||||
'&signature_blob_id='+blob_id+
|
signature_blob_id: blob_id,
|
||||||
'&signature_account='+account+
|
signature_account: account,
|
||||||
'&signature_type='+signatureType;
|
signature_type: signatureType
|
||||||
|
})
|
||||||
|
|
||||||
|
config.url += (parsed.search ? '&' : '?') + query;
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
//prepare for signing
|
//prepare for signing
|
||||||
function copyObjectWithSortedKeys(object) {
|
function copyObjectWithSortedKeys(object) {
|
||||||
if (isPlainObject(object)) {
|
if (isPlainObject(object)) {
|
||||||
var newObj = {};
|
var newObj = {};
|
||||||
var keysSorted = Object.keys(object).sort();
|
var keysSorted = Object.keys(object).sort();
|
||||||
var key;
|
var key;
|
||||||
|
|
||||||
for (var i in keysSorted) {
|
for (var i in keysSorted) {
|
||||||
key = keysSorted[i];
|
key = keysSorted[i];
|
||||||
if (Object.prototype.hasOwnProperty.call(object, key)) {
|
if (Object.prototype.hasOwnProperty.call(object, key)) {
|
||||||
newObj[key] = copyObjectWithSortedKeys(object[key]);
|
newObj[key] = copyObjectWithSortedKeys(object[key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return newObj;
|
return newObj;
|
||||||
} else if (Array.isArray(object)) {
|
} else if (Array.isArray(object)) {
|
||||||
return object.map(copyObjectWithSortedKeys);
|
return object.map(copyObjectWithSortedKeys);
|
||||||
} else {
|
} else {
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
//from npm extend
|
//from npm extend
|
||||||
function isPlainObject(obj) {
|
function isPlainObject(obj) {
|
||||||
var hasOwn = Object.prototype.hasOwnProperty;
|
var hasOwn = Object.prototype.hasOwnProperty;
|
||||||
var toString = Object.prototype.toString;
|
var toString = Object.prototype.toString;
|
||||||
|
|
||||||
if (!obj || toString.call(obj) !== '[object Object]' || obj.nodeType || obj.setInterval)
|
if (!obj || toString.call(obj) !== '[object Object]' || obj.nodeType || obj.setInterval) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var has_own_constructor = hasOwn.call(obj, 'constructor');
|
var has_own_constructor = hasOwn.call(obj, 'constructor');
|
||||||
var has_is_property_of_method = hasOwn.call(obj.constructor.prototype, 'isPrototypeOf');
|
var has_is_property_of_method = hasOwn.call(obj.constructor.prototype, 'isPrototypeOf');
|
||||||
|
|
||||||
// Not own constructor property must be Object
|
// Not own constructor property must be Object
|
||||||
if (obj.constructor && !has_own_constructor && !has_is_property_of_method)
|
if (obj.constructor && !has_own_constructor && !has_is_property_of_method) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Own properties are enumerated firstly, so to speed up,
|
// Own properties are enumerated firstly, so to speed up,
|
||||||
// if last one is own, then all properties are own.
|
// if last one is own, then all properties are own.
|
||||||
var key;
|
var key;
|
||||||
|
|
||||||
for ( key in obj ) {}
|
for ( key in obj ) {}
|
||||||
|
|
||||||
return key === undefined || hasOwn.call( obj, key );
|
return key === void(0) || hasOwn.call( obj, key );
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var dateAsIso8601 = (function() {
|
||||||
var dateAsIso8601 = (function () {
|
|
||||||
function pad(n) {
|
function pad(n) {
|
||||||
return (n < 0 || n > 9 ? "" : "0") + n;
|
return (n < 0 || n > 9 ? '' : '0') + n;
|
||||||
}
|
};
|
||||||
|
|
||||||
return function dateAsIso8601() {
|
return function dateAsIso8601() {
|
||||||
var date = new Date();
|
var date = new Date();
|
||||||
return date.getUTCFullYear() + "-"
|
return date.getUTCFullYear() + '-'
|
||||||
+ pad(date.getUTCMonth() + 1) + "-"
|
+ pad(date.getUTCMonth() + 1) + '-'
|
||||||
+ pad(date.getUTCDate()) + "T"
|
+ pad(date.getUTCDate()) + 'T'
|
||||||
+ pad(date.getUTCHours()) + ":"
|
+ pad(date.getUTCHours()) + ':'
|
||||||
+ pad(date.getUTCMinutes()) + ":"
|
+ pad(date.getUTCMinutes()) + ':'
|
||||||
+ pad(date.getUTCSeconds()) + ".000Z";
|
+ pad(date.getUTCSeconds()) + '.000Z';
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
exports.Crypt = Crypt;
|
||||||
module.exports = Crypt;
|
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
var request = require('superagent');
|
var request = require('superagent');
|
||||||
|
|
||||||
|
|
||||||
function RippleTxt() {
|
function RippleTxt() {
|
||||||
this.txts = {};
|
this.txts = { };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the ripple.txt file for the given domain
|
* Gets the ripple.txt file for the given domain
|
||||||
|
*
|
||||||
* @param {string} domain - Domain to retrieve file from
|
* @param {string} domain - Domain to retrieve file from
|
||||||
* @param {function} fn - Callback function
|
* @param {function} fn - Callback function
|
||||||
*/
|
*/
|
||||||
|
|
||||||
RippleTxt.prototype.get = function (domain, fn) {
|
RippleTxt.prototype.get = function (domain, fn) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if (self.txts[domain]) return fn(null, self.txts[domain]);
|
if (self.txts[domain]) {
|
||||||
|
return fn(null, self.txts[domain]);
|
||||||
|
}
|
||||||
|
|
||||||
var urls = [
|
var urls = [
|
||||||
'https://ripple.'+domain+'/ripple.txt',
|
'https://ripple.'+domain+'/ripple.txt',
|
||||||
@@ -23,40 +25,47 @@ RippleTxt.prototype.get = function (domain, fn) {
|
|||||||
'http://ripple.'+domain+'/ripple.txt',
|
'http://ripple.'+domain+'/ripple.txt',
|
||||||
'http://www.'+domain+'/ripple.txt',
|
'http://www.'+domain+'/ripple.txt',
|
||||||
'http://'+domain+'/ripple.txt'
|
'http://'+domain+'/ripple.txt'
|
||||||
].reverse();
|
];
|
||||||
|
|
||||||
next();
|
;(function nextUrl() {
|
||||||
function next () {
|
var url = urls.shift();
|
||||||
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) {
|
request.get(url, function(err, resp) {
|
||||||
if (err || !resp.text) return next();
|
if (err || !resp.text) {
|
||||||
|
return nextUrl();
|
||||||
|
}
|
||||||
|
|
||||||
var sections = self.parse(resp.text);
|
var sections = self.parse(resp.text);
|
||||||
self.txts[domain] = sections;
|
self.txts[domain] = sections;
|
||||||
|
|
||||||
fn(null, sections);
|
fn(null, sections);
|
||||||
});
|
});
|
||||||
}
|
})();
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse a ripple.txt file
|
* Parse a ripple.txt file
|
||||||
|
*
|
||||||
* @param {string} txt - Unparsed ripple.txt data
|
* @param {string} txt - Unparsed ripple.txt data
|
||||||
*/
|
*/
|
||||||
|
|
||||||
RippleTxt.prototype.parse = function (txt) {
|
RippleTxt.prototype.parse = function (txt) {
|
||||||
|
var txt = txt.replace(/\r?\n/g, '\n').split('\n')
|
||||||
|
var currentSection = '';
|
||||||
|
var sections = { };
|
||||||
|
|
||||||
txt = txt.replace('\r\n', '\n');
|
|
||||||
txt = txt.replace('\r', '\n');
|
|
||||||
txt = txt.split('\n');
|
|
||||||
|
|
||||||
var currentSection = "", sections = {};
|
|
||||||
for (var i = 0, l = txt.length; i < l; i++) {
|
for (var i = 0, l = txt.length; i < l; i++) {
|
||||||
var line = txt[i];
|
var line = txt[i];
|
||||||
|
|
||||||
if (!line.length || line[0] === '#') {
|
if (!line.length || line[0] === '#') {
|
||||||
continue;
|
continue;
|
||||||
} else if (line[0] === '[' && line[line.length-1] === ']') {
|
}
|
||||||
|
|
||||||
|
if (line[0] === '[' && line[line.length-1] === ']') {
|
||||||
currentSection = line.slice(1, line.length-1);
|
currentSection = line.slice(1, line.length-1);
|
||||||
sections[currentSection] = [];
|
sections[currentSection] = [];
|
||||||
} else {
|
} else {
|
||||||
@@ -68,6 +77,6 @@ RippleTxt.prototype.parse = function (txt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return sections;
|
return sections;
|
||||||
}
|
};
|
||||||
|
|
||||||
module.exports = RippleTxt;
|
exports.RippleTxt = RippleTxt;
|
||||||
|
|||||||
@@ -1,227 +1,269 @@
|
|||||||
var AuthInfo = require('./authinfo');
|
var async = require('async');
|
||||||
var blobClient = require('./blob');
|
var blobClient = require('./blob');
|
||||||
var crypt = require('./crypt');
|
var AuthInfo = require('./authinfo').AuthInfo;
|
||||||
|
var crypt = require('./crypt').Crypt;
|
||||||
|
|
||||||
function VaultClient(opts) {
|
function VaultClient(opts) {
|
||||||
if (!opts) opts = {};
|
if (!opts) {
|
||||||
else if (typeof opts === "string") opts = {domain:opts};
|
opts = {};
|
||||||
|
}
|
||||||
|
|
||||||
this.domain = opts.domain || 'ripple.com';
|
if (typeof opts === 'string') {
|
||||||
this.authInfo = new AuthInfo;
|
opts = { domain: opts };
|
||||||
this.infos = {};
|
}
|
||||||
|
|
||||||
|
this.domain = opts.domain || 'ripple.com';
|
||||||
|
this.authInfo = new AuthInfo();
|
||||||
|
this.infos = { };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reduce username to standardized form.
|
|
||||||
* Strips whitespace at beginning and end.
|
|
||||||
* @param {string} username - Username to normalize
|
|
||||||
*/
|
|
||||||
VaultClient.prototype.normalizeUsername = function (username) {
|
|
||||||
username = ""+username;
|
|
||||||
username = username.trim();
|
|
||||||
return username;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reduce password to standardized form.
|
|
||||||
* Strips whitespace at beginning and end.
|
|
||||||
* @param {string} password - password to normalize
|
|
||||||
*/
|
|
||||||
VaultClient.prototype.normalizePassword = function (password) {
|
|
||||||
password = ""+password;
|
|
||||||
password = password.trim();
|
|
||||||
return password;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a ripple name from a given account address, if it has one
|
* Get a ripple name from a given account address, if it has one
|
||||||
* @param {string} address - Account address to query
|
* @param {string} address - Account address to query
|
||||||
* @param {string} url - Url of blob vault
|
* @param {string} url - Url of blob vault
|
||||||
*/
|
*/
|
||||||
VaultClient.prototype.getRippleName = function(address, url, fn) {
|
|
||||||
|
|
||||||
|
VaultClient.prototype.getRippleName = function(address, url, callback) {
|
||||||
//use the url from previously retrieved authInfo, if necessary
|
//use the url from previously retrieved authInfo, if necessary
|
||||||
if (!url) return fn(new Error("Blob vault URL is required"));
|
if (!url) {
|
||||||
blobClient.getRippleName(url, address, fn);
|
callback(new Error('Blob vault URL is required'));
|
||||||
|
} else {
|
||||||
|
blobClient.getRippleName(url, address, callback);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authenticate and retrieve a decrypted blob using a ripple name and password
|
* Authenticate and retrieve a decrypted blob using a ripple name and password
|
||||||
|
*
|
||||||
* @param {string} username
|
* @param {string} username
|
||||||
* @param {string} password
|
* @param {string} password
|
||||||
* @param {function} fn - Callback function
|
* @param {function} fn - Callback function
|
||||||
*/
|
*/
|
||||||
VaultClient.prototype.login = function(username, password, fn) {
|
|
||||||
|
VaultClient.prototype.login = function(username, password, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.authInfo.get(self.domain, username, function(err, authInfo){
|
function getAuthInfo(callback) {
|
||||||
if (err) return fn(err);
|
self.authInfo.get(self.domain, username, function(err, authInfo) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
if (authInfo.version !== 3) {
|
if (authInfo.version !== 3) {
|
||||||
return fn(new Error("This wallet is incompatible with this version of the vault-client."));
|
return callback(new Error('This wallet is incompatible with this version of the vault-client.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!authInfo.pakdf) {
|
if (!authInfo.pakdf) {
|
||||||
return fn(new Error("No settings for PAKDF in auth packet."));
|
return callback(new Error('No settings for PAKDF in auth packet.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!authInfo.exists) {
|
if (!authInfo.exists) {
|
||||||
return fn(new Error("User does not exist."));
|
return callback(new Error('User does not exist.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("string" !== typeof authInfo.blobvault) {
|
if (typeof authInfo.blobvault !== 'string') {
|
||||||
return fn(new Error("No blobvault specified in the authinfo."));
|
return callback(new Error('No blobvault specified in the authinfo.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
callback(null, authInfo);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function deriveLoginKeys(authInfo, callback) {
|
||||||
//derive login keys
|
//derive login keys
|
||||||
crypt.derive(authInfo.pakdf, 'login', username.toLowerCase(), password, function(err, keys){
|
crypt.derive(authInfo.pakdf, 'login', username.toLowerCase(), password, function(err, keys) {
|
||||||
if (err) return fn(err);
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
} else {
|
||||||
|
callback(null, authInfo, keys);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
blobClient.get(authInfo.blobvault, keys.id, keys.crypt, function (err, blob) {
|
function getBlob(authInfo, keys, callback) {
|
||||||
if (err) return fn(err);
|
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, {
|
callback(null, {
|
||||||
blob : blob,
|
blob: blob,
|
||||||
username : authInfo.username,
|
username: authInfo.username,
|
||||||
verified : authInfo.emailVerified
|
verified: authInfo.emailVerified
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
};
|
||||||
};
|
|
||||||
|
|
||||||
|
var steps = [
|
||||||
|
getAuthInfo,
|
||||||
|
deriveLoginKeys,
|
||||||
|
getBlob
|
||||||
|
];
|
||||||
|
|
||||||
|
async.waterfall(steps, callback);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retreive and decrypt blob using a blob url, id and crypt derived previously.
|
* Retreive and decrypt blob using a blob url, id and crypt derived previously.
|
||||||
|
*
|
||||||
* @param {string} url - Blob vault url
|
* @param {string} url - Blob vault url
|
||||||
* @param {string} id - Blob id from previously retreived blob
|
* @param {string} id - Blob id from previously retreived blob
|
||||||
* @param {string} key - Blob decryption key
|
* @param {string} key - Blob decryption key
|
||||||
* @param {function} fn - Callback function
|
* @param {function} fn - Callback function
|
||||||
*/
|
*/
|
||||||
VaultClient.prototype.relogin = function(url, id, key, fn) {
|
|
||||||
|
|
||||||
|
VaultClient.prototype.relogin = function(url, id, key, callback) {
|
||||||
//use the url from previously retrieved authInfo, if necessary
|
//use the url from previously retrieved authInfo, if necessary
|
||||||
if (!url && this.infos[id]) url = this.infos[id].blobvault;
|
if (!url && this.infos[id]) {
|
||||||
|
url = this.infos[id].blobvault;
|
||||||
|
}
|
||||||
|
|
||||||
if (!url) return fn(new Error("Blob vault URL is required"));
|
if (!url) {
|
||||||
|
return callback(new Error('Blob vault URL is required'));
|
||||||
|
}
|
||||||
|
|
||||||
blobClient.get(url, id, key, function (err, blob) {
|
blobClient.get(url, id, key, function(err, blob) {
|
||||||
if (err) return fn(err);
|
if (err) {
|
||||||
|
callback(err);
|
||||||
fn (null, {
|
} else {
|
||||||
blob : blob,
|
callback (null, { blob: blob });
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt the secret key using a username and password
|
* Decrypt the secret key using a username and password
|
||||||
|
*
|
||||||
* @param {string} username
|
* @param {string} username
|
||||||
* @param {string} password
|
* @param {string} password
|
||||||
* @param {string} encryptSecret
|
* @param {string} encryptSecret
|
||||||
* @param {function} fn - Callback function
|
* @param {function} fn - Callback function
|
||||||
*/
|
*/
|
||||||
VaultClient.prototype.unlock = function(username, password, encryptSecret, fn) {
|
|
||||||
|
VaultClient.prototype.unlock = function(username, password, encryptSecret, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.authInfo.get(self.domain, username, function(err, authInfo){
|
function deriveUnlockKey(authInfo, callback) {
|
||||||
if (err) return fn(err);
|
|
||||||
|
|
||||||
//derive unlock key
|
//derive unlock key
|
||||||
crypt.derive(authInfo.pakdf, 'unlock', username.toLowerCase(), password, function(err, keys){
|
crypt.derive(authInfo.pakdf, 'unlock', username.toLowerCase(), password, function(err, keys) {
|
||||||
if (err) return fn(err);
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
fn(null, {
|
callback(null, {
|
||||||
keys : keys,
|
keys: keys,
|
||||||
secret : crypt.decrypt(keys.unlock, encryptSecret)
|
secret: crypt.decrypt(keys.unlock, encryptSecret)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
self.authInfo.get(self.domain, username, function(err, authInfo) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
} else {
|
||||||
|
deriveUnlockKey(authInfo, callback);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the decrypted blob and secret key in one step using
|
* Retrieve the decrypted blob and secret key in one step using
|
||||||
* the username and password
|
* the username and password
|
||||||
|
*
|
||||||
* @param {string} username
|
* @param {string} username
|
||||||
* @param {string} password
|
* @param {string} password
|
||||||
* @param {function} fn - Callback function
|
* @param {function} fn - Callback function
|
||||||
*/
|
*/
|
||||||
VaultClient.prototype.loginAndUnlock = function(username, password, fn) {
|
|
||||||
|
VaultClient.prototype.loginAndUnlock = function(username, password, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this.login(username, password, function(err, resp){
|
function deriveUnlockKey(authInfo, blob, callback) {
|
||||||
if (err) return fn(err);
|
//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)
|
callback(null, {
|
||||||
return fn(new Error("Unable to retrieve blob and secret."));
|
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)
|
this.login(username, password, function(err, resp) {
|
||||||
return fn(new Error("Unable to retrieve keys."));
|
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
|
//get authInfo via id - would have been saved from login
|
||||||
var authInfo = self.infos[resp.blob.id];
|
var authInfo = self.infos[resp.blob.id];
|
||||||
if (!authInfo) return fn(new Error("Unable to find authInfo"));
|
|
||||||
|
|
||||||
//derive unlock key
|
if (!authInfo) {
|
||||||
crypt.derive(authInfo.pakdf, 'unlock', username.toLowerCase(), password, function(err, keys){
|
return callback(new Error('Unable to find authInfo'));
|
||||||
if (err) return fn(err);
|
}
|
||||||
|
|
||||||
fn(null, {
|
deriveUnlockKey(authInfo, resp.blob, callback);
|
||||||
blob : resp.blob,
|
|
||||||
unlock : keys.unlock,
|
|
||||||
secret : crypt.decrypt(keys.unlock, resp.blob.encrypted_secret),
|
|
||||||
username : authInfo.username,
|
|
||||||
verified : authInfo.emailVerified
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check blobvault for existance of username
|
* Check blobvault for existance of username
|
||||||
|
*
|
||||||
* @param {string} username
|
* @param {string} username
|
||||||
* @param {function} fn - Callback function
|
* @param {function} fn - Callback function
|
||||||
*/
|
*/
|
||||||
VaultClient.prototype.exists = function (username, fn) {
|
|
||||||
this.authInfo.get(this.domain, username.toLowerCase(), function(err, authInfo){
|
VaultClient.prototype.exists = function(username, callback) {
|
||||||
if (err) return fn(err);
|
this.authInfo.get(this.domain, username.toLowerCase(), function(err, authInfo) {
|
||||||
return fn(null, !!authInfo.exists);
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
} else {
|
||||||
|
callback(null, !!authInfo.exists);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
/*
|
|
||||||
* Verify an email address for an existing user
|
* Verify an email address for an existing user
|
||||||
|
*
|
||||||
* @param {string} username
|
* @param {string} username
|
||||||
* @param {string} token - Verification token
|
* @param {string} token - Verification token
|
||||||
* @param {function} fn - Callback function
|
* @param {function} fn - Callback function
|
||||||
*/
|
*/
|
||||||
VaultClient.prototype.verify = function (username, token, fn) {
|
|
||||||
|
|
||||||
this.authInfo.get(this.domain, username.toLowerCase(), function (err, authInfo) {
|
VaultClient.prototype.verify = function(username, token, callback) {
|
||||||
if (err) return fn(err);
|
var self = this;
|
||||||
|
|
||||||
if ("string" !== typeof authInfo.blobvault) {
|
this.authInfo.get(this.domain, username.toLowerCase(), function(err, authInfo) {
|
||||||
return fn(new Error("No blobvault specified in the 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
|
* Register a new user and save to the blob vault
|
||||||
*
|
*
|
||||||
* @param {object} options
|
* @param {object} options
|
||||||
@@ -233,53 +275,77 @@ VaultClient.prototype.verify = function (username, token, fn) {
|
|||||||
* @param {object} options.oldUserBlob //optional
|
* @param {object} options.oldUserBlob //optional
|
||||||
* @param {function} fn
|
* @param {function} fn
|
||||||
*/
|
*/
|
||||||
VaultClient.prototype.register = function (options, fn) {
|
|
||||||
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) {
|
if (!authInfo.pakdf) {
|
||||||
return fn(new Error("No blobvault specified in the authinfo."));
|
return callback(new Error('No settings for PAKDF in auth packet.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
callback(null, authInfo);
|
||||||
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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.VaultClient = VaultClient;
|
||||||
module.exports = VaultClient;
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
var assert = require('assert'),
|
var assert = require('assert'),
|
||||||
RippleTxt = require('../src/js/ripple/rippletxt'),
|
RippleTxt = require('../src/js/ripple/rippletxt').RippleTxt,
|
||||||
AuthInfo = require('../src/js/ripple/authinfo'),
|
AuthInfo = require('../src/js/ripple/authinfo').AuthInfo,
|
||||||
VaultClient = require('../src/js/ripple/vaultclient'),
|
VaultClient = require('../src/js/ripple/vaultclient').VaultClient,
|
||||||
Blob = require('../src/js/ripple/blob').Blob,
|
Blob = require('../src/js/ripple/blob').Blob,
|
||||||
UInt256 = require('../src/js/ripple/uint256').UInt256;
|
UInt256 = require('../src/js/ripple/uint256').UInt256;
|
||||||
|
|
||||||
@@ -22,6 +22,7 @@ process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; //must be set for self signed c
|
|||||||
|
|
||||||
describe('Ripple Txt', function() {
|
describe('Ripple Txt', function() {
|
||||||
it('should get the context of a ripple.txt file from a given domain', function(done){
|
it('should get the context of a ripple.txt file from a given domain', function(done){
|
||||||
|
this.timeout(10000);
|
||||||
var rt = new RippleTxt();
|
var rt = new RippleTxt();
|
||||||
rt.get(exampleData.domain, function(err, resp){
|
rt.get(exampleData.domain, function(err, resp){
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
@@ -36,6 +37,7 @@ describe('AuthInfo', function() {
|
|||||||
var auth = new AuthInfo();
|
var auth = new AuthInfo();
|
||||||
|
|
||||||
it ('should', function(done){
|
it ('should', function(done){
|
||||||
|
this.timeout(10000);
|
||||||
auth.get(exampleData.domain, exampleData.user, function(err, resp){
|
auth.get(exampleData.domain, exampleData.user, function(err, resp){
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
assert.equal(typeof resp, 'object');
|
assert.equal(typeof resp, 'object');
|
||||||
|
|||||||
Reference in New Issue
Block a user