From bdfa83592bd05efc67cda6e440a29ceae7419bf4 Mon Sep 17 00:00:00 2001 From: Matthew Fettig Date: Wed, 20 Aug 2014 14:56:29 -0700 Subject: [PATCH 1/6] [FEATURE] identity functions --- src/js/ripple/blob.js | 204 +++++++++++++++++++++++++++-------- src/js/ripple/vaultclient.js | 10 +- 2 files changed, 170 insertions(+), 44 deletions(-) diff --git a/src/js/ripple/blob.js b/src/js/ripple/blob.js index bb2bb39f..79edff8d 100644 --- a/src/js/ripple/blob.js +++ b/src/js/ripple/blob.js @@ -119,7 +119,9 @@ BlobObj.prototype.init = function(fn) { 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')); @@ -1115,48 +1117,6 @@ BlobClient.recoverBlob = function (opts, fn) { }; }; -/** - * updateProfile - * 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 = { - method: 'POST', - url: opts.url + '/v1/user/' + opts.username + '/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); - } else { - fn(new Error('Failed to update profile')); - } - }); - -}; /** * updateKeys @@ -1320,6 +1280,7 @@ BlobClient.create = function(options, fn) { 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)); @@ -1363,4 +1324,163 @@ BlobClient.deleteBlob = function(options, fn) { }); }; + +/*** 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 {srring} opts.identity_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/' + opts.identity_id, + 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); + } 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 + * @param {srring} opts.identity_id + */ +BlobClient.getProfile = function (opts, fn) { + var config = { + method: 'GET', + url: opts.url + '/v1/profile/' + opts.identity_id, + }; + + 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); + } else { + fn(new Error('Failed to get profile')); + } + }); +}; + +/** + * getAttestations + * @param {Object} opts + * @param {string} opts.url + * @param {string} opts.auth_secret + * @param {srring} opts.blob_id + * @param {srring} opts.identity_id + */ +BlobClient.getAttestations = function (opts, fn) { + var config = { + method: 'GET', + url: opts.url + '/v1/profile/' + opts.identity_id + '/attestations', + }; + + 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('getAttestations:', err); + fn(new Error('Failed to get attestations - XHR error')); + } else if (resp.body && resp.body.result === 'success') { + fn(null, resp.body); + } else if (resp.body) { + log.error('getAttestations:', resp.body); + } else { + fn(new Error('Failed to get attestations')); + } + }); +}; + +/** + * attest + * @param {Object} opts + * @param {string} opts.url + * @param {string} opts.auth_secret + * @param {srring} opts.blob_id + * @param {srring} opts.identity_id + * @param {srring} opts.type (email,phone,basic_identity) + */ +BlobClient.attest = function (opts, fn) { + var config = { + method: 'POST', + url: opts.url + '/v1/profile/' + opts.identity_id + '/attest', + 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) { + console.log(err, resp); + + if (err) { + log.error('attest:', err); + fn(new Error('Failed to attest - XHR error')); + } else if (resp.body && resp.body.result === 'success') { + fn(null, resp.body); + } else if (resp.body) { + log.error('attest:', resp.body); + } else { + fn(new Error('Failed to attest')); + } + }); +}; + exports.BlobClient = BlobClient; diff --git a/src/js/ripple/vaultclient.js b/src/js/ripple/vaultclient.js index ec7bc650..9c3c5936 100644 --- a/src/js/ripple/vaultclient.js +++ b/src/js/ripple/vaultclient.js @@ -575,8 +575,6 @@ VaultClient.prototype.generateDeviceID = function () { VaultClient.prototype.resendEmail = blobClient.resendEmail; -VaultClient.prototype.updateProfile = blobClient.updateProfile; - VaultClient.prototype.recoverBlob = blobClient.recoverBlob; VaultClient.prototype.deleteBlob = blobClient.deleteBlob; @@ -585,5 +583,13 @@ VaultClient.prototype.requestToken = blobClient.requestToken; VaultClient.prototype.verifyToken = blobClient.verifyToken; +VaultClient.prototype.updateProfile = blobClient.updateProfile; + +VaultClient.prototype.getProfile = blobClient.getProfile; + +VaultClient.prototype.attest = blobClient.attest; + +VaultClient.prototype.getAttestations = blobClient.getAttestations; + //export by name exports.VaultClient = VaultClient; From d5e32db95438601f0cc0b5c2f5e49cfc0a43265a Mon Sep 17 00:00:00 2001 From: Matthew Fettig Date: Fri, 22 Aug 2014 11:37:25 -0700 Subject: [PATCH 2/6] [CHORE] vault client: add type to attest parameters --- src/js/ripple/blob.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/js/ripple/blob.js b/src/js/ripple/blob.js index 79edff8d..9c93ac00 100644 --- a/src/js/ripple/blob.js +++ b/src/js/ripple/blob.js @@ -1459,7 +1459,7 @@ BlobClient.attest = function (opts, fn) { method: 'POST', url: opts.url + '/v1/profile/' + opts.identity_id + '/attest', dataType: 'json', - data: opts.profile + data: {type:opts.type} }; var signedRequest = new SignedRequest(config); @@ -1468,7 +1468,6 @@ BlobClient.attest = function (opts, fn) { request.post(signed.url) .send(signed.data) .end(function(err, resp) { - console.log(err, resp); if (err) { log.error('attest:', err); From dce15bc5793c3d8e3cd6195b1e1e122f44b8c6bd Mon Sep 17 00:00:00 2001 From: Matthew Fettig Date: Wed, 3 Sep 2014 17:17:31 -0700 Subject: [PATCH 3/6] [TASK] vault client: add parameters to attest --- src/js/ripple/blob.js | 31 +++++++++++++++++------ test/vault-test.js | 58 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 80 insertions(+), 9 deletions(-) diff --git a/src/js/ripple/blob.js b/src/js/ripple/blob.js index 9c93ac00..775685a5 100644 --- a/src/js/ripple/blob.js +++ b/src/js/ripple/blob.js @@ -563,7 +563,6 @@ BlobObj.prototype.get2FA = function (fn) { * @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) { @@ -574,8 +573,7 @@ BlobObj.prototype.set2FA = function(options, fn) { data : { enabled : options.enabled, phone : options.phone, - country_code : options.country_code, - via : options.via + country_code : options.country_code } }; @@ -1371,6 +1369,7 @@ BlobClient.updateProfile = function (opts, fn) { 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')); } @@ -1405,6 +1404,7 @@ BlobClient.getProfile = function (opts, fn) { 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')); } @@ -1439,6 +1439,7 @@ BlobClient.getAttestations = function (opts, fn) { fn(null, resp.body); } else if (resp.body) { log.error('getAttestations:', resp.body); + fn(new Error('Failed to get attestations')); } else { fn(new Error('Failed to get attestations')); } @@ -1450,16 +1451,31 @@ BlobClient.getAttestations = function (opts, fn) { * @param {Object} opts * @param {string} opts.url * @param {string} opts.auth_secret - * @param {srring} opts.blob_id - * @param {srring} opts.identity_id - * @param {srring} opts.type (email,phone,basic_identity) + * @param {string} opts.blob_id + * @param {string} opts.identity_id + * @param {string} opts.type (email,phone,basic_identity) + * @param {object} opts.phone (required for type 'phone') + * @param {object} opts.identity (optional) + * @param {string} opts.email (required for type 'email') + * @param {string} opts.attestation_id (required for completing email or phone attestations) + * @param {string} opts.token (required for completing email or phone attestations) */ BlobClient.attest = function (opts, fn) { + var params = { + type: opts.type + }; + + if (opts.phone) params.phone = opts.phone; + if (opts.identity) params.identity = opts.identity; + if (opts.email) params.email = opts.email; + if (opts.attestation_id) params.attestation_id = opts.attestation_id; + if (opts.token) params.token = opts.token; + var config = { method: 'POST', url: opts.url + '/v1/profile/' + opts.identity_id + '/attest', dataType: 'json', - data: {type:opts.type} + data: params }; var signedRequest = new SignedRequest(config); @@ -1476,6 +1492,7 @@ BlobClient.attest = function (opts, fn) { fn(null, resp.body); } else if (resp.body) { log.error('attest:', resp.body); + fn(new Error('Failed to get attest')); } else { fn(new Error('Failed to attest')); } diff --git a/test/vault-test.js b/test/vault-test.js index 382d6116..5bd85e6c 100644 --- a/test/vault-test.js +++ b/test/vault-test.js @@ -20,6 +20,7 @@ var exampleData = { 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', @@ -103,12 +104,29 @@ var recoverRes = { 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; @@ -693,7 +711,43 @@ describe('Blob', function () { }); }); }); - + + describe('identityVault', function() { + it('#identity-getProfile', function (done) { + var options = { + url : blob.url, + auth_secret : blob.data.auth_secret, + blob_id :blob.id, + identity_id : blob.identity_id, + }; + + options.profile = { + attributes : [{ + name : 'email', + value : 'example@example.com', + visibility : 'public' + }] + }; + + client.getProfile(options, function(err, resp) { + console.log(err, resp); + done(); + }); + }); + + it('#identity-updateProfile', function (done) { + done(); + }); + + it('#identity-getAttestations', function (done) { + done(); + }); + + it('#identity-attest', function (done) { + done(); + }); + }); + //only do these offline if (!online) { From 5e7af2fba41f737122a85601d548b2c5529d66de Mon Sep 17 00:00:00 2001 From: Matthew Fettig Date: Wed, 10 Sep 2014 14:19:59 -0700 Subject: [PATCH 4/6] [TASK] switch to new attestation endpoint --- src/js/ripple/blob.js | 84 ++++++++++++++++++++++++++++++------ src/js/ripple/vaultclient.js | 2 + 2 files changed, 72 insertions(+), 14 deletions(-) diff --git a/src/js/ripple/blob.js b/src/js/ripple/blob.js index 775685a5..dffbd82a 100644 --- a/src/js/ripple/blob.js +++ b/src/js/ripple/blob.js @@ -1332,7 +1332,6 @@ BlobClient.deleteBlob = function(options, fn) { * @param {string} opts.url * @param {string} opts.auth_secret * @param {srring} opts.blob_id - * @param {srring} opts.identity_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) @@ -1350,7 +1349,7 @@ BlobClient.deleteBlob = function(options, fn) { BlobClient.updateProfile = function (opts, fn) { var config = { method: 'POST', - url: opts.url + '/v1/profile/' + opts.identity_id, + url: opts.url + '/v1/profile/', dataType: 'json', data: opts.profile }; @@ -1382,12 +1381,11 @@ BlobClient.updateProfile = function (opts, fn) { * @param {string} opts.url * @param {string} opts.auth_secret * @param {srring} opts.blob_id - * @param {srring} opts.identity_id */ BlobClient.getProfile = function (opts, fn) { var config = { method: 'GET', - url: opts.url + '/v1/profile/' + opts.identity_id, + url: opts.url + '/v1/profile/' }; var signedRequest = new SignedRequest(config); @@ -1417,12 +1415,11 @@ BlobClient.getProfile = function (opts, fn) { * @param {string} opts.url * @param {string} opts.auth_secret * @param {srring} opts.blob_id - * @param {srring} opts.identity_id */ BlobClient.getAttestations = function (opts, fn) { var config = { method: 'GET', - url: opts.url + '/v1/profile/' + opts.identity_id + '/attestations', + url: opts.url + '/v1/profile/attestations', }; var signedRequest = new SignedRequest(config); @@ -1452,7 +1449,6 @@ BlobClient.getAttestations = function (opts, fn) { * @param {string} opts.url * @param {string} opts.auth_secret * @param {string} opts.blob_id - * @param {string} opts.identity_id * @param {string} opts.type (email,phone,basic_identity) * @param {object} opts.phone (required for type 'phone') * @param {object} opts.identity (optional) @@ -1460,20 +1456,26 @@ BlobClient.getAttestations = function (opts, fn) { * @param {string} opts.attestation_id (required for completing email or phone attestations) * @param {string} opts.token (required for completing email or phone attestations) */ +/* BlobClient.attest = function (opts, fn) { var params = { type: opts.type }; - if (opts.phone) params.phone = opts.phone; - if (opts.identity) params.identity = opts.identity; - if (opts.email) params.email = opts.email; - if (opts.attestation_id) params.attestation_id = opts.attestation_id; - if (opts.token) params.token = opts.token; + console.log(opts); + if (opts.phone) params.phone = opts.phone; + if (opts.profile) params.profile = opts.profile; + if (opts.email) params.email = opts.email; + if (opts.attestation_id) params.attestation_id = opts.attestation_id; + if (opts.verification_id) params.verification_id = opts.verification_id; + if (opts.token) params.token = opts.token; + if (opts.questions_id) params.questions_id = opts.questions_id; + if (opts.answers) params.answers = opts.answers; + var config = { method: 'POST', - url: opts.url + '/v1/profile/' + opts.identity_id + '/attest', + url: opts.url + '/v1/profile/attest', dataType: 'json', data: params }; @@ -1492,11 +1494,65 @@ BlobClient.attest = function (opts, fn) { fn(null, resp.body); } else if (resp.body) { log.error('attest:', resp.body); - fn(new Error('Failed to get attest')); + fn(new Error('Failed to attest: ' + resp.body.message || "")); } else { fn(new Error('Failed to attest')); } }); }; +*/ + +/** + * attestation + * @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 {object} opts.identity (optional) + * @param {string} opts.email (required for type 'email') + * @param {string} opts.attestation_id (required for completing email or phone attestations) + * @param {string} opts.token (required for completing email or phone attestations) + */ +BlobClient.attestation = 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.attestation_id) params.attestation_id = opts.attestation_id; + if (opts.verification_id) params.verification_id = opts.verification_id; + if (opts.token) params.token = opts.token; + if (opts.questions_id) params.questions_id = opts.questions_id; + if (opts.answers) params.answers = opts.answers; + + var config = { + method: 'POST', + url: opts.url + '/v1/profile/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') { + 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')); + } + }); +}; exports.BlobClient = BlobClient; diff --git a/src/js/ripple/vaultclient.js b/src/js/ripple/vaultclient.js index 9c3c5936..f2e12d1f 100644 --- a/src/js/ripple/vaultclient.js +++ b/src/js/ripple/vaultclient.js @@ -589,6 +589,8 @@ VaultClient.prototype.getProfile = blobClient.getProfile; VaultClient.prototype.attest = blobClient.attest; +VaultClient.prototype.attestation = blobClient.attestation; + VaultClient.prototype.getAttestations = blobClient.getAttestations; //export by name From 327c35252f7c5642011beb0f989b507627f2726b Mon Sep 17 00:00:00 2001 From: Matthew Fettig Date: Fri, 3 Oct 2014 17:16:41 -0700 Subject: [PATCH 5/6] [FEATURE] vault client: update attestation and attestation summary --- src/js/ripple/blob.js | 172 +++++++++++++++++++---------------- src/js/ripple/vaultclient.js | 10 +- 2 files changed, 99 insertions(+), 83 deletions(-) diff --git a/src/js/ripple/blob.js b/src/js/ripple/blob.js index dffbd82a..c8dc4d10 100644 --- a/src/js/ripple/blob.js +++ b/src/js/ripple/blob.js @@ -1343,7 +1343,6 @@ BlobClient.deleteBlob = function(options, fn) { * @param {string} attribute.domain ... corresponding domain * @param {string} attribute.status ... “current”, “removed”, etc. * @param {string} attribute.visibitlity ... “public”, ”private” - */ BlobClient.updateProfile = function (opts, fn) { @@ -1410,76 +1409,28 @@ BlobClient.getProfile = function (opts, fn) { }; /** - * getAttestations - * @param {Object} opts - * @param {string} opts.url - * @param {string} opts.auth_secret - * @param {srring} opts.blob_id - */ -BlobClient.getAttestations = function (opts, fn) { - var config = { - method: 'GET', - url: opts.url + '/v1/profile/attestations', - }; - - 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('getAttestations:', err); - fn(new Error('Failed to get attestations - XHR error')); - } else if (resp.body && resp.body.result === 'success') { - fn(null, resp.body); - } else if (resp.body) { - log.error('getAttestations:', resp.body); - fn(new Error('Failed to get attestations')); - } else { - fn(new Error('Failed to get attestations')); - } - }); -}; - -/** - * attest + * 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 {object} opts.identity (optional) * @param {string} opts.email (required for type 'email') - * @param {string} opts.attestation_id (required for completing email or phone attestations) - * @param {string} opts.token (required for completing email or phone attestations) */ -/* -BlobClient.attest = function (opts, fn) { - var params = { - type: opts.type - }; +BlobClient.getAttestation = function (opts, fn) { + var params = { }; - console.log(opts); - - if (opts.phone) params.phone = opts.phone; - if (opts.profile) params.profile = opts.profile; - if (opts.email) params.email = opts.email; - if (opts.attestation_id) params.attestation_id = opts.attestation_id; - if (opts.verification_id) params.verification_id = opts.verification_id; - if (opts.token) params.token = opts.token; - if (opts.questions_id) params.questions_id = opts.questions_id; - if (opts.answers) params.answers = opts.answers; + if (opts.phone) params.phone = opts.phone; + if (opts.email) params.email = opts.email; var config = { method: 'POST', - url: opts.url + '/v1/profile/attest', + 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); @@ -1489,47 +1440,89 @@ BlobClient.attest = function (opts, fn) { if (err) { log.error('attest:', err); - fn(new Error('Failed to attest - XHR error')); + 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('attest:', resp.body); - fn(new Error('Failed to attest: ' + resp.body.message || "")); + log.error('attestation:', resp.body); + fn(new Error('attestation error: ' + resp.body.message || "")); } else { - fn(new Error('Failed to attest')); + fn(new Error('attestation error')); } }); -}; -*/ +}; /** - * attestation + * getAttestationSummary * @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) + */ +BlobClient.getAttestationSummary = function (opts, fn) { + + + var config = { + method: 'GET', + url: opts.url + '/v1/attestation/summary', + dataType: 'json' + }; + + 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.identity (optional) + * @param {object} opts.profile (required for type 'profile') * @param {string} opts.email (required for type 'email') - * @param {string} opts.attestation_id (required for completing email or phone attestations) + * @param {string} opts.answers (required for type 'identity') * @param {string} opts.token (required for completing email or phone attestations) */ -BlobClient.attestation = function (opts, fn) { +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.attestation_id) params.attestation_id = opts.attestation_id; - if (opts.verification_id) params.verification_id = opts.verification_id; - if (opts.token) params.token = opts.token; - if (opts.questions_id) params.questions_id = opts.questions_id; - if (opts.answers) params.answers = opts.answers; + 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/profile/attestation/' + opts.type, + url: opts.url + '/v1/attestation/' + opts.type + '/update', dataType: 'json', data: params }; @@ -1545,6 +1538,10 @@ BlobClient.attestation = function (opts, fn) { 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); @@ -1555,4 +1552,27 @@ BlobClient.attestation = function (opts, fn) { }); }; +/** + * parseAttestation + * @param {Object} attestation + */ +BlobClient.parseAttestation = function (attestation) { + var segments = attestation.split('.'); + var decoded; + + // base64 decode and parse JSON + try { + decoded = { + header : JSON.parse(atob(segments[0])), + payload : JSON.parse(atob(segments[1])), + signature : segments[2] + }; + + } catch (e) { + console.log("invalid attestation:", e); + } + + return decoded; +}; + exports.BlobClient = BlobClient; diff --git a/src/js/ripple/vaultclient.js b/src/js/ripple/vaultclient.js index f2e12d1f..f783f45f 100644 --- a/src/js/ripple/vaultclient.js +++ b/src/js/ripple/vaultclient.js @@ -583,15 +583,11 @@ VaultClient.prototype.requestToken = blobClient.requestToken; VaultClient.prototype.verifyToken = blobClient.verifyToken; -VaultClient.prototype.updateProfile = blobClient.updateProfile; +VaultClient.prototype.getAttestation = blobClient.getAttestation; -VaultClient.prototype.getProfile = blobClient.getProfile; +VaultClient.prototype.updateAttestation = blobClient.updateAttestation; -VaultClient.prototype.attest = blobClient.attest; - -VaultClient.prototype.attestation = blobClient.attestation; - -VaultClient.prototype.getAttestations = blobClient.getAttestations; +VaultClient.prototype.getAttestationSummary = blobClient.getAttestationSummary; //export by name exports.VaultClient = VaultClient; From 778ccd4805053eb72cd459094e06f3137b36a4a0 Mon Sep 17 00:00:00 2001 From: Matthew Fettig Date: Mon, 6 Oct 2014 18:03:52 -0700 Subject: [PATCH 6/6] [TASK] vault client: tests for attestation routes and full summary --- src/js/ripple/blob.js | 12 +++- src/js/ripple/crypt.js | 8 +++ test/vault-test.js | 124 +++++++++++++++++++++++++++-------------- 3 files changed, 100 insertions(+), 44 deletions(-) diff --git a/src/js/ripple/blob.js b/src/js/ripple/blob.js index c8dc4d10..5024bc96 100644 --- a/src/js/ripple/blob.js +++ b/src/js/ripple/blob.js @@ -1322,7 +1322,6 @@ BlobClient.deleteBlob = function(options, fn) { }); }; - /*** identity related functions ***/ /** @@ -1381,6 +1380,7 @@ BlobClient.updateProfile = function (opts, fn) { * @param {string} opts.auth_secret * @param {srring} opts.blob_id */ + BlobClient.getProfile = function (opts, fn) { var config = { method: 'GET', @@ -1418,6 +1418,7 @@ BlobClient.getProfile = function (opts, fn) { * @param {object} opts.phone (required for type 'phone') * @param {string} opts.email (required for type 'email') */ + BlobClient.getAttestation = function (opts, fn) { var params = { }; @@ -1463,6 +1464,7 @@ BlobClient.getAttestation = function (opts, fn) { * @param {string} opts.auth_secret * @param {string} opts.blob_id */ + BlobClient.getAttestationSummary = function (opts, fn) { @@ -1472,6 +1474,8 @@ BlobClient.getAttestationSummary = function (opts, fn) { dataType: 'json' }; + if (opts.full) config.url += '?full=true'; + var signedRequest = new SignedRequest(config); var signed = signedRequest.signHmac(opts.auth_secret, opts.blob_id); @@ -1510,6 +1514,7 @@ BlobClient.getAttestationSummary = function (opts, fn) { * @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 = { }; @@ -1556,6 +1561,7 @@ BlobClient.updateAttestation = function (opts, fn) { * parseAttestation * @param {Object} attestation */ + BlobClient.parseAttestation = function (attestation) { var segments = attestation.split('.'); var decoded; @@ -1563,8 +1569,8 @@ BlobClient.parseAttestation = function (attestation) { // base64 decode and parse JSON try { decoded = { - header : JSON.parse(atob(segments[0])), - payload : JSON.parse(atob(segments[1])), + header : JSON.parse(crypt.decodeBase64(segments[0])), + payload : JSON.parse(crypt.decodeBase64(segments[1])), signature : segments[2] }; diff --git a/src/js/ripple/crypt.js b/src/js/ripple/crypt.js index 6395f77c..c6d29665 100644 --- a/src/js/ripple/crypt.js +++ b/src/js/ripple/crypt.js @@ -322,4 +322,12 @@ Crypt.base64UrlToBase64 = function(encodedData) { return encodedData; }; +/** + * base64 to UTF8 + */ + +Crypt.decodeBase64 = function (data) { + return sjcl.codec.utf8String.fromBits(sjcl.codec.base64.toBits(data)); +} + exports.Crypt = Crypt; diff --git a/test/vault-test.js b/test/vault-test.js index 5bd85e6c..0aaf9a7a 100644 --- a/test/vault-test.js +++ b/test/vault-test.js @@ -151,14 +151,12 @@ var mockDelete; if (!online) { mockRippleTxt = nock('https://ripple.com') - .persist() .get('/ripple.txt') .reply(200, rippleTxtRes, { 'Content-Type': 'text/plain' }); mockRippleTxt2 = nock('https://' + exampleData.domain) - .persist() .get('/ripple.txt') .reply(200, rippleTxtRes, { 'Content-Type': 'text/plain' @@ -171,21 +169,21 @@ if (!online) { 'Content-Type': 'text/plain' }); - mockRegister = nock('https://id.staging.ripple.com').persist(); + 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').persist(); + 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').persist(); + 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' @@ -203,47 +201,40 @@ if (!online) { 'Content-Type': 'application/json' }); - mockRename = nock('https://id.staging.ripple.com/v1/user/').persist(); + 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/').persist(); - mockUpdate.filteringPath(/((.+)\/update(.+))/g, 'update/') + 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/').persist(); + 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/').persist(); + + 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').persist(); + mockEmail = nock('https://id.staging.ripple.com/v1/user'); mockEmail.filteringPath(/((.+)\/email(.+))/g, 'email/') .post('email/') .reply(200, {result:'success'}, { 'Content-Type': 'application/json' }); - - mockProfile = nock('https://id.staging.ripple.com/v1/user').persist(); - mockProfile.filteringPath(/((.+)\/profile(.+))/g, 'profile/') - .post('profile/') - .reply(200, {result:'success'}, { - 'Content-Type': 'application/json' - }); } describe('Ripple Txt', function () { @@ -437,6 +428,7 @@ describe('VaultClient', function () { }); }); +/* describe('#updateProfile', function () { it('should update profile parameters associated with a blob', function (done) { this.timeout(10000); @@ -460,7 +452,7 @@ describe('VaultClient', function () { }); }); }); - +*/ }); @@ -713,39 +705,89 @@ describe('Blob', function () { }); describe('identityVault', function() { - it('#identity-getProfile', function (done) { + it('#identity - Get Attestation', function (done) { var options = { - url : blob.url, + url : blob.url, auth_secret : blob.data.auth_secret, - blob_id :blob.id, - identity_id : blob.identity_id, + blob_id : blob.id, }; - options.profile = { - attributes : [{ - name : 'email', - value : 'example@example.com', - visibility : 'public' - }] - }; + options.type = 'identity'; - client.getProfile(options, function(err, resp) { - console.log(err, resp); + 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-updateProfile', function (done) { - 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-getAttestations', function (done) { - done(); - }); + it('#identity - Get Attestation Summary', function (done) { - it('#identity-attest', function (done) { - 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: 'eyJ6IjoieiJ9.eyJ6IjoieiJ9.sig', + }, {'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.deepEqual(resp.decoded, {"header":{"z":"z"},"payload":{"z":"z"},"signature":"sig"}) + done(); + }); + }); }); //only do these offline