diff --git a/js/controllers/open.js b/js/controllers/open.js index 9af583833..8f126dcb7 100644 --- a/js/controllers/open.js +++ b/js/controllers/open.js @@ -1,47 +1,44 @@ 'use strict'; -angular.module('copayApp.controllers').controller('OpenController', - function($scope, $rootScope, $location, walletFactory, controllerUtils, Passphrase, notification) { - controllerUtils.redirIfLogged(); +angular.module('copayApp.controllers').controller('OpenController', function($scope, $rootScope, $location, walletFactory, controllerUtils, Passphrase, notification) { + controllerUtils.redirIfLogged(); - var cmp = function(o1, o2) { - var v1 = o1.show.toLowerCase(), - v2 = o2.show.toLowerCase(); - return v1 > v2 ? 1 : (v1 < v2) ? -1 : 0; - }; - $rootScope.fromSetup = false; - $scope.loading = false; - $scope.wallets = walletFactory.getWallets().sort(cmp); - $scope.selectedWalletId = walletFactory.storage.getLastOpened() || ($scope.wallets[0] && $scope.wallets[0].id); - $scope.openPassword = ''; - $scope.isMobile = !!window.cordova; + var cmp = function(o1, o2) { + var v1 = o1.show.toLowerCase(), + v2 = o2.show.toLowerCase(); + return v1 > v2 ? 1 : (v1 < v2) ? -1 : 0; + }; + $rootScope.fromSetup = false; + $scope.loading = false; + $scope.wallets = walletFactory.getWallets().sort(cmp); + $scope.selectedWalletId = walletFactory.storage.getLastOpened() || ($scope.wallets[0] && $scope.wallets[0].id); + $scope.openPassword = ''; + $scope.isMobile = !!window.cordova; - $scope.open = function(form) { - if (form && form.$invalid) { - notification.error('Error', 'Please enter the required fields'); + $scope.open = function(form) { + if (form && form.$invalid) { + notification.error('Error', 'Please enter the required fields'); + return; + } + + $scope.loading = true; + var password = form.openPassword.$modelValue; + + Passphrase.getBase64Async(password, function(passphrase) { + var w, errMsg; + try { + w = walletFactory.open($scope.selectedWalletId, passphrase); + } catch (e) { + errMsg = e.message; + }; + if (!w) { + $scope.loading = false; + notification.error('Error', errMsg || 'Wrong password'); + $rootScope.$digest(); return; } + controllerUtils.startNetwork(w, $scope); + }); + }; - $scope.loading = true; - var password = form.openPassword.$modelValue; - - Passphrase.getBase64Async(password, function(passphrase) { - var w, errMsg; - try { - w = walletFactory.open($scope.selectedWalletId, { - passphrase: passphrase - }); - } catch (e) { - errMsg = e.message; - }; - if (!w) { - $scope.loading = false; - notification.error('Error', errMsg || 'Wrong password'); - $rootScope.$digest(); - return; - } - controllerUtils.startNetwork(w, $scope); - }); - }; - - }); +}); diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 2a4fa7542..daac04995 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -2,10 +2,10 @@ var http = require('http'); var EventEmitter = require('events').EventEmitter; -var nodeUtil = require('util'); var async = require('async'); var preconditions = require('preconditions').singleton(); var parseBitcoinURI = require('./HDPath').parseBitcoinURI; +var util = require('util'); var bitcore = require('bitcore'); var bignum = bitcore.Bignum; @@ -23,6 +23,7 @@ var PublicKeyRing = require('./PublicKeyRing'); var TxProposal = require('./TxProposal'); var TxProposals = require('./TxProposals'); var PrivateKey = require('./PrivateKey'); +var WalletLock = require('./WalletLock'); var copayConfig = require('../../../config'); function Wallet(opts) { @@ -45,9 +46,11 @@ function Wallet(opts) { this.log('creating ' + opts.requiredCopayers + ' of ' + opts.totalCopayers + ' wallet'); this.id = opts.id || Wallet.getRandomId(); + this.lock = new WalletLock(this.storage, this.id, opts.lockTimeOutMin); + + this.name = opts.name; - this.ignoreLock = opts.ignoreLock; this.verbose = opts.verbose; this.publicKeyRing.walletId = this.id; this.txProposals.walletId = this.id; @@ -64,7 +67,7 @@ function Wallet(opts) { this.network.setHexNonces(opts.networkNonces); } -nodeUtil.inherits(Wallet, EventEmitter); +util.inherits(Wallet, EventEmitter); Wallet.builderOpts = { lockTime: null, @@ -98,27 +101,6 @@ Wallet.prototype.connectToAll = function() { } }; -Wallet.prototype.getLock = function() { - return this.storage.getLock(this.id); -}; - -Wallet.prototype.setLock = function() { - return this.storage.setLock(this.id); -}; - -Wallet.prototype.unlock = function() { - this.storage.removeLock(this.id); -}; - -Wallet.prototype.checkAndLock = function() { - if (this.getLock()) { - return true; - } - - this.setLock(); - return false; -}; - Wallet.prototype._handleIndexes = function(senderId, data, isInbound) { this.log('RECV INDEXES:', data); var inIndexes = HDParams.fromList(data.indexes); @@ -438,11 +420,6 @@ Wallet.prototype.netStart = function(callback) { var self = this; var net = this.network; - if (this.checkAndLock() && !this.ignoreLock) { - this.emit('locked'); - return; - } - net.removeAllListeners(); net.on('connect', self._handleConnect.bind(self)); net.on('disconnect', self._handleDisconnect.bind(self)); @@ -519,7 +496,13 @@ Wallet.prototype.getRegisteredPeerIds = function() { return this.registeredPeerIds; }; +Wallet.prototype.keepAlive = function() { + this.lock.keepAlive(); +}; + Wallet.prototype.store = function() { + this.keepAlive(); + var wallet = this.toObj(); this.storage.setFromObj(this.id, wallet); this.log('Wallet stored'); @@ -556,8 +539,8 @@ Wallet.fromObj = function(o, storage, network, blockchain) { opts.storage = storage; opts.network = network; opts.blockchain = blockchain; - var w = new Wallet(opts); - return w; + + return new Wallet(opts); }; Wallet.prototype.toEncryptedObj = function() { @@ -709,7 +692,7 @@ Wallet.prototype.sign = function(ntxid, cb) { var self = this; setTimeout(function() { var myId = self.getMyCopayerId(); - var txp = self.txProposals.get(ntxid); + var txp = self.txProposals.get(ntxid); // if (!txp || txp.rejectedBy[myId] || txp.signedBy[myId]) { // if (cb) cb(false); // } @@ -782,7 +765,9 @@ Wallet.prototype.createPaymentTx = function(options, cb) { var self = this; if (typeof options === 'string') { - options = { uri: options }; + options = { + uri: options + }; } options.uri = options.uri || options.url; @@ -824,7 +809,9 @@ Wallet.prototype.fetchPaymentTx = function(options, cb) { options = options || {}; if (typeof options === 'string') { - options = { uri: options }; + options = { + uri: options + }; } options.uri = options.uri || options.url; options.fetch = true; @@ -980,8 +967,7 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { var refund_outputs = []; - options.refund_to = options.refund_to - || this.publicKeyRing.getPubKeys(0, false, this.getMyCopayerId())[0]; + options.refund_to = options.refund_to || this.publicKeyRing.getPubKeys(0, false, this.getMyCopayerId())[0]; if (options.refund_to) { var total = txp.merchant.pr.pd.outputs.reduce(function(total, _, i) { @@ -1003,23 +989,23 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { rpo.set('amount', +total.toString(10)); rpo.set('script', - Buffer.concat([ - new Buffer([ - 118, // OP_DUP - 169, // OP_HASH160 - 76, // OP_PUSHDATA1 - 20, // number of bytes - ]), - // needs to be ripesha'd - bitcore.util.sha256ripe160(options.refund_to), - new Buffer([ - 136, // OP_EQUALVERIFY - 172 // OP_CHECKSIG - ]) - ]) - ); + Buffer.concat([ + new Buffer([ + 118, // OP_DUP + 169, // OP_HASH160 + 76, // OP_PUSHDATA1 + 20, // number of bytes + ]), + // needs to be ripesha'd + bitcore.util.sha256ripe160(options.refund_to), + new Buffer([ + 136, // OP_EQUALVERIFY + 172 // OP_CHECKSIG + ]) + ]) + ); - refund_outputs.push(rpo.message); + refund_outputs.push(rpo.message); } // We send this to the serve after receiving a PaymentRequest @@ -1031,8 +1017,7 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { pay.set('transactions', [tx.serialize()]); pay.set('refund_to', refund_outputs); - options.memo = options.memo || options.comment - || 'Hi server, I would like to give you some money.'; + options.memo = options.memo || options.comment || 'Hi server, I would like to give you some money.'; pay.set('memo', options.memo); @@ -1188,8 +1173,8 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) merchantData.total = merchantData.total.toString(10); var b = new Builder(opts) - .setUnspent(unspent) - .setOutputs(outs); + .setUnspent(unspent) + .setOutputs(outs); merchantData.pr.pd.outputs.forEach(function(output, i) { var script = { @@ -1250,9 +1235,7 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) Wallet.prototype.verifyPaymentRequest = function(ntxid) { if (!ntxid) return false; - var txp = typeof ntxid !== 'object' - ? this.txProposals.get(ntxid) - : ntxid; + var txp = typeof ntxid !== 'object' ? this.txProposals.get(ntxid) : ntxid; // If we're not a payment protocol proposal, ignore. if (!txp.merchant) return true; @@ -1358,8 +1341,7 @@ Wallet.prototype.verifyPaymentRequest = function(ntxid) { } // Make sure the tx's output script and values match the payment request's. - if (av.toString('hex') !== ev.toString('hex') - || as.toString('hex') !== es.toString('hex')) { + if (av.toString('hex') !== ev.toString('hex') || as.toString('hex') !== es.toString('hex')) { // Verifiable outputs do not match outputs of merchant // data. We should not sign this transaction proposal! return false; @@ -1384,10 +1366,9 @@ Wallet.prototype.verifyPaymentRequest = function(ntxid) { // Actual script var as = new Buffer(ro.script.buffer, 'hex') - .slice(ro.script.offset, ro.script.limit); + .slice(ro.script.offset, ro.script.limit); - if (av.toString('hex') !== ev.toString('hex') - || as.toString('hex') !== es.toString('hex')) { + if (av.toString('hex') !== ev.toString('hex') || as.toString('hex') !== es.toString('hex')) { return false; } } @@ -1506,14 +1487,19 @@ Wallet.prototype.createTx = function(toAddress, amountSatStr, comment, opts, cb) if (typeof amountSatStr === 'function') { var cb = amountSatStr; var merchant = toAddress; - return this.createPaymentTx({ uri: merchant }, cb); + return this.createPaymentTx({ + uri: merchant + }, cb); } if (typeof comment === 'function') { var cb = comment; var merchant = toAddress; var comment = amountSatStr; - return this.createPaymentTx({ uri: merchant, memo: comment }, cb); + return this.createPaymentTx({ + uri: merchant, + memo: comment + }, cb); } if (typeof opts === 'function') { @@ -1779,7 +1765,9 @@ Wallet.prototype.verifySignedJson = function(senderId, payload, signature) { Wallet.request = function(options, callback) { if (typeof options === 'string') { - options = { uri: options }; + options = { + uri: options + }; } options.method = options.method || 'GET'; @@ -1794,8 +1782,7 @@ Wallet.request = function(options, callback) { this._error = cb; return this; }, - _success: function() { - ; + _success: function() {; }, _error: function(_, err) { throw err; diff --git a/js/models/core/WalletFactory.js b/js/models/core/WalletFactory.js index f40a02e4d..8d7ef29a4 100644 --- a/js/models/core/WalletFactory.js +++ b/js/models/core/WalletFactory.js @@ -108,6 +108,7 @@ WalletFactory.prototype.create = function(opts) { 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: this.networkName, @@ -137,6 +138,7 @@ WalletFactory.prototype.create = function(opts) { opts.requiredCopayers = requiredCopayers; opts.totalCopayers = totalCopayers; opts.version = opts.version || this.version; + var w = new Wallet(opts); w.store(); this.storage.setLastOpened(w.id); @@ -166,16 +168,11 @@ WalletFactory.prototype._checkNetwork = function(inNetworkName) { } }; -WalletFactory.prototype.open = function(walletId, opts) { - opts = opts || {}; - opts.id = walletId; - opts.verbose = this.verbose; - this.storage._setPassphrase(opts.passphrase); - - var w = this.read(walletId); - if (w) { +WalletFactory.prototype.open = function(walletId, passphrase) { + this.storage._setPassphrase(passphrase); + var w = this.read(walletId, opts); + if (w) w.store(); - } this.storage.setLastOpened(walletId); return w; diff --git a/js/models/storage/LocalEncrypted.js b/js/models/storage/LocalEncrypted.js index da1d99742..acfcf696f 100644 --- a/js/models/storage/LocalEncrypted.js +++ b/js/models/storage/LocalEncrypted.js @@ -1,7 +1,7 @@ 'use strict'; var CryptoJS = require('node-cryptojs-aes').CryptoJS; - +var bitcore = require('bitcore'); var id = 0; function Storage(opts) { @@ -93,6 +93,15 @@ Storage.prototype.removeGlobal = function(k) { this.localStorage.removeItem(k); }; +Storage.prototype.getSessionId = function() { + var sessionId = this.sessionStorage.getItem('sessionId'); + if (!sessionId) { + sessionId = bitcore.getRandomBuffer(8).toString('hex'); + this.sessionStorage.setItem(sessionId, 'sessionId'); + } + return sessionId; +}; + Storage.prototype._key = function(walletId, k) { return walletId + '::' + k; }; @@ -132,7 +141,8 @@ Storage.prototype.getWalletIds = function() { if (split.length == 2) { var walletId = split[0]; - if (walletId === 'nameFor') continue; + if (walletId === 'nameFor' || walletId === 'lock') + continue; if (typeof uniq[walletId] === 'undefined') { walletIds.push(walletId); @@ -180,8 +190,9 @@ Storage.prototype.getLastOpened = function() { return this.getGlobal('lastOpened'); } +// Lock related Storage.prototype.setLock = function(walletId) { - this.setGlobal(this._key(walletId, 'Lock'), true); + this.setGlobal(this._key(walletId, 'Lock'), this.sessionId()); } Storage.prototype.getLock = function(walletId) { diff --git a/js/routes.js b/js/routes.js index f0d95f93f..152068985 100644 --- a/js/routes.js +++ b/js/routes.js @@ -74,8 +74,8 @@ angular .html5Mode(false) .hashPrefix('!'); // IDLE timeout - $idleProvider.idleDuration(15 * 60); // in seconds - $idleProvider.warningDuration(10); // in seconds + $idleProvider.idleDuration(config.wallet.idleDurationMin * 60); // in seconds + $idleProvider.warningDuration(20); // in seconds }) .run(function($rootScope, $location, $idle) { $idle.watch(); diff --git a/test/mocks/FakeStorage.js b/test/mocks/FakeStorage.js index 56b88c2f4..c83855306 100644 --- a/test/mocks/FakeStorage.js +++ b/test/mocks/FakeStorage.js @@ -35,6 +35,11 @@ FakeStorage.prototype.getLock = function(id) { return this.storage[id + '::lock']; } +FakeStorage.prototype.getSessionId = function() { + return this.sessionId || 'aSessionId'; +}; + + FakeStorage.prototype.removeLock = function(id) { delete this.storage[id + '::lock']; } diff --git a/test/test.Wallet.js b/test/test.Wallet.js index 75a2d9988..da692b12b 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -182,7 +182,6 @@ describe('Wallet model', function() { cachedW2obj.opts.reconnectDelay = 100; } var w = Wallet.fromObj(cachedW2obj, cachedW2.storage, cachedW2.network, cachedW2.blockchain); - w.unlock(); return w; }; @@ -1025,20 +1024,6 @@ describe('Wallet model', function() { w.network.start.getCall(0).args[0].privkey.length.should.equal(64); }); - it('should check if wallet is already opened', function() { - var w = cachedCreateW2(); - should.not.exist(w.getLock()); - w.checkAndLock().should.equal(false); - w.getLock().should.equal(true); - }); - it('should check if wallet is already opened', function() { - var w = cachedCreateW2(); - should.not.exist(w.getLock()); - w.checkAndLock().should.equal(false); - w.getLock().should.equal(true); - }); - - it('should not start if locked', function() { var w = cachedCreateW2(); w.netStart();