From 7be13bebfcc533fe7a3312e5629320bb469acd4c Mon Sep 17 00:00:00 2001 From: Matthew Fettig Date: Mon, 7 Jul 2014 10:21:04 -0700 Subject: [PATCH 1/4] [FEATURE] vault client: function for 2FA settings --- src/js/ripple/blob.js | 132 +++++++++++++++++++++++++++++------ src/js/ripple/vaultclient.js | 114 ++++++++++++------------------ test/vault-test.js | 9 +-- 3 files changed, 161 insertions(+), 94 deletions(-) diff --git a/src/js/ripple/blob.js b/src/js/ripple/blob.js index 656819f9..361c3554 100644 --- a/src/js/ripple/blob.js +++ b/src/js/ripple/blob.js @@ -7,12 +7,15 @@ var log = require('./log').sub('blob'); var BlobClient = {}; //Blob object class -function BlobObj(url, id, key) { - this.url = url; - this.id = id; - this.key = key; - this.identity = new Identity(this); - this.data = { }; +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 @@ -96,11 +99,16 @@ BlobObj.prototype.init = function(fn) { self.url = 'http://' + url; } - url = self.url + '/v1/blob/' + self.id; - + url = self.url + '/v1/blob/' + self.id; + url += '?' + request.get(url, function(err, resp) { - if (err || !resp.body || resp.body.result !== 'success') { + 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.result !== 'success') { + return fn(new Error('Incorrect username or password')); } self.revision = resp.body.revision; @@ -513,6 +521,50 @@ BlobObj.prototype.postUpdate = function(op, pointer, params, fn) { }); }; +/** + * set2FA + * modify 2 factor auth settings + * @params {object} options + * @params {string} + * @params {boolean} options.remember_me //remember for 30 days + * @params {boolean} options.enabled + * @params {string} options.phone + * @params {string} options.country_code + * @params {string} options.via //sms, etc + */ + +BlobObj.prototype.set2FA = function(options, fn) { + + var config = { + method : 'POST', + url : this.url + '/v1/blob/' + this.id + '/2FA', + data : { + remember_me : options.remember_me, + enabled : options.enabled, + phone : options.phone, + country_code : options.country_code, + via : options.via + } + }; + + 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(new Error(resp.body.message)); + } else { + fn(new Error('Unable to update settings.')); + } + }); +}; + /***** helper functions *****/ function normalizeSubcommands(subcommands, compress) { @@ -796,7 +848,7 @@ exports.Blob = BlobObj; * Get ripple name for a given address */ -exports.getRippleName = function(url, address, fn) { +BlobClient.getRippleName = function(url, address, fn) { if (!crypt.isValidAddress(address)) { return fn (new Error('Invalid ripple address')); } @@ -817,10 +869,15 @@ exports.getRippleName = function(url, address, fn) { /** * 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 (url, id, crypt, fn) { - var blob = new BlobObj(url, id, crypt); +BlobClient.get = function (options, fn) { + var blob = new BlobObj(options); blob.init(fn); }; @@ -842,8 +899,15 @@ BlobClient.verify = function(url, username, token, fn) { }; /** - * ResendEmail - * resend verification email + * 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) { @@ -916,9 +980,14 @@ BlobClient.recoverBlob = function (opts, fn) { }); function handleRecovery (resp) { - //decrypt crypt key - var crypt = decryptBlobCrypt(opts.masterkey, resp.body.encrypted_blobdecrypt_key); - var blob = new BlobObj(opts.url, resp.body.blob_id, crypt); + + 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; @@ -946,8 +1015,18 @@ BlobClient.recoverBlob = function (opts, fn) { /** * updateProfile - * update information stored outside the blob - */ + * update information stored outside the blob - HMAC signed + * @param {object} + * @param {string} opts.url + * @param {string} opts.username + * @param {string} opts.auth_secret + * @param {srring} opts.blob_id + * @param {object} opts.profile + * @param {string} opts.profile.phone - optional + * @param {string} opts.profile.country - optional + * @param {string} opts.profile.region - optional + * @param {string} opts.profile.city - optional + */ BlobClient.updateProfile = function (opts, fn) { var config = { @@ -1088,7 +1167,12 @@ BlobClient.rename = function (opts, fn) { */ BlobClient.create = function(options, fn) { - var blob = new BlobObj(options.url, options.id, options.crypt); + var params = { + url : options.url, + blob_id : options.id, + key : options.crypt + } + var blob = new BlobObj(params); blob.revision = 0; @@ -1144,7 +1228,13 @@ BlobClient.create = function(options, fn) { }; /** - * deleteBlob + * 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) { diff --git a/src/js/ripple/vaultclient.js b/src/js/ripple/vaultclient.js index 2c29f2fd..02600c03 100644 --- a/src/js/ripple/vaultclient.js +++ b/src/js/ripple/vaultclient.js @@ -133,7 +133,7 @@ VaultClient.prototype.exists = function(username, callback) { * @param {function} fn - Callback function */ -VaultClient.prototype.login = function(username, password, callback) { +VaultClient.prototype.login = function(username, password, device_id, callback) { var self = this; var steps = [ @@ -156,7 +156,14 @@ VaultClient.prototype.login = function(username, password, callback) { } function getBlob(authInfo, password, keys, callback) { - blobClient.get(authInfo.blobvault, keys.id, keys.crypt, function(err, blob) { + 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); } @@ -218,7 +225,7 @@ VaultClient.prototype.login = function(username, password, callback) { * @param {function} fn - Callback function */ -VaultClient.prototype.relogin = function(url, id, key, callback) { +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; @@ -228,7 +235,14 @@ VaultClient.prototype.relogin = function(url, id, key, callback) { return callback(new Error('Blob vault URL is required')); } - blobClient.get(url, id, key, function(err, blob) { + var options = { + url : url, + blob_id : id, + key : key, + device_id : device_id + }; + + blobClient.get(options, function(err, blob) { if (err) { callback(err); } else { @@ -293,7 +307,7 @@ VaultClient.prototype.unlock = function(username, password, encryptSecret, fn) { * @param {function} fn - Callback function */ -VaultClient.prototype.loginAndUnlock = function(username, password, fn) { +VaultClient.prototype.loginAndUnlock = function(username, password, device_id, fn) { var self = this; var steps = [ @@ -305,7 +319,7 @@ VaultClient.prototype.loginAndUnlock = function(username, password, fn) { async.waterfall(steps, fn); function login (callback) { - self.login(username, password, function(err, resp) { + self.login(username, password, device_id, function(err, resp) { if (err) { return callback(err); @@ -374,69 +388,6 @@ VaultClient.prototype.verify = function(username, token, callback) { }); }; -/** - * resendEmail - * send a new verification email - * @param {object} options - * @param {string} options.id - * @param {string} options.username - * @param {string} options.account_id - * @param {string} options.email - * @param {string} options.activateLink - * @param {function} fn - Callback - */ - -VaultClient.prototype.resendEmail = function (options, fn) { - blobClient.resendEmail(options, fn); -}; - -/** - * 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 - */ - -VaultClient.prototype.deleteBlob = function (options, fn) { - blobClient.deleteBlob(options, fn); -}; - -/** - * updateProfile - * update information stored outside the blob - * @param {object} - * @param {string} options.url - * @param {string} options.username - * @param {string} options.auth_secret - * @param {srring} options.blob_id - * @param {object} options.profile - * @param {string} options.profile.phone - optional - * @param {string} options.profile.country - optional - * @param {string} options.profile.region - optional - * @param {string} options.profile.city - optional - */ - -VaultClient.prototype.updateProfile = function (options, fn) { - blobClient.updateProfile(options, fn); -}; - -/** - * recoverBlob - * recover blob with account secret - * @param {object} options - * @param {string} options.url - * @param {string} options.username - * @param {string} options.masterkey - * @param {function} - */ - -VaultClient.prototype.recoverBlob = function (options, fn) { - blobClient.recoverBlob(options, fn); -}; - /* * changePassword * @param {object} options @@ -581,6 +532,11 @@ VaultClient.prototype.register = function(options, fn) { }; }; +/** + * validateUsername + * check username for validity + */ + VaultClient.prototype.validateUsername = function (username) { username = String(username).trim(); var result = { @@ -607,4 +563,24 @@ VaultClient.prototype.validateUsername = function (username) { 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.updateProfile = blobClient.updateProfile; + +VaultClient.prototype.recoverBlob = blobClient.recoverBlob; + +VaultClient.prototype.deleteBlob = blobClient.deleteBlob; + + +//export by name exports.VaultClient = VaultClient; diff --git a/test/vault-test.js b/test/vault-test.js index 7d664702..c0bd25c8 100644 --- a/test/vault-test.js +++ b/test/vault-test.js @@ -19,6 +19,7 @@ var exampleData = { masterkey : 'ssize4HrSYZShMWBtK6BhALGEk8VH', email_token : '77825040-9096-4695-9cbc-76720f6a8649', activateLink : 'https://staging.ripple.com/client/#/register/activate/', + device_id : "", blob: { url: 'https://id.staging.ripple.com', id: 'ef203d3e76552c0592384f909e6f61f1d1f02f61f07643ce015d8b0c9710dd2f', @@ -289,7 +290,7 @@ describe('VaultClient', function () { describe('#login', function() { it('with username and password should retrive the blob, crypt key, and id', function(done) { this.timeout(10000); - client.login(exampleData.username, exampleData.password, function(err, resp) { + client.login(exampleData.username, exampleData.password, exampleData.device_id, function(err, resp) { if (online) { assert.ifError(err); assert.strictEqual(typeof resp, 'object'); @@ -313,7 +314,7 @@ describe('VaultClient', function () { 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, function(err, resp) { + 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); @@ -345,7 +346,7 @@ describe('VaultClient', function () { describe('#loginAndUnlock', function () { it('should get the decrypted blob and decrypted secret given name and password', function (done) { this.timeout(10000); - client.loginAndUnlock(exampleData.username, exampleData.password, function(err, resp) { + client.loginAndUnlock(exampleData.username, exampleData.password, exampleData.device_id, function(err, resp) { if (online) { assert.ifError(err); assert.strictEqual(typeof resp, 'object'); @@ -454,7 +455,7 @@ describe('Blob', function () { if (online) { this.timeout(10000); - client.login(exampleData.username, exampleData.password, function(err, res) { + client.login(exampleData.username, exampleData.password, exampleData.device_id, function(err, res) { resp = res; blob = res.blob; done(); From 7daa4b4c3b6267b9b30cab34fbb4f9d81ac2a0fd Mon Sep 17 00:00:00 2001 From: Matthew Fettig Date: Mon, 7 Jul 2014 13:33:43 -0700 Subject: [PATCH 2/4] [FEATURE] vault client: request and verify 2FA tokens --- src/js/ripple/blob.js | 58 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/src/js/ripple/blob.js b/src/js/ripple/blob.js index 361c3554..80eada46 100644 --- a/src/js/ripple/blob.js +++ b/src/js/ripple/blob.js @@ -100,13 +100,15 @@ BlobObj.prototype.init = function(fn) { } url = self.url + '/v1/blob/' + self.id; - url += '?' + 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')); + + //if (failed 2fa) handle response } else if (resp.body.result !== 'success') { return fn(new Error('Incorrect username or password')); } @@ -565,6 +567,60 @@ BlobObj.prototype.set2FA = function(options, fn) { }); }; +/** + * requestToken + * request new token to be sent for 2FA + */ + +BlobObj.prototype.requestToken = function (fn) { + var config = { + method : 'POST', + url : this.url + '/v1/blob/' + this.id + '/2FA/requestToken' + }; + + request.post(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 + */ +BlobObj.prototype.verifyToken = function (device_id, token, fn) { + var config = { + method : 'POST', + url : this.url + '/v1/blob/' + this.id + '/2FA/verifyToken', + data : { + device_id : device_id, + token : token + } + }; + + 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.')); + } + }); +}; + /***** helper functions *****/ function normalizeSubcommands(subcommands, compress) { From 4e9082f4d9996534904ed2186c55c6b72346769a Mon Sep 17 00:00:00 2001 From: Matthew Fettig Date: Mon, 7 Jul 2014 16:55:02 -0700 Subject: [PATCH 3/4] [FEATURE] vault client: get 2FA settings --- src/js/ripple/blob.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/js/ripple/blob.js b/src/js/ripple/blob.js index 80eada46..9bbca28f 100644 --- a/src/js/ripple/blob.js +++ b/src/js/ripple/blob.js @@ -523,6 +523,33 @@ BlobObj.prototype.postUpdate = function(op, pointer, params, fn) { }); }; +/** + * get2FA - ECDSA signed request + */ + +BlobObj.prototype.get2FA = function (masterkey, 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.signAsymmetric(masterkey, this.data.account_id, 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 @@ -596,6 +623,7 @@ BlobObj.prototype.requestToken = function (fn) { * verifyToken * verify a device token for 2FA */ + BlobObj.prototype.verifyToken = function (device_id, token, fn) { var config = { method : 'POST', From bdb74547375a2f206af3280f55f2526e948e0920 Mon Sep 17 00:00:00 2001 From: Matthew Fettig Date: Mon, 14 Jul 2014 09:21:02 -0700 Subject: [PATCH 4/4] [CHORE] move request/verifyToken to blob client --- src/js/ripple/blob.js | 136 +++++++++++++++++++---------------- src/js/ripple/vaultclient.js | 3 + test/vault-test.js | 66 +++++++++++++++-- 3 files changed, 136 insertions(+), 69 deletions(-) diff --git a/src/js/ripple/blob.js b/src/js/ripple/blob.js index 9bbca28f..489c8a18 100644 --- a/src/js/ripple/blob.js +++ b/src/js/ripple/blob.js @@ -107,8 +107,12 @@ BlobObj.prototype.init = function(fn) { return fn(new Error(err.message || 'Could not retrieve blob')); } else if (!resp.body) { return fn(new Error('Could not retrieve blob')); - - //if (failed 2fa) handle response + } 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')); } @@ -507,7 +511,6 @@ BlobObj.prototype.postUpdate = function(op, pointer, params, fn) { var signedRequest = new SignedRequest(config); - var signed = signedRequest.signHmac(this.data.auth_secret, this.id); request.post(signed.url) @@ -532,7 +535,7 @@ BlobObj.prototype.get2FA = function (masterkey, fn) { method : 'GET', url : this.url + '/v1/blob/' + this.id + '/2FA?device_id=' + this.device_id, }; - + var signedRequest = new SignedRequest(config); var signed = signedRequest.signAsymmetric(masterkey, this.data.account_id, this.id); @@ -554,8 +557,7 @@ BlobObj.prototype.get2FA = function (masterkey, fn) { * set2FA * modify 2 factor auth settings * @params {object} options - * @params {string} - * @params {boolean} options.remember_me //remember for 30 days + * @params {string} options.masterkey * @params {boolean} options.enabled * @params {string} options.phone * @params {string} options.country_code @@ -568,7 +570,6 @@ BlobObj.prototype.set2FA = function(options, fn) { method : 'POST', url : this.url + '/v1/blob/' + this.id + '/2FA', data : { - remember_me : options.remember_me, enabled : options.enabled, phone : options.phone, country_code : options.country_code, @@ -587,68 +588,13 @@ BlobObj.prototype.set2FA = function(options, fn) { } 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)); + fn(resp.body); } else { fn(new Error('Unable to update settings.')); } }); }; -/** - * requestToken - * request new token to be sent for 2FA - */ - -BlobObj.prototype.requestToken = function (fn) { - var config = { - method : 'POST', - url : this.url + '/v1/blob/' + this.id + '/2FA/requestToken' - }; - - request.post(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 - */ - -BlobObj.prototype.verifyToken = function (device_id, token, fn) { - var config = { - method : 'POST', - url : this.url + '/v1/blob/' + this.id + '/2FA/verifyToken', - data : { - device_id : device_id, - token : token - } - }; - - 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.')); - } - }); -}; - /***** helper functions *****/ function normalizeSubcommands(subcommands, compress) { @@ -965,6 +911,70 @@ BlobClient.get = function (options, fn) { blob.init(fn); }; +/** + * requestToken + * request new token to be sent for 2FA + * @param {string} url + * @param {string} id + */ + +BlobClient.requestToken = function (url, id, fn) { + var config = { + method : 'GET', + url : url + '/v1/blob/' + id + '/2FA/requestToken' + }; + + 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 */ diff --git a/src/js/ripple/vaultclient.js b/src/js/ripple/vaultclient.js index 02600c03..ec7bc650 100644 --- a/src/js/ripple/vaultclient.js +++ b/src/js/ripple/vaultclient.js @@ -581,6 +581,9 @@ VaultClient.prototype.recoverBlob = blobClient.recoverBlob; VaultClient.prototype.deleteBlob = blobClient.deleteBlob; +VaultClient.prototype.requestToken = blobClient.requestToken; + +VaultClient.prototype.verifyToken = blobClient.verifyToken; //export by name exports.VaultClient = VaultClient; diff --git a/test/vault-test.js b/test/vault-test.js index c0bd25c8..51ad91f1 100644 --- a/test/vault-test.js +++ b/test/vault-test.js @@ -19,7 +19,7 @@ var exampleData = { masterkey : 'ssize4HrSYZShMWBtK6BhALGEk8VH', email_token : '77825040-9096-4695-9cbc-76720f6a8649', activateLink : 'https://staging.ripple.com/client/#/register/activate/', - device_id : "", + device_id : "ac1b6f6dbca98190eb9687ba06f0e066", blob: { url: 'https://id.staging.ripple.com', id: 'ef203d3e76552c0592384f909e6f61f1d1f02f61f07643ce015d8b0c9710dd2f', @@ -105,11 +105,12 @@ var recoverRes = { } var blob = new Blob(); - blob.url = exampleData.blob.url; - blob.id = exampleData.blob.id; - blob.key = exampleData.blob.key; - blob.data = exampleData.blob.data; - blob.revision = exampleData.blob.data.revision; + blob.url = exampleData.blob.url; + blob.id = exampleData.blob.id; + blob.device_id = exampleData.device_id; + blob.key = exampleData.blob.key; + 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'; @@ -178,6 +179,7 @@ if (!online) { }); mockBlob.filteringPath(/(blob\/.+)/g, 'blob/') + .persist() .get('/v1/blob/') .reply(200, JSON.stringify(blobRes.body), { 'Content-Type': 'application/json' @@ -463,6 +465,7 @@ describe('Blob', function () { } else { mockBlob.filteringPath(/(blob\/.+)/g, 'blob/') + .persist() .post('/v1/blob/') .reply(200, {result:'success'}, { 'Content-Type': 'application/json' @@ -691,6 +694,57 @@ describe('Blob', function () { }); }); + //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(exampleData.masterkey, 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();