From 9b3d62b765c4c25beae6eb0fa57ef3a07f2581b1 Mon Sep 17 00:00:00 2001 From: wltsmrz Date: Wed, 14 Jan 2015 14:54:55 -0800 Subject: [PATCH] Remove blobvault client --- package.json | 1 - src/js/ripple/authinfo.js | 59 -- src/js/ripple/blob.js | 1584 -------------------------------- src/js/ripple/index.js | 5 - src/js/ripple/rippletxt.js | 133 --- src/js/ripple/signedrequest.js | 203 ---- src/js/ripple/vaultclient.js | 593 ------------ test/vault-test.js | 851 ----------------- 8 files changed, 3429 deletions(-) delete mode 100644 src/js/ripple/authinfo.js delete mode 100644 src/js/ripple/blob.js delete mode 100644 src/js/ripple/rippletxt.js delete mode 100644 src/js/ripple/signedrequest.js delete mode 100644 src/js/ripple/vaultclient.js delete mode 100644 test/vault-test.js diff --git a/package.json b/package.json index bf2e1ea2..2ae610da 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,6 @@ "lodash": "^2.4.1", "lru-cache": "~2.5.0", "ripple-wallet-generator": "1.0.1", - "superagent": "^0.18.0", "ws": "~0.4.31" }, "devDependencies": { diff --git a/src/js/ripple/authinfo.js b/src/js/ripple/authinfo.js deleted file mode 100644 index 0ebcc75f..00000000 --- a/src/js/ripple/authinfo.js +++ /dev/null @@ -1,59 +0,0 @@ -var async = require('async'); -var superagent = require('superagent'); -var RippleTxt = require('./rippletxt').RippleTxt; - -var AuthInfo = { }; - -AuthInfo._getRippleTxt = function(domain, callback) { - RippleTxt.get(domain, callback); -}; - -AuthInfo._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.get = function(domain, username, callback) { - var self = this; - username = username.toLowerCase(); - - function getRippleTxt(callback) { - self._getRippleTxt(domain, function(err, txt) { - if (err) { - return callback(err); - } - - if (!txt.authinfo_url) { - return callback(new Error('Authentication is not supported on ' + domain)); - } - - var url = Array.isArray(txt.authinfo_url) ? txt.authinfo_url[0] : txt.authinfo_url; - - url += '?domain=' + domain + '&username=' + username; - - callback(null, url); - }); - }; - - function getUser(url, callback) { - self._getUser(url, function(err, res) { - if (err || res.error) { - callback(new Error('Authentication info server unreachable')); - } else { - callback(null, res.body); - } - }); - }; - - async.waterfall([ getRippleTxt, getUser ], callback); -}; - -exports.AuthInfo = AuthInfo; diff --git a/src/js/ripple/blob.js b/src/js/ripple/blob.js deleted file mode 100644 index 6ef07131..00000000 --- a/src/js/ripple/blob.js +++ /dev/null @@ -1,1584 +0,0 @@ -var crypt = require('./crypt').Crypt; -var SignedRequest = require('./signedrequest').SignedRequest; -var request = require('superagent'); -var extend = require("extend"); -var async = require("async"); -var log = require('./log').sub('blob'); -var BlobClient = {}; - -//Blob object class -function BlobObj(options) { - if (!options) options = { }; - - this.device_id = options.device_id; - this.url = options.url; - this.id = options.blob_id; - this.key = options.key; - this.identity = new Identity(this); - this.data = { }; -}; - -// Blob operations -// Do NOT change the mapping of existing ops -BlobObj.ops = { - // Special - noop: 0, - - // Simple ops - set: 16, - unset: 17, - extend: 18, - - // Meta ops - push: 32, - pop: 33, - shift: 34, - unshift: 35, - filter: 36 -}; - - -BlobObj.opsReverseMap = [ ]; -for (var name in BlobObj.ops) { - BlobObj.opsReverseMap[BlobObj.ops[name]] = name; -} - -//Identity fields -var identityRoot = 'identityVault'; -var identityFields = [ - 'name', - 'entityType', - 'email', - 'phone', - 'address', - 'nationalID', - 'birthday', - 'birthplace' -]; - -var entityTypes = [ - 'individual', - 'organization', - 'corporation' -]; - -var addressFields = [ - 'contact', - 'line1', - 'line2', - 'city', - 'region', //state/province/region - 'postalCode', - 'country' -]; - -var nationalIDFields = [ - 'number', - 'type', - 'country', -]; - -var idTypeFields = [ - 'ssn', - 'taxID', - 'passport', - 'driversLicense', - 'other' -]; - -/** - * Initialize a new blob object - * - * @param {function} fn - Callback function - */ - -BlobObj.prototype.init = function(fn) { - var self = this, url; - - if (self.url.indexOf('://') === -1) { - self.url = 'http://' + url; - } - - url = self.url + '/v1/blob/' + self.id; - if (this.device_id) url += '?device_id=' + this.device_id; - - request.get(url, function(err, resp) { - if (err) { - return fn(new Error(err.message || 'Could not retrieve blob')); - } else if (!resp.body) { - return fn(new Error('Could not retrieve blob')); - } else if (resp.body.twofactor) { - resp.body.twofactor.blob_id = self.id; - resp.body.twofactor.blob_url = self.url; - resp.body.twofactor.device_id = self.device_id; - resp.body.twofactor.blob_key = self.key - return fn(resp.body); - } else if (resp.body.result !== 'success') { - return fn(new Error('Incorrect username or password')); - } - - self.revision = resp.body.revision; - self.encrypted_secret = resp.body.encrypted_secret; - self.identity_id = resp.body.identity_id; - self.missing_fields = resp.body.missing_fields; - //self.attestations = resp.body.attestation_summary; - - 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(); - } - } - - //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) { - // Callback is optional - if (typeof fn !== 'function') { - fn = function(){}; - } - - //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 - }, - }; - - var signedRequest = new SignedRequest(config); - - var signed = signedRequest.signHmac(this.data.auth_secret, this.id); - - request.post(signed.url) - .send(signed.data) - .end(function(err, resp) { - // 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 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); - return false; - } -}; - -/** - * Encrypt secret with unlock key - * - * @param {string} secretUnlockkey - */ -BlobObj.prototype.encryptSecret = function (secretUnlockKey, secret) { - return crypt.encrypt(secretUnlockKey, secret); -}; - -/** - * Decrypt secret with unlock key - * - * @param {string} 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) { - 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); - return false; - } -}; - -/** - * Encrypt blob with crypt key - */ - -BlobObj.prototype.encrypt = function() { -// Filter Angular metadata before encryption -// if ('object' === typeof this.data && -// 'object' === typeof this.data.contacts) -// this.data.contacts = angular.fromJson(angular.toJson(this.data.contacts)); - - return crypt.encrypt(this.key, JSON.stringify(this.data)); -}; - -/** - * Encrypt recovery key - * - * @param {string} secret - * @param {string} blobDecryptKey - */ - -BlobObj.prototype.encryptBlobCrypt = function(secret, blobDecryptKey) { - var recoveryEncryptionKey = crypt.deriveRecoveryEncryptionKeyFromSecret(secret); - return crypt.encrypt(recoveryEncryptionKey, blobDecryptKey); -}; - -/** - * Decrypt recovery key - * - * @param {string} secret - * @param {string} encryptedKey - */ - -function decryptBlobCrypt (secret, encryptedKey) { - var recoveryEncryptionKey = crypt.deriveRecoveryEncryptionKeyFromSecret(secret); - return crypt.decrypt(recoveryEncryptionKey, encryptedKey); -}; - -/**** Blob updating functions ****/ - -/** - * Set blob element - */ - -BlobObj.prototype.set = function(pointer, value, fn) { - if (pointer == "/" + identityRoot && this.data[identityRoot]) { - return fn(new Error('Cannot overwrite Identity Vault')); - } - - this.applyUpdate('set', pointer, [value]); - this.postUpdate('set', pointer, [value], fn); -}; - -/** - * Remove blob element - */ - -BlobObj.prototype.unset = function(pointer, fn) { - if (pointer == "/" + identityRoot) { - return fn(new Error('Cannot remove Identity Vault')); - } - - this.applyUpdate('unset', pointer, []); - this.postUpdate('unset', pointer, [], fn); -}; - -/** - * Extend blob object - */ - -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) { - this.applyUpdate('unshift', pointer, [value]); - this.postUpdate('unshift', pointer, [value], fn); -}; - -/** - * Filter the row(s) from an array. - * - * This method will find any entries from the array stored under `pointer` and - * apply the `subcommands` to each of them. - * - * The subcommands can be any commands with the pointer parameter left out. - */ - -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(); - } - - args.shift(); - - // Normalize subcommands to minimize the patch size - args = args.slice(0, 2).concat(normalizeSubcommands(args.slice(2), true)); - - this.applyUpdate('filter', pointer, args); - this.postUpdate('filter', pointer, args, callback); -}; - -/** - * Apply udpdate to the blob - */ - -BlobObj.prototype.applyUpdate = function(op, path, params) { - // Exchange from numeric op code to string - if (typeof op === 'number') { - op = BlobObj.opsReverseMap[op]; - } - - 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('/'); - var first = pointer.shift(); - - 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) { - var _this = this; - var part = _this.unescapeToken(pointer.shift()); - - if (Array.isArray(context)) { - 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 '-''); - } - } 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') { - context[part] = {}; - } else if (op === 'unshift') { - context[part] = []; - } else { - return null; - } - } - - if (pointer.length !== 0) { - 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 (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); - } -}; - -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 '/'; - } - throw new Error('Invalid tilde escape: ' + m); - }); -}; - -/** - * Sumbit update to blob vault - */ - -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 (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); - - 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)) - } - }; - - - var signedRequest = new SignedRequest(config); - var signed = signedRequest.signHmac(this.data.auth_secret, this.id); - - request.post(signed.url) - .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); - } - }); -}; - -/** - * get2FA - HMAC signed request - */ - -BlobObj.prototype.get2FA = function (fn) { - var config = { - method : 'GET', - url : this.url + '/v1/blob/' + this.id + '/2FA?device_id=' + this.device_id, - }; - - var signedRequest = new SignedRequest(config); - var signed = signedRequest.signHmac(this.data.auth_secret, this.id); - - request.get(signed.url) - .end(function(err, resp) { - if (err) { - fn(err); - } else if (resp.body && resp.body.result === 'success') { - fn(null, resp.body); - } else if (resp.body && resp.body.result === 'error') { - fn(new Error(resp.body.message)); - } else { - fn(new Error('Unable to retrieve settings.')); - } - }); -} - -/** - * set2FA - * modify 2 factor auth settings - * @params {object} options - * @params {string} options.masterkey - * @params {boolean} options.enabled - * @params {string} options.phone - * @params {string} options.country_code - */ - -BlobObj.prototype.set2FA = function(options, fn) { - - var config = { - method : 'POST', - url : this.url + '/v1/blob/' + this.id + '/2FA', - data : { - enabled : options.enabled, - phone : options.phone, - country_code : options.country_code - } - }; - - var signedRequest = new SignedRequest(config); - var signed = signedRequest.signAsymmetric(options.masterkey, this.data.account_id, this.id); - - request.post(signed.url) - .send(signed.data) - .end(function(err, resp) { - if (err) { - fn(err); - } else if (resp.body && resp.body.result === 'success') { - fn(null, resp.body); - } else if (resp.body && resp.body.result === 'error') { - fn(resp.body); - } else { - fn(new Error('Unable to update settings.')); - } - }); -}; - -/***** helper functions *****/ - -function normalizeSubcommands(subcommands, compress) { - // Normalize parameter structure - if (/(number|string)/.test(typeof subcommands[0])) { - // Case 1: Single subcommand inline - subcommands = [subcommands]; - } 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])) { - // Case 3: Multiple subcommands as array of arrays - subcommands = subcommands[0]; - } - - // Normalize op name and convert strings to numeric codes - subcommands = subcommands.map(function(subcommand) { - if (typeof subcommand[0] === 'string') { - subcommand[0] = BlobObj.ops[subcommand[0]]; - } - - if (typeof subcommand[0] !== 'number') { - throw new Error('Invalid op in subcommand'); - } - - if (typeof subcommand[1] !== 'string') { - throw new Error('Invalid path in subcommand'); - } - - return subcommand; - }); - - if (compress) { - // Convert to the minimal possible format - if (subcommands.length === 1) { - return subcommands[0]; - } else { - return [subcommands]; - } - } else { - return subcommands; - } -} - - -/***** identity ****/ - -/** - * Identity class - * - */ - -var Identity = function (blob) { - this._getBlob = function() { - return blob; - }; -}; - -/** - * getFullAddress - * returns the address formed into a text string - * @param {string} key - Encryption key - */ - -Identity.prototype.getFullAddress = function (key) { - var blob = this._getBlob(); - if (!blob || - !blob.data || - !blob.data[identityRoot] || - !blob.data[identityRoot].address) { - return ""; - } - - var address = this.get('address', key); - var text = ""; - - if (address.value.contact) text += address.value.contact; - if (address.value.line1) text += " " + address.value.line1; - if (address.value.line2) text += " " + address.value.line2; - if (address.value.city) text += " " + address.value.city; - if (address.value.region) text += " " + address.value.region; - if (address.value.postalCode) text += " " + address.value.postalCode; - if (address.value.country) text += " " + address.value.country; - return text; -}; - -/** - * getAll - * get and decrypt all identity fields - * @param {string} key - Encryption key - * @param {function} fn - Callback function - */ - -Identity.prototype.getAll = function (key) { - var blob = this._getBlob(); - if (!blob || !blob.data || !blob.data[identityRoot]) { - return {}; - } - - var result = {}, identity = blob.data[identityRoot]; - for (var i in identity) { - result[i] = this.get(i, 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) { - var blob = this._getBlob(); - if (!blob || !blob.data || !blob.data[identityRoot]) { - return null; - } - - var data = blob.data[identityRoot][pointer]; - if (data && data.encrypted) { - return decrypt(key, data); - - } else if (data) { - return data; - - } else { - return null; - } - - function decrypt (key, data) { - var value; - var result = {encrypted : true}; - - try { - value = crypt.decrypt(key, data.value); - } catch (e) { - result.value = data.value; - result.error = e; - return result; - } - - try { - result.value = JSON.parse(value); - } catch (e) { - result.value = value; - } - - return result; - } -}; - -/** - * set - * set and encrypt a single identity field. - * @param {string} pointer - Field to set - * @param {string} key - Encryption key - * @param {string} value - Unencrypted data - * @param {function} fn - Callback function - */ - -Identity.prototype.set = function (pointer, key, value, fn) { - var self = this, blob = this._getBlob(); - - if (!fn) fn = function(){ }; - - //check fields for validity - if (identityFields.indexOf(pointer) === -1) { - return fn(new Error("invalid identity field")); - - //validate address fields - } else if (pointer === 'address') { - if (typeof value !== 'object') { - return fn(new Error("address must be an object")); - } - - for (var addressField in value) { - if (addressFields.indexOf(addressField) === -1) { - return fn(new Error("invalid address field")); - } - } - - //validate nationalID fields - } else if (pointer === 'nationalID') { - if (typeof value !== 'object') { - return fn(new Error("nationalID must be an object")); - } - - for (var idField in value) { - if (nationalIDFields.indexOf(idField) === -1) { - return fn(new Error("invalid nationalID field")); - } - - if (idField === 'type') { - if (idTypeFields.indexOf(value[idField]) === -1) { - return fn(new Error("invalid nationalID type")); - } - } - } - - //validate entity type - } else if (pointer === 'entityType') { - if (entityTypes.indexOf(value) === -1) { - return fn(new Error("invalid entity type")); - } - } - - async.waterfall([ validate, set ], fn); - - //make sure the identity setup is valid - function validate (callback) { - - if (!blob) return fn(new Error("Identity must be associated with a blob")); - else if (!blob.data) return fn(new Error("Invalid Blob")); - else if (!blob.data[identityRoot]) { - blob.set("/" + identityRoot, {}, function(err, res){ - if (err) return callback (err); - else return callback (null); - }); - } else return callback (null); - }; - - function set (callback) { - - //NOTE: currently we will overwrite if it already exists - //the other option would be to require decrypting with the - //existing key as a form of authorization - //var current = self.get(pointer, key); - //if (current && current.error) { - // return fn ? fn(current.error) : undefined; - //} - - var data = {}; - data[pointer] = { - encrypted : key ? true : false, - value : key ? encrypt(key, value) : value - }; - - self._getBlob().extend("/" + identityRoot, data, callback); - }; - - function encrypt (key, value) { - if (typeof value === 'object') value = JSON.stringify(value); - return crypt.encrypt(key, value); - } -}; - -/** - * unset - * remove a single identity field - will only be removed - * with a valid decryption key - * @param {string} pointer - Field to remove - * @param {string} key - Encryption key - * @param {function} fn - Callback function - */ - -Identity.prototype.unset = function (pointer, key, fn) { - - if (!fn) fn = function(){ }; - - //NOTE: this is rather useless since you can overwrite - //without an encryption key - var data = this.get(pointer, key); - if (data && data.error) { - return fn(data.error); - } - - this._getBlob().unset("/" + identityRoot+"/" + pointer, fn); -}; - -/***** blob client methods ****/ - -/** - * Blob object class - */ - -exports.Blob = BlobObj; - -/** - * Get ripple name for a given address - */ - -BlobClient.getRippleName = function(url, address, fn) { - if (!crypt.isValidAddress(address)) { - return fn (new Error('Invalid ripple address')); - } - - if (!crypt.isValidAddress(address)) return fn (new Error("Invalid ripple address")); - request.get(url + '/v1/user/' + address, function(err, resp){ - 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 - * @params {object} options - * @params {string} options.url - * @params {string} options.blob_id - * @params {string} options.key - * @params {string} options.device_id //optional - */ - -BlobClient.get = function (options, fn) { - var blob = new BlobObj(options); - blob.init(fn); -}; - -/** - * requestToken - * request new token to be sent for 2FA - * @param {string} url - * @param {string} id - * @param {string} force_sms - */ - -BlobClient.requestToken = function (url, id, force_sms, fn) { - var config = { - method : 'GET', - url : url + '/v1/blob/' + id + '/2FA/requestToken' - }; - - - if (force_sms && force_sms instanceof Function) { - fn = force_sms; - } else if (force_sms) { - config.url += "?force_sms=true"; - } - - request.get(config.url) - .end(function(err, resp) { - if (err) { - fn(err); - } else if (resp.body && resp.body.result === 'success') { - fn(null, resp.body); - } else if (resp.body && resp.body.result === 'error') { - fn(new Error(resp.body.message)); - } else { - fn(new Error('Unable to request authentication token.')); - } - }); -}; - -/** - * verifyToken - * verify a device token for 2FA - * @param {object} options - * @param {string} options.url - * @param {string} options.id - * @param {string} options.device_id - * @param {string} options.token - * @param {boolean} options.remember_me - */ - -BlobClient.verifyToken = function (options, fn) { - var config = { - method : 'POST', - url : options.url + '/v1/blob/' + options.id + '/2FA/verifyToken', - data : { - device_id : options.device_id, - token : options.token, - remember_me : options.remember_me - } - }; - - request.post(config.url) - .send(config.data) - .end(function(err, resp) { - if (err) { - fn(err); - } else if (resp.body && resp.body.result === 'success') { - fn(null, resp.body); - } else if (resp.body && resp.body.result === 'error') { - fn(new Error(resp.body.message)); - } else { - fn(new Error('Unable to verify authentication token.')); - } - }); -}; - -/** - * Verify email address - */ - -BlobClient.verify = function(url, username, token, fn) { - url += '/v1/user/' + username + '/verify/' + token; - request.get(url, function(err, resp) { - if (err) { - fn(new Error("Failed to verify the account - XHR error")); - } else if (resp.body && resp.body.result === 'success') { - fn(null, resp.body); - } else { - fn(new Error('Failed to verify the account')); - } - }); -}; - -/** - * resendEmail - * send a new verification email - * @param {object} opts - * @param {string} opts.id - * @param {string} opts.username - * @param {string} opts.account_id - * @param {string} opts.email - * @param {string} opts.activateLink - * @param {function} fn - Callback - */ - -BlobClient.resendEmail = function (opts, fn) { - var config = { - method : 'POST', - url : opts.url + '/v1/user/email', - data : { - blob_id : opts.id, - username : opts.username, - email : opts.email, - hostlink : opts.activateLink - } - }; - - var signedRequest = new SignedRequest(config); - var signed = signedRequest.signAsymmetric(opts.masterkey, opts.account_id, opts.id); - - request.post(signed.url) - .send(signed.data) - .end(function(err, resp) { - if (err) { - log.error("resendEmail:", err); - fn(new Error("Failed to resend the token")); - } else if (resp.body && resp.body.result === 'success') { - fn(null, resp.body); - } else if (resp.body && resp.body.result === 'error') { - log.error("resendEmail:", resp.body.message); - fn(new Error("Failed to resend the token")); - } else { - fn(new Error("Failed to resend the token")); - } - }); -}; - -/** - * RecoverBlob - * recover a blob using the account secret - * @param {object} opts - * @param {string} opts.url - * @param {string} opts.username - * @param {string} opts.masterkey - * @param {function} fn - */ - -BlobClient.recoverBlob = function (opts, fn) { - var username = String(opts.username).trim(); - var config = { - method : 'GET', - url : opts.url + '/v1/user/recov/' + username, - }; - - var signedRequest = new SignedRequest(config); - var signed = signedRequest.signAsymmetricRecovery(opts.masterkey, username); - - request.get(signed.url) - .end(function(err, resp) { - if (err) { - fn(err); - } else if (resp.body && resp.body.result === 'success') { - if (!resp.body.encrypted_blobdecrypt_key) { - fn(new Error('Missing encrypted blob decrypt key.')); - } else { - handleRecovery(resp); - } - } else if (resp.body && resp.body.result === 'error') { - fn(new Error(resp.body.message)); - } else { - fn(new Error('Could not recover blob')); - } - }); - - function handleRecovery (resp) { - - var params = { - url : opts.url, - blob_id : resp.body.blob_id, - key : decryptBlobCrypt(opts.masterkey, resp.body.encrypted_blobdecrypt_key) - } - - var blob = new BlobObj(params); - - blob.revision = resp.body.revision; - blob.encrypted_secret = resp.body.encrypted_secret; - - if (!blob.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 && blob.applyEncryptedPatch(patch); - }); - - if (successful) { - blob.consolidate(); - } - } - - //return with newly decrypted blob - fn(null, blob); - }; -}; - - -/** - * updateKeys - * Change the blob encryption keys - * @param {object} opts - * @param {string} opts.username - * @param {object} opts.keys - * @param {object} opts.blob - * @param {string} masterkey - */ - -BlobClient.updateKeys = function (opts, fn) { - var old_id = opts.blob.id; - opts.blob.id = opts.keys.id; - opts.blob.key = opts.keys.crypt; - opts.blob.encrypted_secret = opts.blob.encryptSecret(opts.keys.unlock, opts.masterkey); - - var config = { - method : 'POST', - url : opts.blob.url + '/v1/user/' + opts.username + '/updatekeys', - data : { - blob_id : opts.blob.id, - data : opts.blob.encrypt(), - revision : opts.blob.revision, - encrypted_secret : opts.blob.encrypted_secret, - encrypted_blobdecrypt_key : opts.blob.encryptBlobCrypt(opts.masterkey, opts.keys.crypt), - } - }; - - var signedRequest = new SignedRequest(config); - var signed = signedRequest.signAsymmetric(opts.masterkey, opts.blob.data.account_id, old_id); - - request.post(signed.url) - .send(signed.data) - .end(function(err, resp) { - if (err) { - log.error("updateKeys:", err); - fn(new Error('Failed to update blob - XHR error')); - } else if (!resp.body || resp.body.result !== 'success') { - log.error("updateKeys:", resp.body ? resp.body.message : null); - fn(new Error('Failed to update blob - bad result')); - } else { - fn(null, resp.body); - } - }); -}; - -/** - * rename - * Change the username - * @param {object} opts - * @param {string} opts.username - * @param {string} opts.new_username - * @param {object} opts.keys - * @param {object} opts.blob - * @param {string} masterkey - */ - -BlobClient.rename = function (opts, fn) { - var old_id = opts.blob.id; - opts.blob.id = opts.keys.id; - opts.blob.key = opts.keys.crypt; - opts.blob.encryptedSecret = opts.blob.encryptSecret(opts.keys.unlock, opts.masterkey); - - var config = { - method: 'POST', - url: opts.blob.url + '/v1/user/' + opts.username + '/rename', - data: { - blob_id : opts.blob.id, - username : opts.new_username, - data : opts.blob.encrypt(), - revision : opts.blob.revision, - encrypted_secret : opts.blob.encryptedSecret, - encrypted_blobdecrypt_key : opts.blob.encryptBlobCrypt(opts.masterkey, opts.keys.crypt) - } - }; - - var signedRequest = new SignedRequest(config); - var signed = signedRequest.signAsymmetric(opts.masterkey, opts.blob.data.account_id, old_id); - - request.post(signed.url) - .send(signed.data) - .end(function(err, resp) { - if (err) { - log.error("rename:", err); - fn(new Error("Failed to rename")); - } else if (resp.body && resp.body.result === 'success') { - fn(null, resp.body); - } else if (resp.body && resp.body.result === 'error') { - log.error("rename:", resp.body.message); - fn(new Error("Failed to rename")); - } else { - fn(new Error("Failed to rename")); - } - }); -}; - -/** - * Create a blob object - * - * @param {object} options - * @param {string} options.url - * @param {string} options.id - * @param {string} options.crypt - * @param {string} options.unlock - * @param {string} options.username - * @param {string} options.masterkey - * @param {object} options.oldUserBlob - * @param {function} fn - */ - -BlobClient.create = function(options, fn) { - var params = { - url : options.url, - blob_id : options.id, - key : options.crypt - } - var blob = new BlobObj(params); - - 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.encrypted_secret = blob.encryptSecret(options.unlock, options.masterkey); - - // Migration - if (options.oldUserBlob) { - blob.data.contacts = options.oldUserBlob.data.contacts; - } - - //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, - domain : options.domain, - encrypted_blobdecrypt_key : blob.encryptBlobCrypt(options.masterkey, options.crypt), - encrypted_secret : blob.encrypted_secret - } - }; - - var signedRequest = new SignedRequest(config); - var signed = signedRequest.signAsymmetric(options.masterkey, blob.data.account_id, options.id); - - request.post(signed.url) - .send(signed.data) - .end(function(err, resp) { - if (err) { - fn(err); - } else if (resp.body && resp.body.result === 'success') { - blob.identity_id = resp.body.identity_id; - fn(null, blob, resp.body); - } else if (resp.body && resp.body.result === 'error') { - fn(new Error(resp.body.message)); - } else { - fn(new Error('Could not create blob')); - } - }); -}; - -/** - * deleteBlob - * @param {object} options - * @param {string} options.url - * @param {string} options.username - * @param {string} options.blob_id - * @param {string} options.account_id - * @param {string} options.masterkey - */ - -BlobClient.deleteBlob = function(options, fn) { - - var config = { - method : 'DELETE', - url : options.url + '/v1/user/' + options.username, - }; - - var signedRequest = new SignedRequest(config); - var signed = signedRequest.signAsymmetric(options.masterkey, options.account_id, options.blob_id); - - request.del(signed.url) - .end(function(err, resp) { - if (err) { - fn(err); - } else if (resp.body && resp.body.result === 'success') { - fn(null, resp.body); - } else if (resp.body && resp.body.result === 'error') { - fn(new Error(resp.body.message)); - } else { - fn(new Error('Could not delete blob')); - } - }); -}; - -/*** identity related functions ***/ - -/** - * updateProfile - * update information stored outside the blob - HMAC signed - * @param {object} - * @param {string} opts.url - * @param {string} opts.auth_secret - * @param {srring} opts.blob_id - * @param {object} opts.profile - * @param {array} opts.profile.attributes (optional, array of attribute objects) - * @param {array} opts.profile.addresses (optional, array of address objects) - * - * @param {string} attribute.id ... id of existing attribute - * @param {string} attribute.name ... attribute name i.e. ripple_address - * @param {string} attribute.type ... optional, sub-type of attribute - * @param {string} attribute.value ... value of attribute - * @param {string} attribute.domain ... corresponding domain - * @param {string} attribute.status ... “current”, “removed”, etc. - * @param {string} attribute.visibitlity ... “public”, ”private” - */ - -BlobClient.updateProfile = function (opts, fn) { - var config = { - method: 'POST', - url: opts.url + '/v1/profile/', - dataType: 'json', - data: opts.profile - }; - - var signedRequest = new SignedRequest(config); - var signed = signedRequest.signHmac(opts.auth_secret, opts.blob_id); - - request.post(signed.url) - .send(signed.data) - .end(function(err, resp) { - - if (err) { - log.error('updateProfile:', err); - fn(new Error('Failed to update profile - XHR error')); - } else if (resp.body && resp.body.result === 'success') { - fn(null, resp.body); - } else if (resp.body) { - log.error('updateProfile:', resp.body); - fn(new Error('Failed to update profile')); - } else { - fn(new Error('Failed to update profile')); - } - }); -}; - -/** - * getProfile - * @param {Object} opts - * @param {string} opts.url - * @param {string} opts.auth_secret - * @param {srring} opts.blob_id - */ - -BlobClient.getProfile = function (opts, fn) { - var config = { - method: 'GET', - url: opts.url + '/v1/profile/' - }; - - var signedRequest = new SignedRequest(config); - var signed = signedRequest.signHmac(opts.auth_secret, opts.blob_id); - - request.get(signed.url) - .send(signed.data) - .end(function(err, resp) { - - if (err) { - log.error('getProfile:', err); - fn(new Error('Failed to get profile - XHR error')); - } else if (resp.body && resp.body.result === 'success') { - fn(null, resp.body); - } else if (resp.body) { - log.error('getProfile:', resp.body); - fn(new Error('Failed to get profile')); - } else { - fn(new Error('Failed to get profile')); - } - }); -}; - -/** - * getAttestation - * @param {Object} opts - * @param {string} opts.url - * @param {string} opts.auth_secret - * @param {string} opts.blob_id - * @param {string} opts.type (email,phone,basic_identity) - * @param {object} opts.phone (required for type 'phone') - * @param {string} opts.email (required for type 'email') - */ - -BlobClient.getAttestation = function (opts, fn) { - var params = { }; - - if (opts.phone) params.phone = opts.phone; - if (opts.email) params.email = opts.email; - - var config = { - method: 'POST', - url: opts.url + '/v1/attestation/' + opts.type, - dataType: 'json', - data: params - }; - - var signedRequest = new SignedRequest(config); - var signed = signedRequest.signHmac(opts.auth_secret, opts.blob_id); - - request.post(signed.url) - .send(signed.data) - .end(function(err, resp) { - - if (err) { - log.error('attest:', err); - fn(new Error('attestation error - XHR error')); - } else if (resp.body && resp.body.result === 'success') { - if (resp.body.attestation) { - resp.body.decoded = BlobClient.parseAttestation(resp.body.attestation); - } - - fn(null, resp.body); - } else if (resp.body) { - log.error('attestation:', resp.body); - fn(new Error('attestation error: ' + resp.body.message || "")); - } else { - fn(new Error('attestation error')); - } - }); -}; - -/** - * getAttestationSummary - * @param {Object} opts - * @param {string} opts.url - * @param {string} opts.auth_secret - * @param {string} opts.blob_id - */ - -BlobClient.getAttestationSummary = function (opts, fn) { - - - var config = { - method: 'GET', - url: opts.url + '/v1/attestation/summary', - dataType: 'json' - }; - - if (opts.full) config.url += '?full=true'; - - var signedRequest = new SignedRequest(config); - var signed = signedRequest.signHmac(opts.auth_secret, opts.blob_id); - - request.get(signed.url) - .send(signed.data) - .end(function(err, resp) { - - if (err) { - log.error('attest:', err); - fn(new Error('attestation error - XHR error')); - } else if (resp.body && resp.body.result === 'success') { - if (resp.body.attestation) { - resp.body.decoded = BlobClient.parseAttestation(resp.body.attestation); - } - - fn(null, resp.body); - } else if (resp.body) { - log.error('attestation:', resp.body); - fn(new Error('attestation error: ' + resp.body.message || "")); - } else { - fn(new Error('attestation error')); - } - }); -}; - -/** - * updateAttestation - * @param {Object} opts - * @param {string} opts.url - * @param {string} opts.auth_secret - * @param {string} opts.blob_id - * @param {string} opts.type (email,phone,profile,identity) - * @param {object} opts.phone (required for type 'phone') - * @param {object} opts.profile (required for type 'profile') - * @param {string} opts.email (required for type 'email') - * @param {string} opts.answers (required for type 'identity') - * @param {string} opts.token (required for completing email or phone attestations) - */ - -BlobClient.updateAttestation = function (opts, fn) { - - var params = { }; - - if (opts.phone) params.phone = opts.phone; - if (opts.profile) params.profile = opts.profile; - if (opts.email) params.email = opts.email; - if (opts.token) params.token = opts.token; - if (opts.answers) params.answers = opts.answers; - - var config = { - method: 'POST', - url: opts.url + '/v1/attestation/' + opts.type + '/update', - dataType: 'json', - data: params - }; - - var signedRequest = new SignedRequest(config); - var signed = signedRequest.signHmac(opts.auth_secret, opts.blob_id); - - request.post(signed.url) - .send(signed.data) - .end(function(err, resp) { - - if (err) { - log.error('attest:', err); - fn(new Error('attestation error - XHR error')); - } else if (resp.body && resp.body.result === 'success') { - if (resp.body.attestation) { - resp.body.decoded = BlobClient.parseAttestation(resp.body.attestation); - } - - fn(null, resp.body); - } else if (resp.body) { - log.error('attestation:', resp.body); - fn(new Error('attestation error: ' + resp.body.message || "")); - } else { - fn(new Error('attestation error')); - } - }); -}; - -/** - * parseAttestation - * @param {Object} attestation - */ - -BlobClient.parseAttestation = function (attestation) { - var segments = decodeURIComponent(attestation).split('.'); - var decoded; - - // base64 decode and parse JSON - try { - decoded = { - header : JSON.parse(crypt.decodeBase64(segments[0])), - payload : JSON.parse(crypt.decodeBase64(segments[1])), - signature : segments[2] - }; - - } catch (e) { - console.log("invalid attestation:", e); - } - - return decoded; -}; - -exports.BlobClient = BlobClient; diff --git a/src/js/ripple/index.js b/src/js/ripple/index.js index ac1faa12..2d326957 100644 --- a/src/js/ripple/index.js +++ b/src/js/ripple/index.js @@ -13,17 +13,12 @@ exports.Meta = require('./meta').Meta; exports.SerializedObject = require('./serializedobject').SerializedObject; exports.RippleError = require('./rippleerror').RippleError; exports.Message = require('./message').Message; -exports.VaultClient = require('./vaultclient').VaultClient; -exports.AuthInfo = require('./authinfo').AuthInfo; -exports.RippleTxt = require('./rippletxt').RippleTxt; exports.binformat = require('./binformat'); exports.utils = require('./utils'); exports.Server = require('./server').Server; exports.Wallet = require('./wallet'); exports.Ledger = require('./ledger').Ledger; exports.TransactionQueue = require('./transactionqueue').TransactionQueue; -exports.VaultClient = require('./vaultclient').VaultClient; -exports.Blob = require('./blob').Blob; exports.RangeSet = require('./rangeset').RangeSet; // Important: We do not guarantee any specific version of SJCL or for any diff --git a/src/js/ripple/rippletxt.js b/src/js/ripple/rippletxt.js deleted file mode 100644 index 0cb93a18..00000000 --- a/src/js/ripple/rippletxt.js +++ /dev/null @@ -1,133 +0,0 @@ -var request = require('superagent'); -var Currency = require('./currency').Currency; - -var RippleTxt = { - txts : { } -}; - -RippleTxt.urlTemplates = [ - 'https://{{domain}}/ripple.txt', - 'https://www.{{domain}}/ripple.txt', - 'https://ripple.{{domain}}/ripple.txt', - 'http://{{domain}}/ripple.txt', - 'http://www.{{domain}}/ripple.txt', - 'http://ripple.{{domain}}/ripple.txt' -]; - -/** - * Gets the ripple.txt file for the given domain - * @param {string} domain - Domain to retrieve file from - * @param {function} fn - Callback function - */ - -RippleTxt.get = function(domain, fn) { - var self = this; - - if (self.txts[domain]) { - return fn(null, self.txts[domain]); - } - - ;(function nextUrl(i) { - var url = RippleTxt.urlTemplates[i]; - - if (!url) { - return fn(new Error('No ripple.txt found')); - } - - url = url.replace('{{domain}}', domain); - - request.get(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.parse = function(txt) { - var currentSection = ''; - var sections = { }; - - txt = txt.replace(/\r?\n/g, '\n').split('\n'); - - for (var i = 0, l = txt.length; i < l; i++) { - var line = txt[i]; - - if (!line.length || line[0] === '#') { - continue; - } - - if (line[0] === '[' && line[line.length - 1] === ']') { - currentSection = line.slice(1, line.length - 1); - sections[currentSection] = []; - } else { - line = line.replace(/^\s+|\s+$/g, ''); - if (sections[currentSection]) { - sections[currentSection].push(line); - } - } - } - - return sections; -}; - -/** - * extractDomain - * attempt to extract the domain from a given url - * returns the url if unsuccessful - * @param {Object} url - */ - -RippleTxt.extractDomain = function (url) { - match = /[^.]*\.[^.]{2,3}(?:\.[^.]{2,3})?([^.\?][^\?.]+?)?$/.exec(url); - return match && match[0] ? match[0] : url; -}; - -/** - * getCurrencies - * returns domain, issuer account and currency object - * for each currency found in the domain's ripple.txt file - * @param {Object} domain - * @param {Object} fn - */ - -RippleTxt.getCurrencies = function(domain, fn) { - domain = RippleTxt.extractDomain(domain); - this.get(domain, function(err, txt) { - if (err) { - return fn(err); - } - - if (err || !txt.currencies || !txt.accounts) { - return fn(null, []); - } - - //NOTE: this won't be accurate if there are - //multiple issuer accounts with different - //currencies associated with each. - var issuer = txt.accounts[0]; - var currencies = []; - - txt.currencies.forEach(function(currency) { - currencies.push({ - issuer : issuer, - currency : Currency.from_json(currency), - domain : domain - }); - }); - - fn(null, currencies); - }); -}; - -exports.RippleTxt = RippleTxt; diff --git a/src/js/ripple/signedrequest.js b/src/js/ripple/signedrequest.js deleted file mode 100644 index 57499182..00000000 --- a/src/js/ripple/signedrequest.js +++ /dev/null @@ -1,203 +0,0 @@ -var Crypt = require('./crypt').Crypt; -var Message = require('./message').Message; -var parser = require("url"); -var querystring = require('querystring'); -var extend = require("extend"); - -var SignedRequest = function (config) { - // XXX Constructor should be generalized and constructing from an Angular.js - // $http config should be a SignedRequest.from... utility method. - this.config = extend(true, {}, config); - if (!this.config.data) this.config.data = {}; -}; - - - -/** - * Create a string from request parameters that - * will be used to sign a request - * @param {Object} parsed - parsed url - * @param {Object} date - * @param {Object} mechanism - type of signing - */ -SignedRequest.prototype.getStringToSign = function (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(this.config.data)); - - // Canonical request using Amazon's v4 signature format - // See: http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html - var canonicalRequest = [ - this.config.method || 'GET', - parsed.pathname || '', - parsed.search || '', - // XXX Headers signing not supported - '', - '', - Crypt.hashSha512(canonicalData).toLowerCase() - ].join('\n'); - - // String to sign inspired by Amazon's v4 signature format - // See: http://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html - // - // We don't have a credential scope, so we skip it. - // - // But that modifies the format, so the format ID is RIPPLE1, instead of AWS4. - return [ - mechanism, - date, - Crypt.hashSha512(canonicalRequest).toLowerCase() - ].join('\n'); -}; - -//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) - 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) - 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 ); -}; - -/** - * HMAC signed request - * @param {Object} config - * @param {Object} auth_secret - * @param {Object} blob_id - */ -SignedRequest.prototype.signHmac = function (auth_secret, blob_id) { - var config = extend(true, {}, this.config); - - // Parse URL - var parsed = parser.parse(config.url); - var date = dateAsIso8601(); - var signatureType = 'RIPPLE1-HMAC-SHA512'; - var stringToSign = this.getStringToSign(parsed, date, signatureType); - var signature = Crypt.signString(auth_secret, stringToSign); - - 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 - */ -SignedRequest.prototype.signAsymmetric = function (secretKey, account, blob_id) { - var config = extend(true, {}, this.config); - - // Parse URL - var parsed = parser.parse(config.url); - var date = dateAsIso8601(); - var signatureType = 'RIPPLE1-ECDSA-SHA512'; - var stringToSign = this.getStringToSign(parsed, date, signatureType); - var signature = Message.signMessage(stringToSign, secretKey); - - 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; -}; - -/** - * Asymmetric signed request for vault recovery - * @param {Object} config - * @param {Object} secretKey - * @param {Object} username - */ -SignedRequest.prototype.signAsymmetricRecovery = function (secretKey, username) { - var config = extend(true, {}, this.config); - - // Parse URL - var parsed = parser.parse(config.url); - var date = dateAsIso8601(); - var signatureType = 'RIPPLE1-ECDSA-SHA512'; - var stringToSign = this.getStringToSign(parsed, date, signatureType); - var signature = Message.signMessage(stringToSign, secretKey); - - var query = querystring.stringify({ - signature: Crypt.base64ToBase64Url(signature), - signature_date: date, - signature_username: username, - signature_type: signatureType - }); - - config.url += (parsed.search ? '&' : '?') + query; - - return config; -}; - -var dateAsIso8601 = (function () { - function pad(n) { - return (n < 0 || n > 9 ? "" : "0") + n; - } - - return function dateAsIso8601() { - var date = new Date(); - return date.getUTCFullYear() + "-" + - pad(date.getUTCMonth() + 1) + "-" + - pad(date.getUTCDate()) + "T" + - pad(date.getUTCHours()) + ":" + - pad(date.getUTCMinutes()) + ":" + - pad(date.getUTCSeconds()) + ".000Z"; - }; -})(); - -// XXX Add methods for verifying requests -// SignedRequest.prototype.verifySignatureHmac -// SignedRequest.prototype.verifySignatureAsymetric - -exports.SignedRequest = SignedRequest; - diff --git a/src/js/ripple/vaultclient.js b/src/js/ripple/vaultclient.js deleted file mode 100644 index f783f45f..00000000 --- a/src/js/ripple/vaultclient.js +++ /dev/null @@ -1,593 +0,0 @@ -var async = require('async'); -var blobClient = require('./blob').BlobClient; -var AuthInfo = require('./authinfo').AuthInfo; -var crypt = require('./crypt').Crypt; -var log = require('./log').sub('vault'); -function VaultClient(opts) { - - var self = this; - - if (!opts) { - opts = { }; - } - - if (typeof opts === 'string') { - opts = { domain: opts }; - } - - this.domain = opts.domain || 'ripple.com'; - this.infos = { }; -}; - -/** - * getAuthInfo - * gets auth info for a username. returns authinfo - * even if user does not exists (with exist set to false) - * @param {string} username - * @param {function} callback - */ -VaultClient.prototype.getAuthInfo = function (username, callback) { - - AuthInfo.get(this.domain, username, function(err, authInfo) { - if (err) { - return callback(err); - } - - if (authInfo.version !== 3) { - return callback(new Error('This wallet is incompatible with this version of the vault-client.')); - } - - if (!authInfo.pakdf) { - return callback(new Error('No settings for PAKDF in auth packet.')); - } - - if (typeof authInfo.blobvault !== 'string') { - return callback(new Error('No blobvault specified in the authinfo.')); - } - - callback(null, authInfo); - }); -}; - -/** - * _deriveLoginKeys - * method designed for asnyc waterfall - */ - -VaultClient.prototype._deriveLoginKeys = function (authInfo, password, callback) { - var normalizedUsername = authInfo.username.toLowerCase().replace(/-/g, ''); - - //derive login keys - crypt.derive(authInfo.pakdf, 'login', normalizedUsername, password, function(err, keys) { - if (err) { - callback(err); - } else { - callback(null, authInfo, password, keys); - } - }); -}; - - - -/** - * _deriveUnlockKey - * method designed for asnyc waterfall - */ - -VaultClient.prototype._deriveUnlockKey = function (authInfo, password, keys, callback) { - var normalizedUsername = authInfo.username.toLowerCase().replace(/-/g, ''); - - //derive unlock key - crypt.derive(authInfo.pakdf, 'unlock', normalizedUsername, password, function(err, unlock) { - if (err) { - log.error('derive:', err); - return callback(err); - } - - if (!keys) { - keys = { }; - } - - keys.unlock = unlock.unlock; - callback(null, authInfo, keys); - }); -}; - -/** - * 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, callback) { - //use the url from previously retrieved authInfo, if necessary - if (!url) { - callback(new Error('Blob vault URL is required')); - } else { - blobClient.getRippleName(url, address, callback); - } -}; - -/** - * Check blobvault for existance of username - * - * @param {string} username - * @param {function} fn - Callback function - */ - -VaultClient.prototype.exists = function(username, callback) { - AuthInfo.get(this.domain, username.toLowerCase(), function(err, authInfo) { - if (err) { - callback(err); - } else { - callback(null, !!authInfo.exists); - } - }); -}; - -/** - * 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, device_id, callback) { - var self = this; - - var steps = [ - getAuthInfo, - self._deriveLoginKeys, - getBlob - ]; - - async.waterfall(steps, callback); - - function getAuthInfo(callback) { - self.getAuthInfo(username, function(err, authInfo){ - - if (authInfo && !authInfo.exists) { - return callback(new Error('User does not exist.')); - } - - return callback (err, authInfo, password); - }); - } - - function getBlob(authInfo, password, keys, callback) { - var options = { - url : authInfo.blobvault, - blob_id : keys.id, - key : keys.crypt, - device_id : device_id - }; - - blobClient.get(options, function(err, blob) { - if (err) { - return callback(err); - } - - //save for relogin - self.infos[keys.id] = authInfo; - - //migrate missing fields - if (blob.missing_fields) { - if (blob.missing_fields.encrypted_blobdecrypt_key) { - log.info('migration: saving encrypted blob decrypt key'); - authInfo.blob = blob; - //get the key to unlock the secret, then update the blob keys - self._deriveUnlockKey(authInfo, password, keys, updateKeys); - } - } - - callback(null, { - blob : blob, - username : authInfo.username, - verified : authInfo.emailVerified - }); - }); - }; - - function updateKeys (err, params, keys) { - if (err || !keys.unlock) { - return; //unable to unlock - } - - var secret; - try { - secret = crypt.decrypt(keys.unlock, params.blob.encrypted_secret); - } catch (error) { - return log.error('decrypt:', error); - } - - options = { - username : params.username, - blob : params.blob, - masterkey : secret, - keys : keys - }; - - blobClient.updateKeys(options, function(err, resp){ - if (err) { - log.error('updateKeys:', err); - } - }); - } -}; - -/** - * 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, device_id, callback) { - //use the url from previously retrieved authInfo, if necessary - if (!url && this.infos[id]) { - url = this.infos[id].blobvault; - } - - if (!url) { - return callback(new Error('Blob vault URL is required')); - } - - var options = { - url : url, - blob_id : id, - key : key, - device_id : device_id - }; - - blobClient.get(options, 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) { - var self = this; - - var steps = [ - getAuthInfo, - self._deriveUnlockKey, - unlockSecret - ]; - - async.waterfall(steps, fn); - - function getAuthInfo(callback) { - self.getAuthInfo(username, function(err, authInfo){ - - if (authInfo && !authInfo.exists) { - return callback(new Error('User does not exist.')); - } - - return callback (err, authInfo, password, {}); - }); - } - - function unlockSecret (authinfo, keys, callback) { - - var secret; - try { - secret = crypt.decrypt(keys.unlock, encryptSecret); - } catch (error) { - return callback(error); - } - - callback(null, { - keys : keys, - secret : secret - }); - } -}; - -/** - * 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, device_id, fn) { - var self = this; - - var steps = [ - login, - deriveUnlockKey, - unlockSecret - ]; - - async.waterfall(steps, fn); - - function login (callback) { - self.login(username, password, device_id, function(err, resp) { - - if (err) { - return callback(err); - } - - if (!resp.blob || !resp.blob.encrypted_secret) { - return callback(new Error('Unable to retrieve blob and secret.')); - } - - if (!resp.blob.id || !resp.blob.key) { - return callback(new Error('Unable to retrieve keys.')); - } - - //get authInfo via id - would have been saved from login - var authInfo = self.infos[resp.blob.id]; - - if (!authInfo) { - return callback(new Error('Unable to find authInfo')); - } - - callback(null, authInfo, password, resp.blob); - }); - }; - - function deriveUnlockKey (authInfo, password, blob, callback) { - self._deriveUnlockKey(authInfo, password, null, function(err, authInfo, keys){ - callback(err, keys.unlock, authInfo, blob); - }); - }; - - function unlockSecret (unlock, authInfo, blob, callback) { - var secret; - try { - secret = crypt.decrypt(unlock, blob.encrypted_secret); - } catch (error) { - return callback(error); - } - - callback(null, { - blob : blob, - unlock : unlock, - secret : secret, - username : authInfo.username, - verified : authInfo.emailVerified - }); - }; -}; - -/** - * 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, callback) { - var self = this; - - self.getAuthInfo(username, function (err, authInfo){ - if (err) { - return callback(err); - } - - blobClient.verify(authInfo.blobvault, username.toLowerCase(), token, callback); - }); -}; - -/* - * changePassword - * @param {object} options - * @param {string} options.username - * @param {string} options.password - * @param {string} options.masterkey - * @param {object} options.blob - */ - -VaultClient.prototype.changePassword = function (options, fn) { - var self = this; - var password = String(options.password).trim(); - - var steps = [ - getAuthInfo, - self._deriveLoginKeys, - self._deriveUnlockKey, - changePassword - ]; - - async.waterfall(steps, fn); - - function getAuthInfo(callback) { - self.getAuthInfo(options.username, function(err, authInfo) { - return callback (err, authInfo, password); - }); - }; - - function changePassword (authInfo, keys, callback) { - options.keys = keys; - blobClient.updateKeys(options, callback); - }; -}; - -/** - * rename - * rename a ripple account - * @param {object} options - * @param {string} options.username - * @param {string} options.new_username - * @param {string} options.password - * @param {string} options.masterkey - * @param {object} options.blob - * @param {function} fn - */ - -VaultClient.prototype.rename = function (options, fn) { - var self = this; - var new_username = String(options.new_username).trim(); - var password = String(options.password).trim(); - - var steps = [ - getAuthInfo, - self._deriveLoginKeys, - self._deriveUnlockKey, - renameBlob - ]; - - async.waterfall(steps, fn); - - function getAuthInfo(callback) { - self.getAuthInfo(new_username, function(err, authInfo){ - - if (authInfo && authInfo.exists) { - return callback(new Error('username already taken.')); - } else { - authInfo.username = new_username; - } - - return callback (err, authInfo, password); - }); - }; - - function renameBlob (authInfo, keys, callback) { - options.keys = keys; - blobClient.rename(options, callback); - }; -}; - -/** - * Register a new user and save to the blob vault - * - * @param {object} options - * @param {string} options.username - * @param {string} options.password - * @param {string} options.masterkey //optional, will create if absent - * @param {string} options.email - * @param {string} options.activateLink - * @param {object} options.oldUserBlob //optional - * @param {function} fn - */ - -VaultClient.prototype.register = function(options, fn) { - var self = this; - var username = String(options.username).trim(); - var password = String(options.password).trim(); - var result = self.validateUsername(username); - - if (!result.valid) { - return fn(new Error('invalid username.')); - } - - var steps = [ - getAuthInfo, - self._deriveLoginKeys, - self._deriveUnlockKey, - create - ]; - - async.waterfall(steps, fn); - - function getAuthInfo(callback) { - self.getAuthInfo(username, function(err, authInfo){ - return callback (err, authInfo, password); - }); - }; - - function create(authInfo, keys, callback) { - var params = { - url : authInfo.blobvault, - id : keys.id, - crypt : keys.crypt, - unlock : keys.unlock, - username : username, - email : options.email, - masterkey : options.masterkey || crypt.createMaster(), - activateLink : options.activateLink, - oldUserBlob : options.oldUserBlob, - domain : options.domain - }; - - blobClient.create(params, function(err, blob) { - if (err) { - callback(err); - } else { - callback(null, { - blob : blob, - username : username - }); - } - }); - }; -}; - -/** - * validateUsername - * check username for validity - */ - -VaultClient.prototype.validateUsername = function (username) { - username = String(username).trim(); - var result = { - valid : false, - reason : '' - }; - - if (username.length < 2) { - result.reason = 'tooshort'; - } else if (username.length > 20) { - result.reason = 'toolong'; - } else if (!/^[a-zA-Z0-9\-]+$/.exec(username)) { - result.reason = 'charset'; - } else if (/^-/.exec(username)) { - result.reason = 'starthyphen'; - } else if (/-$/.exec(username)) { - result.reason = 'endhyphen'; - } else if (/--/.exec(username)) { - result.reason = 'multhyphen'; - } else { - result.valid = true; - } - - return result; -}; - -/** - * generateDeviceID - * create a new random device ID for 2FA - */ -VaultClient.prototype.generateDeviceID = function () { - return crypt.createSecret(4); -}; - -/*** pass thru some blob client function ***/ - -VaultClient.prototype.resendEmail = blobClient.resendEmail; - -VaultClient.prototype.recoverBlob = blobClient.recoverBlob; - -VaultClient.prototype.deleteBlob = blobClient.deleteBlob; - -VaultClient.prototype.requestToken = blobClient.requestToken; - -VaultClient.prototype.verifyToken = blobClient.verifyToken; - -VaultClient.prototype.getAttestation = blobClient.getAttestation; - -VaultClient.prototype.updateAttestation = blobClient.updateAttestation; - -VaultClient.prototype.getAttestationSummary = blobClient.getAttestationSummary; - -//export by name -exports.VaultClient = VaultClient; diff --git a/test/vault-test.js b/test/vault-test.js deleted file mode 100644 index 1fb892c8..00000000 --- a/test/vault-test.js +++ /dev/null @@ -1,851 +0,0 @@ -var assert = require('assert'); -var nock = require('nock'); -var RippleTxt = require('ripple-lib').RippleTxt; -var AuthInfo = require('ripple-lib').AuthInfo; -var VaultClient = require('ripple-lib').VaultClient; -var Blob = require('ripple-lib').Blob; -var UInt256 = require('ripple-lib').UInt256; -var sjcl = require('ripple-lib').sjcl; -var online = process.argv.indexOf('--online-blobvault') !== -1 ? true : false; - -var exampleData = { - id: 'ef203d3e76552c0592384f909e6f61f1d1f02f61f07643ce015d8b0c9710dd2f', - crypt: 'f0cc91a7c1091682c245cd8e13c246cc150b2cf98b17dd6ef092019c99dc9d82', - unlock: '3e15fe3218a9c664835a6f585582e14480112110ddbe50e5028d05fc5bd9b5f4', - username: 'exampleUser', - new_username : 'exampleUser-rename', - password: 'pass word', - domain: 'staging.ripple.com', - masterkey : 'ssize4HrSYZShMWBtK6BhALGEk8VH', - email_token : '77825040-9096-4695-9cbc-76720f6a8649', - activateLink : 'https://staging.ripple.com/client/#/register/activate/', - device_id : "ac1b6f6dbca98190eb9687ba06f0e066", - identity_id : "17fddb71-a5c2-44ce-8b50-4b381339d4f2", - blob: { - url: 'https://id.staging.ripple.com', - id: 'ef203d3e76552c0592384f909e6f61f1d1f02f61f07643ce015d8b0c9710dd2f', - key: 'f0cc91a7c1091682c245cd8e13c246cc150b2cf98b17dd6ef092019c99dc9d82', - data: { - auth_secret: 'd0aa918e693080a6a8d0ddc7f4dcf4bc0eecc3c3e3235f16a98661ee9c2e7a58', - account_id: 'raVUps4RghLYkVBcpMaRbVKRTTzhesPXd', - email: 'example@example.com', - contacts: [ ], - created: '2014-05-20T23:39:52.538Z', - apps: [ ], - lastSeenTxDate: 1401925490000, - identityVault: { }, - revision: 2199, - encrypted_secret: 'APYqtqvjJk/J324rx2BGGzUiQ3mtmMMhMsbrUmgxb00W2aFVQzCC2mqd58Z17gzeUUcjtjAm' - } - } -}; - -var rippleTxtRes = "[authinfo_url]\r\nhttps://id.staging.ripple.com/v1/authinfo"; - -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 - } -}; - -var authInfoNewUsernameRes = { - 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: false, - username: exampleData.new_username, - emailVerified: false, - reserved: false - } -}; - -var signRes = '{"result":"success","signres":"64e9e46618fff0b720b8162e6caa209e046af128b929b766d3be421d3f048ba523453dad42597dcec01f23a5080c16695f6209d39a03668d46b782409e4a53821f70b5e6f7c8fd28eb641c504f9f9b2f378bf2ea7f19950790ac6a8832e2659800f5bb06b735bd450fa47b499fbcebeb3b0fc327619dd2171fa40fb0a41d9bcd69dd29567fa94e9466d4674b908f1cfc43822b38b94534cb37eead183b11b33761a73d78be6ba6f3a53291d4154ca0891fa59da58380e05a1e85b15a24d12406795385bcc5a6360a24ecbf068ff6f02097cd917281972d4895769f3a8668b852ea5d4232050200bcd03934f49ea0693d832980614dff1ead67ca2e0ce9073c25","modulus":"c7f1bc1dfb1be82d244aef01228c1409c198894eca9e21430f1669b4aa3864c9f37f3d51b2b4ba1ab9e80f59d267fda1521e88b05117993175e004543c6e3611242f24432ce8efa3b81f0ff660b4f91c5d52f2511a6f38181a7bf9abeef72db056508bbb4eeb5f65f161dd2d5b439655d2ae7081fcc62fdcb281520911d96700c85cdaf12e7d1f15b55ade867240722425198d4ce39019550c4c8a921fc231d3e94297688c2d77cd68ee8fdeda38b7f9a274701fef23b4eaa6c1a9c15b2d77f37634930386fc20ec291be95aed9956801e1c76601b09c413ad915ff03bfdc0b6b233686ae59e8caf11750b509ab4e57ee09202239baee3d6e392d1640185e1cd","alpha":"7283d19e784f48a96062271a4fa6e2c3addf14e6edf78a4bb61364856d580f13552008d7b9e3b60ebd9555e9f6c7778ec69f976757d206134e54d61ba9d588a7e37a77cf48060522478352d76db000366ef669a1b1ca93c5e3e05bc344afa1e8ccb15d3343da94180dccf590c2c32408c3f3f176c8885e95d988f1565ee9b80c12f72503ab49917792f907bbb9037487b0afed967fefc9ab090164597fcd391c43fab33029b38e66ff4af96cbf6d90a01b891f856ddd3d94e9c9b307fe01e1353a8c30edd5a94a0ebba5fe7161569000ad3b0d3568872d52b6fbdfce987a687e4b346ea702e8986b03b6b1b85536c813e46052a31ed64ec490d3ba38029544aa","exponent":"010001"}'; - -var blobRes = { - body : { - result: 'success', - encrypted_secret: 'APYqtqvjJk/J324rx2BGGzUiQ3mtmMMhMsbrUmgxb00W2aFVQzCC2mqd58Z17gzeUUcjtjAm', - blob: 'ALXga/k8mgvPpZCY0zJZdaqlptHUBL0E4V/90p4edvb7eCucU2M7aFsHIl3Z3UDu9MdlDnDU42/C+YKL1spkSTPb3rGWr0kXIFmRu8xDAd+OA3Ot7u3OBq0sN2BUHbEc47WiCue84XQHTgBh9tdeiRTqm90LJ7hZ1pD0oqr823YpFguwcC1inxFbSTNxIdWSoC3XCqZtRFM2Y5ALleWhaWKc3OwaFU6yPRcW05IBvTY/7a2SfZyklvXnJh7Bg+vfvz7ms8UCybmBgHlBPY/UqGOdZI6iFGrEQrDMFHgbxwf7bTTiaOM7Su3OsqhM1k90LvQgk3b1olb1VIMZ5J1UuTtOVTpLSsIlzgMvxxdUUyN2zMkeDE3t8kHOThhwWbLG6O+s9F9fktIv4NtoAm0dG9LtkSE1YXajk0qIYr/zrblJy7pEvNv+EzdSr+dpvssmPshgwxoHwvCwae0vL7UTmrCxIWLlHbsbU2uAzgvudJL0WOpX4W+R43U3sgMD2XysKgX783Sa7DLUWCk3Rk9eGp7c3k/XpI0IWvuKzMxID8VzdyMmXP0RE77uUufisBDtwr8gPGzS5kzU3Z/FG/dHJkfBLZdbHOffQTPKO9DKUjztWpx7CTAkN9O21XrdLK1FRAtWFTuvlA66sDYtHRaqglzFjt9DsJk7PAKi1odHeLBmYob/Bs5eK9yNnlLwu3JpHLH/jKxkuxcZ3NEdTm1WPjTdNlvT7kdGAIG9c0vIywEABkQh1kPDOe39h3GRGcUqVWJcMjJmdVDrQH7BBVV+VptCVtMOo1LviaD0MIWMiYdZyGeH5x+FkpAMjKDB3cCUkmxmis8lrDiMlnTZ5Czj+bDPp62Looc7cr2pTR2niFZRosYNgUPx6cAh7tn64RDaa/spAyv0mWyD1qRA8H0sEPmC7m7EPaBIQpODh1NFg/Bxunh+QGSmy9deINB78b9A9zLS6qWljrzg5fMDUN66xRUUKJMSD9+QJePsM4pb60vbnBBtbe04JzY7iOc/CxiT0Px6/1jlSmnY6SCtaFqtDgmQ5MLGTm1tA+aj6caT6FWsXrBboXt3eXRDPHTN+ciKELx7M3dpd4mKVWhBu7nnnVMEu1rSUrmtUStXQHod/C7vVRF2EU1hhTW7ou0hvLn+7xs9B76QeVG7iYFLiZH1qgs+upqnLCnmY3ug9yd9GQ6YwbVL1hbXJLadaOg7qhKst0KXjjjcE4G9AIEyI+UCxGdc/0PNPOCCeYEPshvonCgElGo/fAbaIuSSOFfusiovYffJ0rCkq1RagH0R/llWtUEFEDR5YFVlD3DqK6B22fQK', - revision: 2191, - email: 'example@example.com', - quota: -2975, - patches: [] - } -}; - -var recoverRes = { - body: { - encrypted_secret: 'AAd69B9En2OF4O4LsjD+pFNeJHEGIuLh2hbla58zGvN7qU/16bDfy0QlFj8/Gu++AdFwH5U6', - revision: 2403, - blob_id: 'ef203d3e76552c0592384f909e6f61f1d1f02f61f07643ce015d8b0c9710dd2f', - blob: 'AFfW9vuHJ2J5UMnEl4WrVIT9z2d+PPVNNHkqzN64b3pKDQcRPFp8vVEqL9B+YVs/KHhFVFNxxCNyVXwO/yGg4BAslYl8Ioo11IODmOltJmb94oKR/JVyfaY4bDWaOzAoa5N/c9LHpmd0L+9igK1o260MK5OZW4BQ6EG7I+8cYi5uM2CLguiddySu2yTEnyHW47zspWP33y2deh6p5mHtLdii/tmlm7b2rKpzrRVuLN/J09jqilhMxlCEr4X065YZLlQapJ45UWvpifejEw/6Qgl1WngZxwifHa504aR/QYhb1XCNeYbkjQ1MmkTmTef47Al4r/Irzoe//pDbAFA70XXkBUVUMAXWiOxU5V6gHO4yhXbTFEn7922JZlY7PIjo2Q+BxLkozMzuh8MZdoeadqffZX1fOuyTRWfPlqi7vIYgnUyTmThKe2EZv1LsB5ZUaX3KSArKDv1xPTKS0nexGNZoFckwEfVr6B2PGbMx8LPLYEEEmd95kh8NAKN1wkOPuBehLAtbMtcnLpTsotY6diqWdW4V9BSst0KDMTxZVfeesWD7/7ga9hzNvAWO1MN3aAvDCiQVufb44i4Qfu6fLS7+nxtcDCN2PqPHcANcW0cUhUNB50ajzNwRXN8B92CiY0zkS61CzWeooHOslGp0Acau1CJy8iHGyjzbPS4ui8F2h2TbDUuInOoMqiRjXFvRTxA=', - encrypted_blobdecrypt_key: 'AA9vUokfQ1WXEOArl2DUwY3cxgXGKj9uNEqrJQzUu0hqXIWRu1V+6l1qqxXKPnm9BNscMpm0BMSbxUz++lfV50c1B4akvrzIBH+MUUgNyyPcHR7JBgjEYt0=', - patches: [], - result: 'success' - } - } - -var getProfileRes = { - "result":"success", - "addresses":[], - "attributes":[{ - "attribute_id":"4034e477-ffc9-48c4-bcbc-058293f081d8", - "identity_id":"17fddb71-a5c2-44ce-8b50-4b381339d4f2", - "name":"email", - "type":"default", - "domain":null, - "value":"example@example.com", - "visibility":"public", - "updated":null - } - ] -}; - -var blob = new Blob(); - blob.url = exampleData.blob.url; - blob.id = exampleData.blob.id; - blob.device_id = exampleData.device_id; - blob.key = exampleData.blob.key; - blob.identity_id = exampleData.blob.identity_id; - blob.data = exampleData.blob.data; - blob.revision = exampleData.blob.data.revision; - -//must be set for self signed certs -process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; -while(!sjcl.random.isReady()) { - sjcl.random.addEntropy(require('crypto').randomBytes(128).toString('base64')); //add entropy to seed the generator -} - -var mockRippleTxt; -var mockRippleTxt2; -var mockAuthSign; -var mockRegister; -var mockBlob; -var mockRename; -var mockUpdate; -var mockRecover; -var mockVerify; -var mockEmail; -var mockProfile; -var mockDelete; - -if (!online) { - mockRippleTxt = nock('https://ripple.com') - .get('/ripple.txt') - .reply(200, rippleTxtRes, { - 'Content-Type': 'text/plain' - }); - - mockRippleTxt2 = nock('https://' + exampleData.domain) - .get('/ripple.txt') - .reply(200, rippleTxtRes, { - 'Content-Type': 'text/plain' - }); - - mockAuthSign = nock('https://auth1.ripple.com') - .persist() - .post('/api/sign') - .reply(200, signRes, { - 'Content-Type': 'text/plain' - }); - - mockRegister = nock('https://id.staging.ripple.com'); - mockRegister.filteringPath(/(v1\/user\?signature(.+))/g, 'register/') - .post('/register/') - .reply(200, { result: 'error', message: 'User already exists' }, { - 'Content-Type': 'application/json' - }); - - mockDelete = nock('https://id.staging.ripple.com'); - mockDelete.filteringPath(/(v1\/user\/(.+))/g, 'delete/') - .delete('/delete/') - .reply(200, { result: 'success' }, { - 'Content-Type': 'application/json' - }); - - mockBlob = nock('https://id.staging.ripple.com'); - mockBlob.get('/v1/authinfo?domain=' + exampleData.domain + '&username=' + exampleData.username.toLowerCase()) - .reply(200, JSON.stringify(authInfoRes.body), { - 'Content-Type': 'application/json' - }); - - mockBlob.get('/v1/authinfo?domain=' + exampleData.domain + '&username=' + exampleData.new_username.toLowerCase()) - .reply(200, JSON.stringify(authInfoNewUsernameRes.body), { - 'Content-Type': 'application/json' - }); - - mockBlob.filteringPath(/(blob\/.+)/g, 'blob/') - .persist() - .get('/v1/blob/') - .reply(200, JSON.stringify(blobRes.body), { - 'Content-Type': 'application/json' - }); - - mockRename = nock('https://id.staging.ripple.com/v1/user/'); - mockRename.filteringPath(/((.+)\/rename(.+))/g, 'rename/') - .post('rename/') - .reply(200, {result:'success',message:'rename'}, { - 'Content-Type': 'application/json' - }); - - mockUpdate = nock('https://id.staging.ripple.com/v1/user/'); - mockUpdate.filteringPath(/((.+)\/updatekeys(.+))/g, 'update/') - .post('update/') - .reply(200, {result:'success',message:'updateKeys'}, { - 'Content-Type': 'application/json' - }); - - mockRecover = nock('https://id.staging.ripple.com/') - mockRecover.filteringPath(/((.+)user\/recov\/(.+))/g, 'recov/') - .get('recov/') - .reply(200, recoverRes.body, { - 'Content-Type': 'application/json' - }); - - mockVerify = nock('https://id.staging.ripple.com/v1/user/'); - mockVerify.filteringPath(/((.+)\/verify(.+))/g, 'verify/') - .get('verify/') - .reply(200, {result:'error', message:'invalid token'}, { - 'Content-Type': 'application/json' - }); - - mockEmail = nock('https://id.staging.ripple.com/v1/user'); - mockEmail.filteringPath(/((.+)\/email(.+))/g, 'email/') - .post('email/') - .reply(200, {result:'success'}, { - 'Content-Type': 'application/json' - }); -} - -describe('Ripple Txt', function () { - it('should get the content of a ripple.txt file from a given domain', function(done) { - RippleTxt.get(exampleData.domain, function(err, resp) { - assert.ifError(err); - assert.strictEqual(typeof resp, 'object'); - done(); - }); - }); - - it('should get currencies from a ripple.txt file for a given domain', function(done) { - RippleTxt.getCurrencies(exampleData.domain, function(err, currencies) { - assert.ifError(err); - assert(Array.isArray(currencies)); - done(); - }); - }); - - it('should get the domain from a given url', function() { - var domain = RippleTxt.extractDomain("http://www.example.com"); - assert.strictEqual(typeof domain, 'string'); - }); -}); - -describe('AuthInfo', function() { - it('should get auth info', function(done) { - AuthInfo.get(exampleData.domain, exampleData.username, function(err, resp) { - assert.ifError(err); - Object.keys(authInfoRes.body).forEach(function(prop) { - assert(resp.hasOwnProperty(prop)); - }); - done(); - }); - }); -}); - -describe('VaultClient', function () { - var client = new VaultClient(exampleData.domain); - - describe('#initialization', function() { - it('should be initialized with a domain', function() { - var client = new VaultClient({ domain: exampleData.domain }); - assert.strictEqual(client.domain, exampleData.domain); - }); - - it('should default to ripple.com without a domain', function () { - var client = new VaultClient(); - assert.strictEqual(client.domain, 'ripple.com'); - }); - }); - - 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.strictEqual(typeof resp, 'boolean'); - 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, exampleData.device_id, function(err, resp) { - if (online) { - assert.ifError(err); - assert.strictEqual(typeof resp, 'object'); - assert(resp.blob instanceof Blob); - assert.strictEqual(typeof resp.blob.id, 'string'); - assert(UInt256.from_json(resp.blob.id).is_valid()); - assert.strictEqual(typeof resp.blob.key, 'string'); - assert(UInt256.from_json(resp.blob.key).is_valid()); - assert.strictEqual(typeof resp.username, 'string'); - assert.strictEqual(typeof resp.verified, 'boolean'); - } else { - assert(err instanceof Error); - assert.strictEqual(resp, void(0)); - } - - 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.blob.url, exampleData.id, exampleData.crypt, exampleData.device_id, function(err, resp) { - assert.ifError(err); - assert.strictEqual(typeof resp, 'object'); - assert(resp.blob instanceof Blob); - done(); - }); - }); - }); - - 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.blob.data.encrypted_secret, function(err, resp) { - if (online) { - assert.ifError(err); - assert.strictEqual(typeof resp, 'object'); - assert.strictEqual(typeof resp.keys, 'object'); - assert.strictEqual(typeof resp.keys.unlock, 'string'); - assert(UInt256.from_json(resp.keys.unlock).is_valid()); - } else { - assert.strictEqual(err.toString(), 'CORRUPT: ccm: tag doesn\'t match'); - assert.strictEqual(resp, void(0)); - } - - done(); - }); - }); - }); - - 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, exampleData.device_id, function(err, resp) { - if (online) { - assert.ifError(err); - assert.strictEqual(typeof resp, 'object'); - assert(resp.blob instanceof Blob); - assert.strictEqual(typeof resp.blob.id, 'string'); - assert(UInt256.from_json(resp.blob.id).is_valid()); - assert.strictEqual(typeof resp.blob.key, 'string'); - assert(UInt256.from_json(resp.blob.key).is_valid()); - assert.strictEqual(typeof resp.unlock, 'string'); - assert(UInt256.from_json(resp.unlock).is_valid()); - assert.strictEqual(typeof resp.secret, 'string'); - assert.strictEqual(typeof resp.username, 'string'); - assert.strictEqual(typeof resp.verified, 'boolean'); - } else { - assert(err instanceof Error); - assert.strictEqual(resp, void(0)); - } - done(); - }); - }); - }); - - describe('#register', function () { - it('should create a new blob', function (done) { - this.timeout(10000); - var options = { - username : exampleData.username, - password : exampleData.password, - email : exampleData.blob.data.email, - activateLink : exampleData.activateLink - } - - client.register(options, function(err, resp) { - - //fails, user already exists - assert(err instanceof Error); - assert.strictEqual(resp, void(0)); - done(); - }); - }); - }); - - - describe('#deleteBlob', function () { - it('should remove an existing blob', function (done) { - this.timeout(10000); - - var options = { - url : exampleData.blob.url, - blob_id : exampleData.blob.id, - username : online ? "" : exampleData.username, - account_id : exampleData.blob.data.account_id, - masterkey : exampleData.masterkey - } - - client.deleteBlob(options, function(err, resp) { - if (online) { - //removing the username will result in an error from the server - assert(err instanceof Error); - assert.strictEqual(resp, void(0)); - } else { - assert.ifError(err); - assert.strictEqual(typeof resp, 'object'); - assert.strictEqual(typeof resp.result, 'string'); - } - done(); - }); - }); - }); - -/* - describe('#updateProfile', function () { - it('should update profile parameters associated with a blob', function (done) { - this.timeout(10000); - - var options = { - url : exampleData.blob.url, - blob_id : exampleData.blob.id, - username : exampleData.username, - auth_secret : exampleData.blob.data.auth_secret, - profile : { - city : "San Francisco", - phone : "555-555-5555" - } - } - - client.updateProfile(options, function(err, resp) { - assert.ifError(err); - assert.strictEqual(typeof resp, 'object'); - assert.strictEqual(typeof resp.result, 'string'); - done(); - }); - }); - }); -*/ - -}); - - -describe('Blob', function () { - var client; - var resp; - - client = new VaultClient({ domain: exampleData.domain }); - - before(function(done) { - if (online) { - this.timeout(10000); - - client.login(exampleData.username, exampleData.password, exampleData.device_id, function(err, res) { - resp = res; - blob = res.blob; - done(); - }); - } else { - - mockBlob.filteringPath(/(blob\/.+)/g, 'blob/') - .persist() - .post('/v1/blob/') - .reply(200, {result:'success'}, { - 'Content-Type': 'application/json' - }); - - done(); - } - }); - - describe('#rename', function () { - it('should change the username of a blob', function (done) { - this.timeout(20000); - - var options = { - username : exampleData.username, - new_username : exampleData.new_username, - password : exampleData.password, - masterkey : exampleData.masterkey, - blob : blob - } - - - client.rename(options, function(err, resp) { - assert.ifError(err); - assert.strictEqual(typeof resp, 'object'); - assert.strictEqual(typeof resp.result, 'string'); - assert.strictEqual(typeof resp.message, 'string'); - - if (online) { - options.username = exampleData.new_username; - options.new_username = exampleData.username; - - //change it back - client.rename(options, function(err,resp){ - assert.ifError(err); - assert.strictEqual(typeof resp, 'object'); - assert.strictEqual(typeof resp.result, 'string'); - assert.strictEqual(typeof resp.message, 'string'); - done(); - }); - - } else { - done(); - } - }); - }); - }); - - describe('#changePassword', function () { - it('should change the password and keys of a blob', function (done) { - this.timeout(10000); - - var options = { - username : exampleData.username, - password : exampleData.password, - masterkey : exampleData.masterkey, - blob : blob - } - - client.changePassword(options, function(err, resp) { - assert.ifError(err); - assert.strictEqual(typeof resp, 'object'); - assert.strictEqual(typeof resp.result, 'string'); - assert.strictEqual(typeof resp.message, 'string'); - done(); - }); - }); - }); - - describe('#recoverBlob', function () { - it('should recover the blob given a username and secret', function (done) { - this.timeout(10000); - - var options = { - url : exampleData.blob.url, - username : exampleData.username, - masterkey : exampleData.masterkey, - } - - client.recoverBlob(options, function(err, blob) { - assert.ifError(err); - assert(blob instanceof Blob); - done(); - }); - }); - }); - - describe('#verifyEmail', function () { - it('should verify an email given a username and token', function (done) { - this.timeout(10000); - - client.verify(exampleData.username, exampleData.email_token, function(err, resp) { - //result will be error, because of invalid token - assert(err instanceof Error); - assert.strictEqual(resp, void(0)); - done(); - }); - }); - }); - - describe('#resendVerifcationEmail', function () { - it('should resend a verification given options', function (done) { - this.timeout(10000); - - var options = { - url : exampleData.blob.url, - id : exampleData.blob.id, - username : exampleData.username, - account_id : exampleData.blob.data.account_id, - email : exampleData.blob.data.email, - activateLink : exampleData.activateLink, - masterkey : exampleData.masterkey - } - client.resendEmail(options, function(err, resp) { - assert.ifError(err); - assert.strictEqual(typeof resp, 'object'); - assert.strictEqual(typeof resp.result, 'string'); - done(); - }); - }); - }); - - it('#set', function(done) { - this.timeout(10000) - blob.extend('/testObject', { - foo: [], - }, function(err, resp) { - - assert.ifError(err); - assert.strictEqual(resp.result, 'success'); - done(); - }); - }); - - it('#extend', function(done) { - this.timeout(10000) - blob.extend('/testObject', { - foobar: 'baz', - }, function(err, resp){ - assert.ifError(err); - assert.strictEqual(resp.result, 'success'); - done(); - }); - }); - - it('#unset', function(done) { - this.timeout(10000) - blob.unset('/testObject', function(err, resp){ - assert.ifError(err); - assert.strictEqual(resp.result, 'success'); - done(); - }); - }); - - it('#unshift', function(done) { - this.timeout(10000) - blob.unshift('/testArray', { - name: 'bob', - address: '1234' - }, function(err, resp){ - assert.ifError(err); - assert.strictEqual(resp.result, 'success'); - done(); - }); - }); - - it('#filter', function(done) { - this.timeout(10000) - - blob.filter('/testArray', 'name', 'bob', 'extend', '', {description:'Alice'}, function(err, resp){ - assert.ifError(err); - assert.strictEqual(resp.result, 'success'); - done(); - }); - }); - - it('#consolidate', function(done) { - this.timeout(10000) - blob.unset('/testArray', function(err, resp){ - assert.ifError(err); - assert.strictEqual(resp.result, 'success'); - blob.consolidate(function(err, resp){ - assert.ifError(err); - assert.strictEqual(resp.result, 'success'); - done(); - }); - }); - }); - - describe('identity', function() { - it('#identity_set', function (done) { - this.timeout(10000); - - blob.identity.set('address', exampleData.unlock, {city:"San Francisco", region:"CA"}, function (err, resp) { - assert.ifError(err); - assert.strictEqual(resp.result, 'success'); - done(); - }); - }); - - it('#identity_get', function () { - var property = blob.identity.get('address', exampleData.unlock); - assert.ifError(property.error); - assert.strictEqual(typeof property.encrypted, 'boolean'); - assert.notEqual(typeof property.value, 'undefined'); - }); - - it('#identity_getAll', function () { - var obj = blob.identity.getAll(exampleData.unlock); - assert.strictEqual(typeof obj, 'object'); - }); - - it('#identity_getFullAddress', function () { - var address = blob.identity.getFullAddress(exampleData.unlock); - assert.strictEqual(typeof address, 'string'); - }); - - it('#identity_unset', function (done) { - this.timeout(10000); - - blob.identity.unset('name', exampleData.unlock, function (err, resp) { - assert.ifError(err); - assert.strictEqual(resp.result, 'success'); - done(); - }); - }); - }); - - describe('identityVault', function() { - it('#identity - Get Attestation', function (done) { - var options = { - url : blob.url, - auth_secret : blob.data.auth_secret, - blob_id : blob.id, - }; - - options.type = 'identity'; - - nock('https://id.staging.ripple.com') - .filteringPath(/(v1\/attestation\/identity(.+))/g, '') - .post('/') - .reply(200, { - result: 'success', - status: 'verified', - attestation: 'eyJ6IjoieiJ9.eyJ6IjoieiJ9.sig', - blinded:'eyJ6IjoieiJ9.eyJ6IjoieiJ9.sig' - }, {'Content-Type': 'application/json'}); - - client.getAttestation(options, function(err, resp) { - assert.ifError(err); - assert.strictEqual(resp.result, 'success'); - assert.strictEqual(typeof resp.attestation, 'string'); - assert.strictEqual(typeof resp.blinded, 'string'); - assert.deepEqual(resp.decoded, {"header":{"z":"z"},"payload":{"z":"z"},"signature":"sig"}) - done(); - }); - }); - - it('#identity - Update Attestation', function (done) { - - var options = { - url : blob.url, - auth_secret : blob.data.auth_secret, - blob_id : blob.id, - }; - - options.type = 'identity'; - - nock('https://id.staging.ripple.com') - .filteringPath(/(v1\/attestation\/identity\/update(.+))/g, '') - .post('/') - .reply(200, { - result: 'success', - status: 'verified', - attestation: 'eyJ6IjoieiJ9.eyJ6IjoieiJ9.sig', - blinded:'eyJ6IjoieiJ9.eyJ6IjoieiJ9.sig' - }, {'Content-Type': 'application/json'}); - - client.updateAttestation(options, function(err, resp) { - assert.ifError(err); - assert.strictEqual(resp.result, 'success'); - assert.strictEqual(typeof resp.attestation, 'string'); - assert.strictEqual(typeof resp.blinded, 'string'); - assert.deepEqual(resp.decoded, {"header":{"z":"z"},"payload":{"z":"z"},"signature":"sig"}) - done(); - }); - }); - - it('#identity - Get Attestation Summary', function (done) { - - var options = { - url : blob.url, - auth_secret : blob.data.auth_secret, - blob_id : blob.id, - }; - - nock('https://id.staging.ripple.com') - .filteringPath(/(v1\/attestation\/summary(.+))/g, '') - .get('/') - .reply(200, { - result: 'success', - attestation: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjY2ZGI3MzgxIn0%3D.eyJwcm9maWxlX3ZlcmlmaWVkIjpmYWxzZSwiaWRlbnRpdHlfdmVyaWZpZWQiOmZhbHNlLCJpc3MiOiJodHRwczovL2lkLnJpcHBsZS5jb20iLCJzdWIiOiIwNDMzNTA0ZS0yYTRmLTQ1NjktODQwMi1lYWI2YTU0YTgzYjUiLCJleHAiOjE0MTI4MTc2NjksImlhdCI6MTQxMjgxNTgwOX0%3D.Jt14Y2TsM7fKqGWn0j16cPldlYqRr7%2F2dptBsdZuZhRGRTREO4TSpZZhBaU95WL3M9eXIfaoSs8f2pTOa%2BBGAYHZSZK4%2FLqeWdDH8zz8Bx9YFqGije1KmHQR%2FeoWSp1GTEfcq5Oho4nSHozHhGNN8IrDkl8woMvWb%2FE1938Y5Zl2vyv7wjlNUF4ND33XWzJkvQjzIK15uYfaB%2FUIsNW32udfHAdkigesdMDNm%2BRGBqHMDZeAMdVxzrDzE3m8oWKDMJXbcaLmk75COfJrLWYiZCHd7VcReyPEZegwEucetZJ9uDnoBcvw0%2B6hIRmjTN6Gy1eeBoJaiDYsWuOwInbIlw%3D%3D', - }, {'Content-Type': 'application/json'}); - - client.getAttestationSummary(options, function(err, resp) { - assert.ifError(err); - assert.strictEqual(resp.result, 'success'); - assert.strictEqual(typeof resp.attestation, 'string'); - assert.strictEqual(typeof resp.decoded.header, 'object'); - assert.strictEqual(typeof resp.decoded.payload, 'object'); - assert.strictEqual(typeof resp.decoded.signature, 'string'); - done(); - }); - }); - }); - - //only do these offline - if (!online) { - - describe('2FA', function() { - - it('#2FA_set2FA', function (done) { - blob.set2FA({masterkey:exampleData.masterkey}, function(err, resp){ - assert.ifError(err); - assert.strictEqual(typeof resp, 'object'); - assert.strictEqual(typeof resp.result, 'string'); - done(); - }); - }); - - it('#2FA_get2FA', function (done) { - blob.get2FA(function(err, resp) { - assert.ifError(err); - assert.strictEqual(typeof resp, 'object'); - assert.strictEqual(typeof resp.result, 'string'); - done(); - }); - }); - - it('#2FA_requestToken', function (done) { - client.requestToken(exampleData.blob.url, exampleData.blob.id, function(err, resp){ - assert.ifError(err); - assert.strictEqual(typeof resp, 'object'); - assert.strictEqual(typeof resp.result, 'string'); - done(); - }); - }); - - it('#2FA_verifyToken', function (done) { - var options = { - url : exampleData.blob.url, - id : exampleData.blob.id, - device_id : client.generateDeviceID(), - token : "5555", - remember_me : true - } - - client.verifyToken(options, function(err, resp){ - assert.ifError(err); - assert.strictEqual(typeof resp, 'object'); - assert.strictEqual(typeof resp.result, 'string'); - done(); - }); - }); - }); - } - - if (!online) { - after(function () { - nock.restore(); - }); - } -});