From 1021cbc4fa31c3893fa8ef3047c81d56544469ce Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Tue, 30 Dec 2014 15:12:01 -0300 Subject: [PATCH 1/5] added checksum methods to profile --- js/models/Identity.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/js/models/Identity.js b/js/models/Identity.js index 51d8c4191..23796c78f 100644 --- a/js/models/Identity.js +++ b/js/models/Identity.js @@ -76,6 +76,10 @@ Identity.getKeyForEmail = function(email) { return Identity.getStoragePrefix() + bitcore.util.sha256ripe160(email).toString('hex'); }; +Identity.prototype.getChecksumForStorage = function() { + return JSON.stringify(this.walletIds); +}; + Identity.prototype.getId = function() { return Identity.getKeyForEmail(this.email); }; @@ -148,6 +152,22 @@ Identity.open = function(opts, cb) { }); }; +Identity.prototype.verifyChecksum = function (cb) { + var self = this; + + self.storage.getItem(Identity.getKeyForEmail(self.email), function(err, data, headers) { + var iden; + if (err) return cb(err); + try { + iden = JSON.parse(data); + } catch (e) { + return cb(e); + } + + return cb(null, self.getChecksumForStorage() == self.getChecksumForStorage.call(iden)); +}; + + /** * @param {string} walletId * @returns {Wallet} From f2cda099f96ab5eacc1240701ee264ea30ac8da4 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Tue, 30 Dec 2014 15:49:19 -0300 Subject: [PATCH 2/5] verify checksum on actions that store the profile --- js/models/Identity.js | 181 +++++++++++++++++++---------------- js/services/backupService.js | 2 +- 2 files changed, 97 insertions(+), 86 deletions(-) diff --git a/js/models/Identity.js b/js/models/Identity.js index 23796c78f..2ad0b22da 100644 --- a/js/models/Identity.js +++ b/js/models/Identity.js @@ -77,7 +77,7 @@ Identity.getKeyForEmail = function(email) { }; Identity.prototype.getChecksumForStorage = function() { - return JSON.stringify(this.walletIds); + return JSON.stringify(_.sortBy(this.walletIds)); }; Identity.prototype.getId = function() { @@ -163,8 +163,8 @@ Identity.prototype.verifyChecksum = function (cb) { } catch (e) { return cb(e); } - return cb(null, self.getChecksumForStorage() == self.getChecksumForStorage.call(iden)); + }); }; @@ -206,18 +206,22 @@ Identity.prototype.deleteWallet = function(walletId, cb) { var self = this; - - var w = this.getWalletById(walletId); - w.close(); - - delete this.wallets[walletId]; - delete this.focusedTimestamps[walletId]; - this.walletIds = _.without(this.walletIds, walletId); - - this.storage.removeItem(Wallet.getStorageKey(walletId), function(err) { + self.verifyChecksum(function (err, match) { if (err) return cb(err); - self.emitAndKeepAlive('walletDeleted', walletId); - self.store(null, cb); + if (!match) return cb('The profile is out of sync'); + + var w = self.getWalletById(walletId); + w.close(); + + delete self.wallets[walletId]; + delete self.focusedTimestamps[walletId]; + self.walletIds = _.without(self.walletIds, walletId); + + self.storage.removeItem(Wallet.getStorageKey(walletId), function(err) { + if (err) return cb(err); + self.emitAndKeepAlive('walletDeleted', walletId); + self.store(null, cb); + }); }); }; @@ -366,18 +370,19 @@ Identity.prototype.exportEncryptedWithWalletInfo = function(opts) { return crypto.encrypt(this.password, this.exportWithWalletInfo(opts)); }; -Identity.prototype.setBackupNeeded = function() { - this.backupNeeded = true; - this.store({ - noWallets: true - }, function() {}); -} +Identity.prototype.setBackupNeeded = function(backupNeeded) { + var self = this; -Identity.prototype.setBackupDone = function() { - this.backupNeeded = false; - this.store({ - noWallets: true - }, function() {}); + self.backupNeeded = !!backupNeeded; + + self.verifyChecksum(function (err, match) { + if (err) return cb(err); + if (!match) return cb('The profile is out of sync'); + + self.store({ + noWallets: true + }, function() {}); + }); } Identity.prototype.exportWithWalletInfo = function(opts) { @@ -618,71 +623,77 @@ Identity.prototype.bindWallet = function(w) { */ Identity.prototype.createWallet = function(opts, cb) { preconditions.checkArgument(cb); - - opts = opts || {}; - opts.networkName = opts.networkName || 'testnet'; - - log.debug('### CREATING NEW WALLET.' + (opts.id ? ' USING ID: ' + opts.id : ' NEW ID') + (opts.privateKey ? ' USING PrivateKey: ' + opts.privateKey.getId() : ' NEW PrivateKey')); - - var privOpts = { - networkName: opts.networkName, - }; - - if (opts.privateKeyHex && opts.privateKeyHex.length > 1) { - privOpts.extendedPrivateKeyString = opts.privateKeyHex; - } - - opts.privateKey = opts.privateKey || new PrivateKey(privOpts); - - var requiredCopayers = opts.requiredCopayers || this.walletDefaults.requiredCopayers; - var totalCopayers = opts.totalCopayers || this.walletDefaults.totalCopayers; - opts.lockTimeoutMin = this.walletDefaults.idleDurationMin; - - opts.publicKeyRing = opts.publicKeyRing || new PublicKeyRing({ - networkName: opts.networkName, - requiredCopayers: requiredCopayers, - totalCopayers: totalCopayers, - }); - opts.publicKeyRing.addCopayer( - opts.privateKey.deriveBIP45Branch().extendedPublicKeyString(), - opts.nickname || this.getName() - ); - log.debug('\t### PublicKeyRing Initialized'); - - opts.txProposals = opts.txProposals || new TxProposals({ - networkName: opts.networkName, - }); - var walletClass = opts.walletClass || Wallet; - - log.debug('\t### TxProposals Initialized'); - - - opts.networkOpts = this.networkOpts; - opts.blockchainOpts = this.blockchainOpts; - - opts.spendUnconfirmed = opts.spendUnconfirmed || this.walletDefaults.spendUnconfirmed; - opts.reconnectDelay = opts.reconnectDelay || this.walletDefaults.reconnectDelay; - opts.requiredCopayers = requiredCopayers; - opts.totalCopayers = totalCopayers; - opts.version = opts.version || this.version; - + var self = this; - var w = new walletClass(opts); - if (self.getWalletById(w.getId())) { - return cb('walletAlreadyExists'); - } - self.addWallet(w); - self.updateFocusedTimestamp(w.getId()); - self.bindWallet(w); - self.storeWallet(w, function(err) { + self.verifyChecksum(function (err, match) { if (err) return cb(err); + if (!match) return cb('The profile is out of sync'); - self.backupNeeded = true; - self.store({ - noWallets: true, - }, function(err) { - return cb(err, w); + opts = opts || {}; + opts.networkName = opts.networkName || 'testnet'; + + log.debug('### CREATING NEW WALLET.' + (opts.id ? ' USING ID: ' + opts.id : ' NEW ID') + (opts.privateKey ? ' USING PrivateKey: ' + opts.privateKey.getId() : ' NEW PrivateKey')); + + var privOpts = { + networkName: opts.networkName, + }; + + if (opts.privateKeyHex && opts.privateKeyHex.length > 1) { + privOpts.extendedPrivateKeyString = opts.privateKeyHex; + } + + opts.privateKey = opts.privateKey || new PrivateKey(privOpts); + + var requiredCopayers = opts.requiredCopayers || self.walletDefaults.requiredCopayers; + var totalCopayers = opts.totalCopayers || self.walletDefaults.totalCopayers; + opts.lockTimeoutMin = self.walletDefaults.idleDurationMin; + + opts.publicKeyRing = opts.publicKeyRing || new PublicKeyRing({ + networkName: opts.networkName, + requiredCopayers: requiredCopayers, + totalCopayers: totalCopayers, + }); + opts.publicKeyRing.addCopayer( + opts.privateKey.deriveBIP45Branch().extendedPublicKeyString(), + opts.nickname || self.getName() + ); + log.debug('\t### PublicKeyRing Initialized'); + + opts.txProposals = opts.txProposals || new TxProposals({ + networkName: opts.networkName, + }); + var walletClass = opts.walletClass || Wallet; + + log.debug('\t### TxProposals Initialized'); + + + opts.networkOpts = self.networkOpts; + opts.blockchainOpts = self.blockchainOpts; + + opts.spendUnconfirmed = opts.spendUnconfirmed || self.walletDefaults.spendUnconfirmed; + opts.reconnectDelay = opts.reconnectDelay || self.walletDefaults.reconnectDelay; + opts.requiredCopayers = requiredCopayers; + opts.totalCopayers = totalCopayers; + opts.version = opts.version || self.version; + + var w = new walletClass(opts); + + if (self.getWalletById(w.getId())) { + return cb('walletAlreadyExists'); + } + self.addWallet(w); + self.updateFocusedTimestamp(w.getId()); + self.bindWallet(w); + self.storeWallet(w, function(err) { + if (err) return cb(err); + + self.backupNeeded = true; + self.store({ + noWallets: true, + }, function(err) { + return cb(err, w); + }); }); }); }; diff --git a/js/services/backupService.js b/js/services/backupService.js index f8a445907..5caf3f3ce 100644 --- a/js/services/backupService.js +++ b/js/services/backupService.js @@ -72,7 +72,7 @@ BackupService.prototype.walletDownload = function(wallet) { }; BackupService.prototype.profileEncrypted = function(iden) { - iden.setBackupDone(); + iden.setBackupNeeded(false); return iden.exportEncryptedWithWalletInfo(iden.password); } From e3aafbf86005dd8aedd7f52b71de02a6805ccf3e Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Tue, 30 Dec 2014 15:50:30 -0300 Subject: [PATCH 3/5] improved error message --- js/models/Identity.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/models/Identity.js b/js/models/Identity.js index 2ad0b22da..84868bafd 100644 --- a/js/models/Identity.js +++ b/js/models/Identity.js @@ -208,7 +208,7 @@ Identity.prototype.deleteWallet = function(walletId, cb) { self.verifyChecksum(function (err, match) { if (err) return cb(err); - if (!match) return cb('The profile is out of sync'); + if (!match) return cb('The profile is out of sync. Please re-login to get the latest changes.'); var w = self.getWalletById(walletId); w.close(); @@ -377,7 +377,7 @@ Identity.prototype.setBackupNeeded = function(backupNeeded) { self.verifyChecksum(function (err, match) { if (err) return cb(err); - if (!match) return cb('The profile is out of sync'); + if (!match) return cb('The profile is out of sync. Please re-login to get the latest changes.'); self.store({ noWallets: true @@ -628,7 +628,7 @@ Identity.prototype.createWallet = function(opts, cb) { self.verifyChecksum(function (err, match) { if (err) return cb(err); - if (!match) return cb('The profile is out of sync'); + if (!match) return cb('The profile is out of sync. Please re-login to get the latest changes.'); opts = opts || {}; opts.networkName = opts.networkName || 'testnet'; From 094f38c5e9801f32f3fd3ca97e5894e67a4a6f8d Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Tue, 30 Dec 2014 16:20:18 -0300 Subject: [PATCH 4/5] giving proper feedback --- js/controllers/profile.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/js/controllers/profile.js b/js/controllers/profile.js index bc66c2806..8bebdfeae 100644 --- a/js/controllers/profile.js +++ b/js/controllers/profile.js @@ -52,9 +52,9 @@ angular.module('copayApp.controllers').controller('ProfileController', function( return; } $location.path('/'); - setTimeout(function() { + $timeout(function() { notification.error('Success', 'Profile successfully deleted'); - }, 1); + }); }); }; @@ -73,10 +73,10 @@ angular.module('copayApp.controllers').controller('ProfileController', function( identityService.deleteWallet($scope.item, function(err) { if (err) { $scope.loading = null; - $scope.error = err.message; + $scope.error = err.message || err; copay.logger.warn(err); - } - else { + $timeout(function () { $scope.$digest(); }); + } else { $modalInstance.close($scope.item.name || $scope.item.id); } }); From a769ba456a3a158bdd1aa88133a3bcdea4c946e7 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Tue, 30 Dec 2014 16:30:05 -0300 Subject: [PATCH 5/5] fixed tests --- test/Identity.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/Identity.js b/test/Identity.js index 2b77a5ac8..038ca7d34 100644 --- a/test/Identity.js +++ b/test/Identity.js @@ -327,6 +327,7 @@ describe('Identity model', function() { it('should be able to create wallets with given pk', function(done) { var priv = 'tprv8ZgxMBicQKsPdEqHcA7RjJTayxA3gSSqeRTttS1JjVbgmNDZdSk9EHZK5pc52GY5xFmwcakmUeKWUDzGoMLGAhrfr5b3MovMUZUTPqisL2m'; + args.storage.getItem = sinon.stub().yields(null, JSON.stringify(iden)); args.storage.setItem = sinon.stub(); args.storage.setItem.onFirstCall().callsArg(2); args.storage.setItem.onSecondCall().callsArg(2); @@ -342,6 +343,7 @@ describe('Identity model', function() { }); it('should be able to create wallets with random pk', function(done) { + args.storage.getItem = sinon.stub().yields(null, JSON.stringify(iden)); args.storage.setItem = sinon.stub(); args.storage.setItem.onCall(0).callsArg(2); args.storage.setItem.onCall(1).callsArg(2); @@ -351,7 +353,8 @@ describe('Identity model', function() { walletClass: walletClass, }, function(err, w1) { should.exist(w1); - + + args.storage.getItem = sinon.stub().yields(null, JSON.stringify(iden)); iden.createWallet({ walletClass: walletClass, }, function(err, w2) { @@ -622,6 +625,7 @@ describe('Identity model', function() { it('should delete wallet', function(done) { iden.addWallet(w); iden.getWalletById('32').getName().should.equal('treintaydos'); + iden.storage.getItem = sinon.stub().yields(null, JSON.stringify(iden)); iden.deleteWallet('32', function(err) { should.not.exist(iden.getWalletById('32')); iden.walletIds.should.deep.equal([]);