diff --git a/js/models/Identity.js b/js/models/Identity.js index aa13270db..8c1571bd0 100644 --- a/js/models/Identity.js +++ b/js/models/Identity.js @@ -79,7 +79,7 @@ Identity._newStorage = function(opts) { }; /* for stubbing */ -Identity.prototype._newWallet = function(opts) { +Identity._newWallet = function(opts) { return new Wallet(opts); }; @@ -88,6 +88,16 @@ Identity._walletFromObj = function(o, s, n, b, skip) { return Wallet.fromObj(o, s, n, b, skip); }; +/* for stubbing */ +Identity._walletRead = function(id, s, n, b, skip, cb) { + return Wallet.read(id, s, n, b, skip, cb); +}; + +/* for stubbing */ +Identity._walletDelete = function(id, cb) { + return Wallet.delete(id, cb); +}; + @@ -181,6 +191,35 @@ Identity.prototype.store = function(opts, cb) { }); }; +/** + * read + * + * @param opts + * @param cb + * @return {undefined} + */ +Identity.prototype.read = function(opts, cb) { + var self = this; + self.profile.read(opts, function(err) { + if (err) return cb(err); + + var l = self.wallets.length, + i = 0; + if (!l) return cb(); + + _.each(self.wallets, function(w) { + w.store(function(err) { + if (err) return cb(err); + + if (++i == l) + return cb(); + }) + }); + }); +}; + + + /** * @desc Imports a wallet from an encrypted base64 object * @param {string} base64 - the base64 encoded object @@ -204,61 +243,6 @@ Identity.prototype.importWallet = function(base64, passphrase, skipFields, cb) { w.store(cb); }); }; - -Identity.prototype.migrateWallet = function(walletId, passphrase, cb) { - var self = this; - - self.storage.setPassphrase(passphrase); - self.read_Old(walletId, null, function(err, wallet) { - if (err) return cb(err); - - wallet.store(function(err) { - if (err) return cb(err); - - self.storage.deleteWallet_Old(walletId, function(err) { - if (err) return cb(err); - - self.storage.removeGlobal('nameFor::' + walletId, function() { - return cb(); - }); - }); - }); - }); - -}; - - - -Identity.prototype.read_Old = function(walletId, skipFields, cb) { - var self = this, - err; - var obj = {}; - - this.storage.readWallet_Old(walletId, function(err, ret) { - if (err) return cb(err); - - _.each(Wallet.PERSISTED_PROPERTIES, function(p) { - obj[p] = ret[p]; - }); - - if (!_.any(_.values(obj))) - return cb(new Error('Wallet not found')); - - var w, err; - obj.id = walletId; - try { - w = self.fromObj(obj, skipFields); - } catch (e) { - if (e && e.message && e.message.indexOf('MISSOPTS')) { - err = new Error('Could not read: ' + walletId); - } else { - err = e; - } - w = null; - } - return cb(err, w); - }); -}; /** * @desc This method prepares options for a new Wallet * @@ -331,7 +315,7 @@ Identity.prototype.createWallet = function(opts, cb) { this.storage.setPassphrase(opts.passphrase); var self = this; - var w = this._newWallet(opts); + var w = Identity._newWallet(opts); this.addWallet(w, function(err) { if (err) return cb(err); self.profile.setLastOpenedTs(w.id, function(err) { @@ -389,10 +373,11 @@ Identity.prototype._checkVersion = function(inVersion) { Identity.prototype.openWallet = function(walletId, passphrase, cb) { preconditions.checkArgument(cb); var self = this; - self.storage.setPassphrase(passphrase); + self.storage.setPassphrase(passphrase); self.migrateWallet(walletId, passphrase, function() { - self._readWallet(walletId, null, function(err, w) { + + Identity._walletRead(walletId, self.storage, self.networks, self.blockchains, [], function(err, w) { if (err) return cb(err); w.store(function(err) { @@ -418,7 +403,7 @@ Identity.prototype.listWallets = function() { Identity.prototype.deleteWallet = function(walletId, cb) { var self = this; - Wallet.delete(walletId, this.storage, function(err) { + Identity._walletDelete(walletId, this.storage, function(err) { if (err) return cb(err); self.profile.deleteWallet(walletId, function(err) { return cb(err); diff --git a/js/models/Profile.js b/js/models/Profile.js index a2a675b30..dd8ee7f0d 100644 --- a/js/models/Profile.js +++ b/js/models/Profile.js @@ -4,16 +4,18 @@ var _ = require('underscore'); var log = require('../log'); var bitcore = require('bitcore'); -function Profile(info, password, storage) { +function Profile(info, storage) { preconditions.checkArgument(info.email); - preconditions.checkArgument(password); + preconditions.checkArgument(info.hash); preconditions.checkArgument(storage); preconditions.checkArgument(storage.getItem); + this.hash = info.hash; this.email = info.email; this.extra = info.extra; + + this.key = Profile.key(this.hash); this.walletInfos = {}; - this.hash = Profile.hash(this.email, password); this.storage = storage; }; @@ -21,28 +23,26 @@ Profile.hash = function(email, password) { return bitcore.util.sha256ripe160(email + password).toString('hex'); }; -Profile.fromObj = function(obj, password, storage) { - var o = _.clone(obj); - return new Profile(obj, password, storage); +Profile.key = function(hash) { + return 'identity::' + hash; }; +Profile.open = function(email, password, storage, cb) { + preconditions.checkArgument(cb); -Profile.prototype.key = function() { - return 'identity::' + this.hash + '_' + this.email; + var key = Profile.key(Profile.hash(email, password)); + storage.getGlobal(key, function(err, val) { + if (err) return cb(err); + + if (!val) + return cb(new Error('PNOTFOUND: Profile not found')); + + return cb(new Profile(val, storage)); + }); }; Profile.prototype.toObj = function() { - var obj = _.clone(this); - delete obj['hash']; - return JSON.parse(JSON.stringify(obj)); -}; - -Profile.open = function(storage, cb) { - var key = this.key(); - this.storage.getGlobal(key, function(err, val) { - if (!val) return cb(new Error('PNOTFOUND: Profile not found')); - return cb(Profile.fromObj(val, password, storage)); - }); + return JSON.parse(JSON.stringify(this)); }; Profile.prototype.getWallet = function(walletId, cb) { @@ -104,7 +104,7 @@ Profile.prototype.setLasOpenedTs = function(walletId, cb) { Profile.prototype.store = function(opts, cb) { var self = this; var val = self.toObj(); - var key = self.key(); + var key = self.key; self.storage.get(key, function(val2) { if (val2 && !opts.overwrite) { diff --git a/js/models/Wallet.js b/js/models/Wallet.js index c67d677f0..f628e3f6f 100644 --- a/js/models/Wallet.js +++ b/js/models/Wallet.js @@ -1009,7 +1009,6 @@ Wallet.prototype.toObj = function() { */ Wallet.fromObj = function(o, storage, network, blockchain, skipFields) { - if (skipFields) { _.each(skipFields, function(k) { if (o[k]) { @@ -1020,19 +1019,23 @@ Wallet.fromObj = function(o, storage, network, blockchain, skipFields) { }); } + var networkName = Wallet.obtainNetworkName(o); + + // TODO Why moving everything to opts. This needs refactoring. - + // // clone opts var opts = JSON.parse(JSON.stringify(o.opts)); opts.addressBook = o.addressBook; opts.settings = o.settings; + if (o.privateKey) { opts.privateKey = PrivateKey.fromObj(o.privateKey); } else { opts.privateKey = new PrivateKey({ - networkName: opts.networkName + networkName: networkName }); } @@ -1040,7 +1043,7 @@ Wallet.fromObj = function(o, storage, network, blockchain, skipFields) { opts.publicKeyRing = PublicKeyRing.fromObj(o.publicKeyRing); } else { opts.publicKeyRing = new PublicKeyRing({ - networkName: opts.networkName, + networkName: networkName, requiredCopayers: opts.requiredCopayers, totalCopayers: opts.totalCopayers, }); @@ -1054,15 +1057,15 @@ Wallet.fromObj = function(o, storage, network, blockchain, skipFields) { opts.txProposals = TxProposals.fromObj(o.txProposals, Wallet.builderOpts); } else { opts.txProposals = new TxProposals({ - networkName: this.networkName, + networkName: networkName, }); } opts.lastTimestamp = o.lastTimestamp; opts.storage = storage; - opts.network = network; - opts.blockchain = blockchain; + opts.network = _.isArray(network)? network[networkName] : network; + opts.blockchain = _.isArray(blockchain) ? blockchain[networkName] : blockchain; opts.isImported = true; return new Wallet(opts); @@ -2756,4 +2759,64 @@ Wallet.request = function(options, callback) { return ret; }; +/* + * Old fns, only for compat + * + */ + + +Wallet.prototype.migrateWallet = function(walletId, passphrase, cb) { + var self = this; + + self.storage.setPassphrase(passphrase); + self.read_Old(walletId, null, function(err, wallet) { + if (err) return cb(err); + + wallet.store(function(err) { + if (err) return cb(err); + + self.storage.deleteWallet_Old(walletId, function(err) { + if (err) return cb(err); + + self.storage.removeGlobal('nameFor::' + walletId, function() { + return cb(); + }); + }); + }); + }); + +}; + +Wallet.prototype.read_Old = function(walletId, skipFields, cb) { + var self = this, + err; + var obj = {}; + + this.storage.readWallet_Old(walletId, function(err, ret) { + if (err) return cb(err); + + _.each(Wallet.PERSISTED_PROPERTIES, function(p) { + obj[p] = ret[p]; + }); + + if (!_.any(_.values(obj))) + return cb(new Error('Wallet not found')); + + var w, err; + obj.id = walletId; + try { + w = self.fromObj(obj, skipFields); + } catch (e) { + if (e && e.message && e.message.indexOf('MISSOPTS')) { + err = new Error('Could not read: ' + walletId); + } else { + err = e; + } + w = null; + } + return cb(err, w); + }); +}; + + module.exports = Wallet; diff --git a/test/Wallet.js b/test/Wallet.js index f16e3ad39..4683f7f56 100644 --- a/test/Wallet.js +++ b/test/Wallet.js @@ -1809,6 +1809,7 @@ describe('Wallet model', function() { networkName: 'testnet' } }).should.equal('testnet'); + Wallet.obtainNetworkName({ privateKey: { networkName: 'testnet' diff --git a/test/test.Identity.js b/test/test.Identity.js index cf147e761..f8b1ce7ce 100644 --- a/test/test.Identity.js +++ b/test/test.Identity.js @@ -43,22 +43,20 @@ describe('Identity model', function() { wallet = sinon.stub(); wallet.store = sinon.stub().yields(null); + wallet.getId = sinon.stub().returns('wid:123'); Identity._newWallet = sinon.stub().returns(wallet); profile = sinon.stub(); profile.addWallet = sinon.stub().yields(null);; profile.deleteWallet = sinon.stub().yields(null);; + profile.listWallets = sinon.stub().returns([]); profile.setLastOpenedTs = sinon.stub().yields(null);; profile.store = sinon.stub().yields(null);; Identity._newProfile = sinon.stub().returns(profile); + + iden = new Identity(email, password, config); - - var w = sinon.stub(); - w.store = sinon.stub().yields(null); - iden._newWallet = sinon.stub().returns(w); - - }); @@ -194,7 +192,7 @@ describe('Identity model', function() { iden.createWallet({ privateKeyHex: priv, }, function(err, w) { - iden._newWallet.getCall(0).args[0].privateKey.toObj().extendedPrivateKeyString.should.equal(priv); + Identity._newWallet.getCall(0).args[0].privateKey.toObj().extendedPrivateKeyString.should.equal(priv); should.not.exist(err); done(); }); @@ -203,8 +201,8 @@ describe('Identity model', function() { it('should be able to create wallets with random pk', function(done) { iden.createWallet(null, function(err, w1) { iden.createWallet(null, function(err, w2) { - iden._newWallet.getCall(0).args[0].privateKey.toObj().extendedPrivateKeyString.should.not.equal( - iden._newWallet.getCall(1).args[0].privateKey.toObj().extendedPrivateKeyString + Identity._newWallet.getCall(0).args[0].privateKey.toObj().extendedPrivateKeyString.should.not.equal( + Identity._newWallet.getCall(1).args[0].privateKey.toObj().extendedPrivateKeyString ); done(); }); @@ -225,12 +223,18 @@ describe('Identity model', function() { }); - describe.only('#openWallet', function() { + describe('#openWallet', function() { + beforeEach(function() { iden.migrateWallet = sinon.stub().yields(null); - iden.storage.setLastOpened = sinon.stub().yields(null); + iden.profile.setLastOpened = sinon.stub().yields(null); iden.storage.setPassphrase = sinon.spy(); - storage.getFirst = sinon.stub().yields('wallet'); + storage.getFirst = sinon.stub().yields('wallet1234'); + + var wallet = sinon.stub(); + wallet.store = sinon.stub().yields(null); + + Identity._walletRead = sinon.stub().callsArgWith(5, null, wallet); }); it('should call setPassphrase', function(done) { @@ -245,67 +249,13 @@ describe('Identity model', function() { }); }); - it('should call return wallet', function(done) { - - var s1 = sinon.stub(); - s1.store = sinon.stub().yields(null); - iden.read = sinon.stub().yields(null, s1); + it('should return wallet and call .store, .setLastOpenedTs & .migrateWallet', function(done) { iden.openWallet('dummy', 'xxx', function(err, w) { - w.should.equal(s1); - s1.store.calledOnce.should.equal(true); - done(); - }); - }); - - - it('should call #store', function(done) { - var iden = new Identity(config, '0.0.1'); - iden.storage.setPassphrase = sinon.spy(); - - var s1 = sinon.stub(); - s1.store = sinon.stub().yields(null); - iden.read = sinon.stub().yields(null, s1); - iden.migrateWallet = sinon.stub().yields(null); - iden.storage.setLastOpened = sinon.stub().yields(null); - - iden.open('dummy', 'xxx', function(err, w) { - s1.store.calledOnce.should.equal(true); - done(); - }); - }); - - it('should call #setLastOpened', function(done) { - var iden = new Identity(config, '0.0.1'); - iden.storage.setPassphrase = sinon.spy(); - - var s1 = sinon.stub(); - s1.store = sinon.stub().yields(null); - iden.read = sinon.stub().yields(null, s1); - iden.migrateWallet = sinon.stub().yields(null); - iden.storage.setLastOpened = sinon.stub().yields(null); - - iden.open('dummy', 'xxx', function(err, w) { - iden.storage.setLastOpened.calledOnce.should.equal(true); - iden.storage.setLastOpened.getCall(0).args[0].should.equal('dummy'); - done(); - }); - }); - it('should call #migrateWallet', function(done) { - var iden = new Identity(config, '0.0.1'); - iden.storage.setPassphrase = sinon.spy(); - - var s1 = sinon.stub(); - s1.store = sinon.stub().yields(null); - iden.read = sinon.stub().yields(null, s1); - iden.migrateWallet = sinon.stub().yields(null); - iden.storage.deleteWallet_Old = sinon.stub().yields(null); - iden.storage.removeGlobal = sinon.stub().yields(null); - iden.storage.setLastOpened = sinon.stub().yields(null); - - iden.open('dummy', 'xxx', function(err, w) { + should.not.exist(err); + w.store.calledOnce.should.equal(true); + iden.profile.setLastOpenedTs.calledOnce.should.equal(true); iden.migrateWallet.calledOnce.should.equal(true); - iden.migrateWallet.getCall(0).args[0].should.equal('dummy'); done(); }); }); @@ -314,6 +264,11 @@ describe('Identity model', function() { describe('#importWallet', function() { + + beforeEach(function() { + iden.migrateWallet = sinon.stub().yields(null); + }); + it('should create wallet from encrypted object', function(done) { iden.storage.setPassphrase = sinon.spy(); iden.storage.decrypt = sinon.stub().withArgs('base64').returns({ @@ -323,14 +278,11 @@ describe('Identity model', function() { wallet.getId = sinon.stub().returns('ID123'); Identity._walletFromObj = sinon.stub().returns(wallet); - iden.importWallet("encrypted object", "123", [], function(err) { - iden.openWallet('ID123', function(err, w) { + iden.importWallet("encrypted object", "xxx", [], function(err) { + iden.openWallet('ID123', 'xxx', function(err, w) { should.not.exist(err); - w.should.equal('ok'); - iden.storage.setPassphrase.calledOnce.should.be.true; - iden.storage.setPassphrase.getCall(0).args[0].should.equal('123'); - iden.storage.import.calledOnce.should.be.true; - iden.fromObj.calledWith('walletObj').should.be.true; + should.exist(w); + done(); }); }); }); @@ -357,84 +309,21 @@ describe('Identity model', function() { }); }); - describe('#getWallets', function() { - it('should return empty array if no wallets', function(done) { - iden.storage.getWallets = sinon.stub().yields([]); - iden.storage.getLastOpened = sinon.stub().yields(null); - - iden.getWallets(function(err, ws) { - should.not.exist(err); - ws.should.deep.equal([]); - done(); - }); - }); - - it('should be able to get current wallets', function(done) { - iden.storage.getWallets = sinon.stub().yields([{ - name: 'w1', - id: 'id1', - }, { - name: 'w', - id: 'id2', - }]); - iden.storage.getLastOpened = sinon.stub().yields(null); - - iden.getWallets(function(err, ws) { - should.not.exist(err); - ws.should.deep.equal([{ - name: 'w1', - id: 'id1', - show: 'w1 ' - }, { - name: 'w', - id: 'id2', - show: 'w ' - }]); - done(); - }); - }); - it('should include last used info', function(done) { - iden.storage.getWallets = sinon.stub().yields([{ - name: 'w1', - id: 'id1', - }, { - name: 'w', - id: 'id2', - }]); - iden.storage.getLastOpened = sinon.stub().yields('id2'); - - iden.getWallets(function(err, ws) { - should.not.exist(err); - ws.should.deep.equal([{ - name: 'w1', - id: 'id1', - show: 'w1 ' - }, { - name: 'w', - id: 'id2', - lastOpened: true, - show: 'w ' - }]); - done(); - }); + describe('#listWallets', function() { + it('should return empty array if no wallets', function() { + iden.listWallets(); + iden.profile.listWallets.calledOnce.should.equal(true); }); }); - describe('#delete', function() { - it('should call deleteWallet', function(done) { - iden.storage.deleteWallet = sinon.stub().yields(null); - iden.delete('xxx', function() { - iden.storage.deleteWallet.getCall(0).args[0].should.equal('xxx'); - done(); - }); - }); - it('should call lastOpened', function(done) { - iden.storage.deleteWallet = sinon.stub().yields(null); - iden.storage.setLastOpened = sinon.stub().yields(null); - iden.delete('xxx', function() { - iden.storage.setLastOpened.calledOnce.should.equal(true); - should.not.exist(iden.storage.setLastOpened.getCall(0).args[0]); + describe('#deleteWallet', function() { + Identity._walletDelete = sinon.stub().callsArgWith(2, null); + + it('should call Profile deleteWallet', function(done) { + iden.profile.deleteWallet = sinon.stub().yields(null); + iden.deleteWallet('xxx', function() { + iden.profile.deleteWallet.getCall(0).args[0].should.equal('xxx'); done(); }); }); diff --git a/test/test.Profile.js b/test/test.Profile.js index b4801aee2..af06fc544 100644 --- a/test/test.Profile.js +++ b/test/test.Profile.js @@ -11,9 +11,11 @@ var FakeStorage = function() {}; describe('Profile model', function() { var email = 'email@pepe.com'; var password = 'iamnotsatoshi'; + var hash = '1234'; var storage = new FakeStorage(); var opts = { email: email, + hash:hash, }; beforeEach(function() { @@ -34,19 +36,20 @@ describe('Profile model', function() { it('should create an instance', function() { var p = new Profile({ email: email, - }, password, storage); + hash: hash, + }, storage); should.exist(p); }); it('#fromObj #toObj round trip', function() { - var p = new Profile(opts, password, storage); - var p2 = Profile.fromObj(p.toObj(), password, storage); + var p = new Profile(opts, storage); + var p2 = new Profile(p.toObj(), storage); p2.should.deep.equal(p); }); describe('#addWallet', function() { it('should add a wallet id', function(done) { - var p = new Profile(opts, password, storage); + var p = new Profile(opts, storage); p.addWallet('123', {}, function(err) { p.getWallet('123').createdTs.should.be.above(123456789); storage.set.getCall(0).args[1].should.deep.equal(p.toObj()); @@ -54,7 +57,7 @@ describe('Profile model', function() { }) }); it('should keep old ts value', function(done) { - var p = new Profile(opts, password, storage); + var p = new Profile(opts, storage); p.walletInfos['123'] = { createdTs: 1 }; @@ -66,7 +69,7 @@ describe('Profile model', function() { }) }); it('should add a wallet info', function(done) { - var p = new Profile(opts, password, storage); + var p = new Profile(opts, storage); p.addWallet('123', { a: 1, b: 2 @@ -83,14 +86,14 @@ describe('Profile model', function() { describe('#addToWallet', function() { it('should warn if wallet does not exist', function(done) { - var p = new Profile(opts, password, storage); + var p = new Profile(opts, storage); p.addToWallet('234',{1:1}, function(err) { err.toString().should.contain('WNOEXIST'); done(); }); }); it('should add info to a wallet', function(done) { - var p = new Profile(opts, password, storage); + var p = new Profile(opts, storage); p.addWallet('234', {}, function(err) { p.addToWallet('234',{'hola':1}, function(err) { var w = p.getWallet('234'); @@ -107,7 +110,7 @@ describe('Profile model', function() { describe('#listWallets', function() { it('should list wallets in order', function(done) { - var p = new Profile(opts, password, storage); + var p = new Profile(opts, storage); p.addWallet('123', {}, function(err) { p.addWallet('234', {}, function(err) { _.pluck(p.listWallets(), 'id').should.deep.equal(['123', '234']); @@ -119,7 +122,7 @@ describe('Profile model', function() { describe('#deleteWallet', function() { it('should delete a wallet', function(done) { - var p = new Profile(opts, password, storage); + var p = new Profile(opts, storage); p.addWallet('123', {}, function(err) { p.addWallet('234', {}, function(err) { p.addWallet('345', {}, function(err) { @@ -133,7 +136,7 @@ describe('Profile model', function() { }); }); it('should warn if wallet does not exist', function(done) { - var p = new Profile(opts, password, storage); + var p = new Profile(opts, storage); p.deleteWallet('234', function(err) { err.toString().should.contain('WNOEXIST'); done(); @@ -144,7 +147,7 @@ describe('Profile model', function() { describe('#store', function() { it('should call storage set', function(done) { - var p = new Profile(opts, password, storage); + var p = new Profile(opts, storage); p.store({}, function(err) { storage.set.getCall(0).args[1].should.deep.equal(p.toObj()); should.not.exist(err); @@ -153,7 +156,7 @@ describe('Profile model', function() { }); it('should use fail to overwrite', function(done) { storage.get = sinon.stub().yields(123); - var p = new Profile(opts, password, storage); + var p = new Profile(opts, storage); p.store({}, function(err) { err.toString().should.contain('PEXISTS'); should.not.exist(storage.set.getCall(0)); @@ -163,7 +166,7 @@ describe('Profile model', function() { it('should use overwrite param', function(done) { storage.get = sinon.stub().yields(123); - var p = new Profile(opts, password, storage); + var p = new Profile(opts, storage); p.store({ overwrite: true }, function(err) {