Skip to content
This repository has been archived by the owner on Mar 7, 2023. It is now read-only.

Commit

Permalink
feat(shared Metadata): extracted API
Browse files Browse the repository at this point in the history
  • Loading branch information
sha49 committed Feb 15, 2017
1 parent 8f9f2c3 commit b26107a
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 139 deletions.
4 changes: 3 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,7 @@ module.exports = {
Networks: require('bitcoinjs-lib/src/networks'),
ECDSA: require('bitcoinjs-lib/src/ecdsa'),
SharedMetadata: require('./src/sharedMetadata'),
Contacts: require('./src/contacts')
Contacts: require('./src/contacts'),
SharedMetadataAPI: require('./src/sharedMetadataAPI'),
R: require('ramda')
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"bs58": "2.0.*",
"es6-promise": "^3.0.2",
"isomorphic-fetch": "^2.2.0",
"jwt-decode": "^2.1.0",
"pbkdf2": "3.0.4",
"ramda": "^0.22.1",
"randombytes": "^2.0.1",
Expand Down
3 changes: 3 additions & 0 deletions src/blockchain-wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ var AccountInfo = require('./account-info');
var Metadata = require('./metadata');
var constants = require('./constants');
var Payment = require('./payment');
var SharedMetadata = require('./sharedMetadata');

// Wallet

Expand Down Expand Up @@ -77,6 +78,8 @@ function Wallet (object) {
this._latestBlock = null;
this._accountInfo = null;
this._external = null;
// handle second password and non-upgraded wallets
this._sharedMetadata = SharedMetadata.fromMasterHDNode(this.hdwallet.getMasterHDNode());
}

Object.defineProperties(Wallet.prototype, {
Expand Down
188 changes: 50 additions & 138 deletions src/sharedMetadata.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
// var master = Blockchain.MyWallet.wallet.hdwallet.getMasterHDNode()
// var m = Blockchain.SharedMetadata.fromMasterHDNode(master)
'use strict';

const WalletCrypto = require('./wallet-crypto');
const Bitcoin = require('bitcoinjs-lib');
const crypto = require('crypto');
const API = require('./api');
const MyWallet = require('./wallet');
const Contacts = require('./contacts');
const Helpers = require('./helpers');
const Metadata = require('./metadata');
const jwtDecode = require('jwt-decode');
const API = require('./sharedMetadataAPI');
import * as R from 'ramda'

class SharedMetadata {
Expand All @@ -21,131 +20,45 @@ class SharedMetadata {
this._keyPair = mdidHDNode.keyPair;
this._auth_token = null;
this._sequence = Promise.resolve();
this.authorize();
}

get mdid() {
return this._mdid;
}
get priv() {
return this._priv;
}
get mdid() { return this._mdid; }
get node() { return this._node; }
get token() { return this._auth_token; }
}

SharedMetadata.sign = function (keyPair, message) {
return Bitcoin.message.sign(keyPair, message)
};

SharedMetadata.verify = function (mdid, signature, message) {
return Bitcoin.message.verify (mdid, signature, message);
}
// should be overwritten by iOS
SharedMetadata.sign = Bitcoin.message.sign;
SharedMetadata.verify = Bitcoin.message.verify

SharedMetadata.request = function (method, endpoint, data, authToken) {
var url = API.API_ROOT_URL + 'metadata/' + endpoint;
var options = {
headers: { 'Content-Type': 'application/json' },
credentials: 'omit'
};
if (authToken) {
options.headers.Authorization = 'Bearer ' + authToken;
SharedMetadata.signChallenge = R.curry((key, r) => (
{
nonce: r.nonce,
signature: SharedMetadata.sign(key, r.nonce).toString('base64'),
mdid: key.getAddress()
}

// encodeFormData :: Object -> url encoded params
var encodeFormData = function (data) {
if (!data) return '';
var encoded = Object.keys(data).map(function (k) {
return encodeURIComponent(k) + '=' + encodeURIComponent(data[k]);
}).join('&');
return encoded ? '?' + encoded : encoded;
};

if (data && data !== {}) {
if (method === 'GET') {
url += encodeFormData(data);
} else {
options.body = JSON.stringify(data);
}
));

SharedMetadata.getAuthToken = (mdidHDNode) =>
API.getAuth().then(SharedMetadata.signChallenge(mdidHDNode.keyPair))
.then(API.postAuth);

SharedMetadata.isValidToken = (token) => {
try {
const decoded = jwtDecode(token);
var expDate = new Date(decoded.exp * 1000);
var now = new Date();
return now < expDate;
} catch (e) {
return false;
}

options.method = method;

var handleNetworkError = function (e) {
return Promise.reject({ error: 'SHARED_METADATA_CONNECT_ERROR', message: e });
};

var checkStatus = function (response) {
if (response.ok) {
return response.json();
} else {
return response.text().then(Promise.reject.bind(Promise));
}
};

return fetch(url, options)
.catch(handleNetworkError)
.then(checkStatus);
};

SharedMetadata.getAuthToken = function (mdidHDNode) {
const S = SharedMetadata;
const mdid = mdidHDNode.getAddress();
const key = mdidHDNode.keyPair;
return S.request('GET','auth')
.then( (r) => ({ nonce: r.nonce
, signature: S.sign(key, r.nonce).toString('base64')
, mdid: mdid}))
.then( (d) => S.request('POST', 'auth' , d))
.then( (r) => r.token);
};

SharedMetadata.prototype.authorize = function () {
return this.next(() => {
return SharedMetadata.getAuthToken(this._node)
.then((token) => {
this._auth_token = token;
return token;
})
});
}
SharedMetadata.prototype.getMessages = function (onlyNew) {
return this.next(
SharedMetadata.request.bind(this, 'GET', 'messages', onlyNew ? {new: true} : {}, this._auth_token)
);
};

SharedMetadata.prototype.getMessage = function (id) {
return this.next(
SharedMetadata.request.bind(this, 'GET', 'message/' + id, null, this._auth_token)
);
};

SharedMetadata.prototype.processMessage = function (id) {
return this.next(
SharedMetadata.request.bind(this, 'PUT', 'message/' + id + '/processed', null, this._auth_token)
);
};

SharedMetadata.prototype.trustContact = function (contactMdid) {
return this.next(
SharedMetadata.request.bind(this, 'PUT', 'trusted/' + contactMdid, null, this._auth_token)
);
};

SharedMetadata.prototype.getTrustedList = function () {
return this.next(
SharedMetadata.request.bind(this, 'GET', 'trusted', null, this._auth_token)
);
};

SharedMetadata.prototype.getTrusted = function (contactMdid) {
return this.next(
SharedMetadata.request.bind(this, 'GET', 'trusted/' + contactMdid, null, this._auth_token)
);
};

SharedMetadata.prototype.removeContact = function (contactMdid) {
const saveToken = (r) => { this._auth_token = r.token; return r.token; };
return this.next(
SharedMetadata.request.bind(this, 'DELETE', 'trusted/' + contactMdid, null, this._auth_token)
() => SharedMetadata.isValidToken(this.token)
? Promise.resolve(this.token)
: SharedMetadata.getAuthToken(this.node).then(saveToken)
);
};

Expand All @@ -155,7 +68,6 @@ SharedMetadata.prototype.removeContact = function (contactMdid) {
// type: type,
// payload: encrypted,
// signature: this.sign(encrypted),
// // sender: this.mdid,
// recipient: mdidRecipient
// };
// return this.request('POST', 'messages', body);
Expand Down Expand Up @@ -207,40 +119,40 @@ SharedMetadata.prototype.removeContact = function (contactMdid) {
// return msgP.then(f.bind(this));
// };
//
SharedMetadata.prototype.publishXPUB = function () {
return this.next(() => {
var myDirectory = new Metadata(this._keyPair);
myDirectory.fetch();
return myDirectory.update({xpub: this._xpub});
});
};
// SharedMetadata.prototype.publishXPUB = function () {
// return this.next(() => {
// var myDirectory = new Metadata(this._keyPair);
// myDirectory.fetch();
// return myDirectory.update({xpub: this._xpub});
// });
// };
//
// SharedMetadata.prototype.getXPUB = function (contactMDID) {
// return this.next(Metadata.read.bind(undefined, contactMDID));
// };

SharedMetadata.prototype.getXPUB = function (contactMDID) {
return this.next(Metadata.read.bind(undefined, contactMDID));
};
// createInvitation :: Promise InvitationID
SharedMetadata.prototype.createInvitation = function () {
return this.next(SharedMetadata.request.bind(this, 'POST', 'share', undefined, this._auth_token));
return this.authorize().then((t) => this.next(API.createInvitation.bind(null, t)));
};
// readInvitation :: String -> Promise RequesterID
SharedMetadata.prototype.readInvitation = function (id) {
return this.next(SharedMetadata.request.bind(this, 'GET', 'share/' + id, undefined, this._auth_token));
SharedMetadata.prototype.readInvitation = function (uuid) {
return this.authorize().then((t) => this.next(API.readInvitation.bind(null, t, uuid)));
};
// acceptInvitation :: String -> Promise ()
SharedMetadata.prototype.acceptInvitation = function (id) {
return this.next(SharedMetadata.request.bind(this, 'POST', 'share/' + id, undefined, this._auth_token));
SharedMetadata.prototype.acceptInvitation = function (uuid) {
return this.authorize().then((t) => this.next(API.acceptInvitation.bind(null, t, uuid)));
};
// deleteInvitation :: String -> Promise ()
SharedMetadata.prototype.deleteInvitation = function (id) {
return this.next(SharedMetadata.request.bind(this, 'DELETE', 'share/' + id, undefined, this._auth_token));
SharedMetadata.prototype.deleteInvitation = function (uuid) {
return this.authorize().then((t) => this.next(API.deleteInvitation.bind(null, t, uuid)));
};

SharedMetadata.fromMDIDHDNode = function (mdidHDNode) {
return new SharedMetadata(mdidHDNode);
};

SharedMetadata.fromMasterHDNode = function (masterHDNode) {
// var masterHDNode = MyWallet.wallet.hdwallet.getMasterHDNode(cipher);
var hash = WalletCrypto.sha256('info.blockchain.mdid');
var purpose = hash.slice(0, 4).readUInt32BE(0) & 0x7FFFFFFF;
var mdidHDNode = masterHDNode.deriveHardened(purpose);
Expand All @@ -249,7 +161,7 @@ SharedMetadata.fromMasterHDNode = function (masterHDNode) {

SharedMetadata.prototype.next = function (f) {
var nextInSeq = this._sequence.then(f);
this._sequence = nextInSeq.then(Helpers.noop, Helpers.noop);
this._sequence = nextInSeq.then(x => x, x => x);
return nextInSeq;
};

Expand Down
68 changes: 68 additions & 0 deletions src/sharedMetadataAPI.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
'use strict';
const API = require('./api');
const S = {};

S.request = function (method, endpoint, data, authToken) {
var url = API.API_ROOT_URL + 'metadata/' + endpoint;
var options = {
headers: { 'Content-Type': 'application/json' },
credentials: 'omit'
};
if (authToken) {
options.headers.Authorization = 'Bearer ' + authToken;
}
// encodeFormData :: Object -> url encoded params
var encodeFormData = function (data) {
if (!data) return '';
var encoded = Object.keys(data).map(function (k) {
return encodeURIComponent(k) + '=' + encodeURIComponent(data[k]);
}).join('&');
return encoded ? '?' + encoded : encoded;
};
if (data && data !== {}) {
if (method === 'GET') {
url += encodeFormData(data);
} else {
options.body = JSON.stringify(data);
}
}
options.method = method;
var handleNetworkError = function (e) {
return Promise.reject({ error: 'SHARED_METADATA_CONNECT_ERROR', message: e });
};
var checkStatus = function (response) {
if (response.ok) {
return response.json();
} else {
return response.text().then(Promise.reject.bind(Promise));
}
};
return fetch(url, options)
.catch(handleNetworkError)
.then(checkStatus);
};

// authentication
S.getAuth = () => S.request('GET', 'auth');
S.postAuth = (data) => S.request('POST', 'auth', data);

// messages
S.getMessages = (token, onlyNew) => S.request('GET', 'messages', onlyNew ? {new: true} : {}, token);
S.getMessage = (token, uuid) => S.request('GET', 'message/' + uuid, null, token);
S.sendMessage = (token, recipient, payload, signature, type) =>
S.request('POST', 'messages', {type, payload, signature, recipient}, token);
S.processMessage = (token, uuid) => S.request('PUT', 'message/' + uuid + '/processed', null, token);

// trusted contact list
S.addTrusted = (token, mdid) => S.request('PUT', 'trusted/' + mdid, null, token);
S.getTrusted = (token, mdid) => S.request('GET', 'trusted/' + mdid, null, token);
S.deleteTrusted = (token, mdid) => S.request('DELETE', 'trusted/' + mdid, null, mdid);
S.getTrustedList = (token) => S.request('GET', 'trusted', null, token);

// invitation process
S.createInvitation = (token) => S.request('POST', 'share', null, token);
S.readInvitation = (token, uuid) => S.request('GET', 'share/' + uuid, null, token);
S.acceptInvitation = (token, uuid) => S.request('POST', 'share/' + uuid, null, token);
S.deleteInvitation = (token, uuid) => S.request('DELETE', 'share/' + uuid, null, token);

module.exports = S;

0 comments on commit b26107a

Please sign in to comment.