diff --git a/Gruntfile.js b/Gruntfile.js index 2955fd54e..c0c40bdc6 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -56,7 +56,7 @@ module.exports = function(grunt) { files: [ 'js/models/*.js', 'js/util/*.js', - 'plugins/*.js', + 'js/plugins/*.js', 'js/*.js', '!js/copayBundle.js', '!js/copayMain.js' @@ -202,7 +202,7 @@ module.exports = function(grunt) { }, jsdoc: { dist: { - src: ['js/models/*.js', 'plugins/*.js'], + src: ['js/models/*.js', 'js/plugins/*.js'], options: { destination: 'doc', configure: 'jsdoc.conf.json', diff --git a/config.js b/config.js index edde35bd6..065b2f128 100644 --- a/config.js +++ b/config.js @@ -54,12 +54,13 @@ var defaultConfig = { verbose: 1, plugins: { - LocalStorage: true, + //LocalStorage: true, //GoogleDrive: true, //InsightStorage: true + EncryptedInsightStorage: true }, - InsightStorage: { + EncryptedInsightStorage: { url: 'https://test-insight.bitpay.com:443/api/email' }, diff --git a/copay.js b/copay.js index 0ea40a3fd..0bf381bf8 100644 --- a/copay.js +++ b/copay.js @@ -11,7 +11,6 @@ module.exports.HDParams = require('./js/models/HDParams'); // components var Async = module.exports.Async = require('./js/models/Async'); var Insight = module.exports.Insight = require('./js/models/Insight'); -var Storage = module.exports.Storage = require('./js/models/Storage'); module.exports.Identity = require('./js/models/Identity'); module.exports.Wallet = require('./js/models/Wallet'); diff --git a/js/controllers/createProfile.js b/js/controllers/createProfile.js index 5dae983b9..34997f5a7 100644 --- a/js/controllers/createProfile.js +++ b/js/controllers/createProfile.js @@ -2,9 +2,7 @@ angular.module('copayApp.controllers').controller('CreateProfileController', function($scope, $rootScope, $location, notification, controllerUtils, pluginManager, identityService) { controllerUtils.redirIfLogged(); - $scope.retreiving = true; - - identityService.check($scope); + $scope.retreiving = false; $scope.createProfile = function(form) { if (form && form.$invalid) { diff --git a/js/controllers/sidebar.js b/js/controllers/sidebar.js index 51e2c2362..24b89c36b 100644 --- a/js/controllers/sidebar.js +++ b/js/controllers/sidebar.js @@ -96,7 +96,7 @@ angular.module('copayApp.controllers').controller('SidebarController', function( var wids = _.pluck($rootScope.iden.listWallets(), 'id'); _.each(wids, function(wid) { if (controllerUtils.isFocusedWallet(wid)) return; - var w = $rootScope.iden.getOpenWallet(wid); + var w = $rootScope.iden.getWalletById(wid); $scope.wallets.push(w); controllerUtils.updateBalance(w, function(err, res) { if (err) return; diff --git a/js/models/Identity.js b/js/models/Identity.js index 6bc46b626..04002b64c 100644 --- a/js/models/Identity.js +++ b/js/models/Identity.js @@ -2,7 +2,9 @@ var preconditions = require('preconditions').singleton(); var _ = require('underscore'); +var bitcore = require('bitcore'); var log = require('../log'); +var async = require('async'); var version = require('../../version').version; var TxProposals = require('./TxProposals'); @@ -10,25 +12,30 @@ var PublicKeyRing = require('./PublicKeyRing'); var PrivateKey = require('./PrivateKey'); var Wallet = require('./Wallet'); var PluginManager = require('./PluginManager'); -var Profile = require('./Profile'); -var Insight = module.exports.Insight = require('./Insight'); var Async = module.exports.Async = require('./Async'); -var Storage = module.exports.Storage = require('./Storage'); /** * @desc * Identity - stores the state for a wallet in creation * - * @param {Object} config - configuration for this wallet - * @param {Object} config.wallet - default configuration for the wallet + * @param {Object} opts - configuration for this wallet + * @param {string} opts.fullName + * @param {string} opts.email + * @param {string} opts.password + * @param {string} opts.storage + * @param {string} opts.pluginManager + * @param {Object} opts.walletDefaults + * @param {string} opts.version + * @param {Object} opts.wallets + * @param {Object} opts.network + * @param {string} opts.network.testnet + * @param {string} opts.network.livenet * @constructor */ - -function Identity(password, opts) { +function Identity(opts) { preconditions.checkArgument(opts); opts = _.extend({}, opts); - this.storage = Identity._getStorage(opts, password); this.networkOpts = { 'livenet': opts.network.livenet, 'testnet': opts.network.testnet, @@ -38,297 +45,205 @@ function Identity(password, opts) { 'testnet': opts.network.testnet, }; - this.pluginManager = opts.pluginManager || {}; - this.insightSaveOpts = opts.insightSave || {}; + this.fullName = opts.fullName || opts.email; + this.email = opts.email; + this.password = opts.password; + + this.storage = opts.storage || opts.pluginManager.get('DB'); + this.storage.setCredentials(this.email, this.password, {}); + this.walletDefaults = opts.walletDefaults || {}; this.version = opts.version || version; - this.wallets = {}; + this.wallets = opts.wallets || {}; }; - -/* for stubbing */ -Identity._createProfile = function(email, password, storage, cb) { - Profile.create(email, password, storage, cb); +Identity.getKeyForEmail = function(email) { + return 'profile::' + bitcore.util.sha256ripe160(email).toString('hex'); }; -Identity._newStorage = function(opts) { - return new Storage(opts); +Identity.prototype.getId = function() { + return Identity.getKeyForEmail(this.email); }; -Identity._newWallet = function(opts) { - return new Wallet(opts); +Identity.prototype.getName = function() { + return this.fullName || this.email; }; -Identity._walletFromObj = function(o, readOpts) { - return Wallet.fromObj(o, readOpts); -}; +/** + * Creates an Identity + * + * @param opts + * @param cb + * @return {undefined} + */ +Identity.create = function(opts, cb) { + opts = _.extend({}, opts); -Identity._walletDelete = function(id, s, cb) { - return Wallet.delete(id, s, cb); -}; - -/* for stubbing */ -Identity._openProfile = function(email, password, storage, cb) { - Profile.open(email, password, storage, cb); -}; - -/* for stubbing */ -Identity._newAsync = function(opts) { - return new Async(opts); -}; - -Identity._getStorage = function(opts, password) { - var storageOpts = {}; - - if (opts.pluginManager) { - storageOpts = _.clone({ - db: opts.pluginManager.get('DB'), - passphraseConfig: opts.passphraseConfig, - }); + var iden = new Identity(opts); + if (opts.noWallets) { + return cb(null, iden); + } else { + return iden.createDefaultWallet(opts, cb); } - if (password) - storageOpts.password = password; - - return Identity._newStorage(storageOpts); }; /** - * check if any profile exists on storage + * Create a wallet, 1-of-1 named general * - * @param opts.storageOpts - * @param cb + * @param {Object} opts + * @param {Object} opts.walletDefaults + * @param {string} opts.walletDefaults.networkName */ -Identity.anyProfile = function(opts, cb) { - var storage = Identity._getStorage(opts); - storage.getFirst(Profile.key(''), { - onlyKey: true - }, function(err, v, k) { - return cb(k ? true : false); - }); -}; - -/** - * check if any wallet exists on storage - * - * @param opts.storageOpts - * @param cb - */ -Identity.anyWallet = function(opts, cb) { - var storage = Identity._getStorage(opts); - storage.getFirst(Wallet.getStorageKey(''), { - onlyKey: true - }, function(err, v, k) { - return cb(k ? true : false); - }); -}; - -/** - * creates and Identity - * - * @param email - * @param password - * @param opts - * @param cb - * @return {undefined} - */ -Identity.create = function(email, password, opts, cb) { - opts = opts || {}; - - var iden = new Identity(password, opts); - - Identity._createProfile(email, password, iden.storage, function(err, profile) { - if (err) return cb(err); - iden.profile = profile; - - if (opts.noWallets) - cb(null, iden); - - // default wallet - var dflt = _.clone(opts.walletDefaults); - var wopts = _.extend(dflt, { - nickname: email, - networkName: opts.networkName, - requiredCopayers: 1, - totalCopayers: 1, - password: password, - name: 'general' - }); - iden.createWallet(wopts, function(err, w) { - if (err) { - return cb(err); - } - if (iden.pluginManager.get && iden.pluginManager.get('remote-backup')) { - iden.pluginManager.get('remote-backup').store( - iden, - iden.insightSaveOpts, - function(error) { - // FIXME: Ignoring this error may not be the best thing to do. But remote storage - // is not required for the user to use the wallet. - return cb(null, iden, w); - } - ); - } else { - return cb(null, iden, w); - } - }); - }); -}; - -/** - * validates Profile's email - * - * @param authcode - * @param cb - * @return {undefined} - */ -Identity.prototype.validate = function(authcode, cb) { - // TODO - console.log('[Identity.js.99] TODO: Should validate email thru authcode'); //TODO - return cb(); -}; - - -/** - * open's an Identity from storage - * - * @param email - * @param password - * @param opts - * @param cb - * @return {undefined} - */ -Identity.open = function(email, password, opts, cb) { - var iden = new Identity(password, opts); - - Identity._openProfile(email, password, iden.storage, function(err, profile) { - if (err) { - if (err.message && err.message.indexOf('PNOTFOUND') !== -1) { - if (opts.pluginManager && opts.pluginManager.get('remote-backup')) { - return opts.pluginManager.get('remote-backup').retrieve(email, password, opts, cb); - } else { - return cb(err); - } - } - return cb(err); - } - iden.profile = profile; - - var wids = _.pluck(iden.listWallets(), 'id'); - if (!wids || !wids.length) - return cb(new Error('Could not open any wallet from profile'), iden); - - // Open All wallets from profile - //This could be optional, or opts.onlyOpen = wid - var wallets = []; - var remaining = wids.length; - _.each(wids, function(wid) { - iden.openWallet(wid, function(err, w) { - if (err) { - log.error('Cound not open wallet id:' + wid + '. Skipping') - iden.profile.deleteWallet(wid, function() {}); - } else { - log.info('Open wallet id:' + wid + ' opened'); - wallets.push(w); - } - if (--remaining == 0) { - var lastFocused = iden.profile.getLastFocusedWallet(); - return cb(err, iden, lastFocused); - } - }) - }); - }); -}; - -/** - * isAvailable - * - * @param email - * @param opts - * @param cb - * @return {undefined} - */ -Identity.isAvailable = function(email, opts, cb) { - console.log('[Identity.js.127:isAvailable:] TODO'); //TODO - return cb(); -}; - -Identity.prototype.readWallet = function(walletId, readOpts, cb) { - preconditions.checkArgument(cb); - var self = this, - err; - var obj = {}; - - this.storage.getFirst(Wallet.getStorageKey(walletId), {}, function(err, obj) { - if (err) return cb(err); - - if (!obj) - return cb(new Error('WNOTFOUND: Wallet not found')); - - var w, err; - obj.id = walletId; - - try { - log.debug('## OPENING Wallet: ' + walletId); - w = Wallet.fromUntrustedObj(obj, readOpts); - } catch (e) { - log.debug("ERROR: ", e.message); - if (e && e.message && e.message.indexOf('MISSOPTS')) { - err = new Error('WERROR: Could not read: ' + walletId + ': ' + e.message); - } else { - err = e; - } - w = null; - } - return cb(err, w); - }); -}; - -Identity.prototype.storeWallet = function(w, cb) { - preconditions.checkArgument(w && _.isObject(w)); - - var id = w.getId(); - var val = w.toObj(); - var key = Wallet.getStorageKey(id + '_' + w.getName()); - - this.storage.set(key, val, function(err) { - log.debug('Wallet:' + w.getName() + ' stored'); - - if (cb) - cb(err); - }); -}; - - -/** - * store - * - * @param opts - * @param cb - * @return {undefined} - */ -Identity.prototype.store = function(opts, cb) { - preconditions.checkState(this.profile); +Identity.prototype.createDefaultWallet = function(opts, callback) { var self = this; - self.profile.store(opts, function(err) { - if (err) return cb(err); - - var l = Object.keys(self.wallets), - i = 0; - if (!l) return cb(); - - _.each(self.wallets, function(w) { - self.storeWallet(w, function(err) { - if (err) return cb(err); - - if (++i == l) - return cb(); - }) - }); + var walletOptions = _.extend(opts.walletDefaults, { + nickname: this.fullName || this.email, + networkName: opts.networkName, + requiredCopayers: 1, + totalCopayers: 1, + password: this.password, + name: 'general' + }); + this.createWallet(walletOptions, function(err, wallet) { + if (err) { + return callback(err); + } + return callback(null, self); }); }; +/** + * Open an Identity from the given storage + * + * @param {string} email + * @param {string} password + * @param {Object} opts + * @param {Object} opts.storage + * @param {Function} cb + */ +Identity.open = function(email, password, opts, cb) { + + var storage = opts.storage || opts.pluginManager.get('DB'); + storage.setCredentials(email, password, opts); + storage.getItem(Identity.getKeyForEmail(email), function(err, data) { + if (err) { + return cb(err); + } + return Identity.createFromPartialJson(data, opts, cb); + }); +}; + +/** + * Creates an Identity, retrieves all Wallets remotely, and activates network + * + * @param {string} jsonString - a string containing a json object with options to rebuild the identity + * @param {Object} opts + * @param {Function} cb + */ +Identity.createFromPartialJson = function(jsonString, opts, callback) { + var exported; + try { + exported = JSON.parse(jsonString); + } catch (e) { + return callback('Invalid JSON'); + } + var identity = new Identity(_.extend(opts, exported)); + async.map(exported.walletIds, function(walletId, callback) { + identity.retrieveWalletFromStorage(walletId, function(error, wallet) { + if (!error) { + identity.wallets[wallet.getId()] = wallet; + wallet.netStart(); + } + callback(error, wallet); + }); + }, function(err) { + return callback(err, identity); + }); +}; + +/** + * @param {string} walletId + * @param {Function} callback + */ +Identity.prototype.retrieveWalletFromStorage = function(walletId, callback) { + var self = this; + this.storage.getItem(Wallet.getStorageKey(walletId), function(error, walletData) { + if (error) { + return callback(error); + } + try { + log.debug('## OPENING Wallet: ' + walletId); + if (_.isString(walletData)) { + walletData = JSON.parse(walletData); + } + var readOpts = { + networkOpts: self.networkOpts, + blockchainOpts: self.blockchainOpts, + skipFields: [] + }; + return callback(null, Wallet.fromUntrustedObj(walletData, readOpts)); + + } catch (e) { + + log.debug("ERROR: ", e.message); + if (e && e.message && e.message.indexOf('MISSOPTS') !== -1) { + return callback(new Error('WERROR: Could not read: ' + walletId + ': ' + e.message)); + } else { + return callback(e); + } + } + }); +}; + +/** + * TODO (matiu): What is this supposed to do? + */ +Identity.isAvailable = function(email, opts, cb) { + return cb(); +}; + +/** + * @param {Wallet} wallet + * @param {Function} cb + */ +Identity.prototype.storeWallet = function(wallet, cb) { + preconditions.checkArgument(w && _.isObject(wallet)); + + var val = wallet.toObj(); + var key = wallet.getStorageKey(); + + this.storage.setItem(key, val, function(err) { + if (err) { + log.debug('Wallet:' + w.getName() + ' couldnt be stored'); + return cb(err); + } + return cb(); + }); +}; + +Identity.prototype.toObj = function() { + return _.extend({walletIds: _.keys(this.wallets)}, + _.pick(this, 'version', 'fullName', 'password', 'email')); +}; + +Identity.prototype.exportWithWalletInfo = function() { + return _.extend({wallets: _.map(this.wallets, function(wallet) { return wallet.toObj(); })}, + _.pick(this, 'version', 'fullName', 'password', 'email')); +}; + +/** + * @param {Object} opts + * @param {Function} cb + */ +Identity.prototype.store = function(opts, cb) { + var self = this; + self.storage.setItem(this.getStorageKey(), this.toObj(), function(err) { + if (err) return cb(err); + async.map(self.wallets, self.storeWallet, cb); + }); +}; Identity.prototype._cleanUp = function() { // NOP @@ -338,28 +253,11 @@ Identity.prototype._cleanUp = function() { * @desc Closes the wallet and disconnects all services */ Identity.prototype.close = function(cb) { - preconditions.checkState(this.profile); - - var l = Object.keys(this.wallets), - i = 0; - if (!l) { - return cb ? cb() : null; - } - - var self = this; - _.each(this.wallets, function(w) { - w.close(function(err) { - if (err) return cb(err); - - if (++i == l) { - self._cleanUp(); - if (cb) return cb(); - } - }) - }); + async.map(this.wallets, function(wallet, callback) { + wallet.close(callback); + }, cb); }; - /** * @desc Imports a wallet from an encrypted base64 object * @param {string} base64 - the base64 encoded object @@ -392,18 +290,20 @@ Identity.prototype.importWallet = function(base64, password, skipFields, cb) { }); }; -Identity.prototype.closeWallet = function(wid, cb) { - var w = this.getOpenWallet(wid); - preconditions.checkState(w, 'Wallet not found'); +/** + * @param {Wallet} wallet + * @param {Function} cb + */ +Identity.prototype.closeWallet = function(wallet, cb) { + preconditions.checkState(wallet, 'Wallet not found'); - var self = this; - w.close(function(err) { + wallet.close(function(err) { delete self.wallets[wid]; return cb(err); }); }; -Identity.importFromJson = function(str, password, opts, cb) { +Identity.importFromFullJson = function(str, password, opts, cb) { preconditions.checkArgument(str); var json; try { @@ -415,59 +315,29 @@ Identity.importFromJson = function(str, password, opts, cb) { if (!_.isNumber(json.iterations)) return cb('BADSTR: Missing iterations'); - if (!json.profile) - return cb('BADSTR: Missing profile'); - - var iden = new Identity(password, opts); - iden.profile = Profile.import(json.profile, password, iden.storage); + var email = json.email; + var iden = new Identity(email, password, opts); json.wallets = json.wallets || {}; - var walletInfoBackup = iden.profile.walletInfos; - iden.profile.walletInfos = {}; - - var l = _.size(json.wallets), - i = 0; - - if (!l) - return cb(null, iden); - - _.each(json.wallets, function(wstr) { + async.map(json.wallets, function(walletData, callback) { iden.importWallet(wstr, password, opts.skipFields, function(err, w) { - if (err) return cb(err); + if (err) return callback(err); log.debug('Wallet ' + w.getId() + ' imported'); - - if (++i == l) { - iden.profile.walletInfos = walletInfoBackup; - iden.store(opts, function(err) { - if (err) { - return cb(err); - } else { - return cb(null, iden, iden.openWallets[0]); - } - }); + callback(); + }); + }, function(err, results) { + if (err) { + return cb(err); + } + iden.store(function(err) { + if (err) { + return cb(err); } - }) + return cb(null, iden, iden.openWallets[0]); + }); }); }; -/** - * @desc Return JSON with base64 encoded strings for wallets and profile, and iteration count - * @return {string} Stringify JSON - */ -Identity.prototype.exportAsJson = function() { - var ret = {}; - ret.iterations = this.storage.iterations; - ret.profile = this.profile.export(); - ret.wallets = {}; - - _.each(this.wallets, function(w) { - ret.wallets[w.getId()] = w.export(); - }); - - var r = JSON.stringify(ret); - return r; -}; - Identity.prototype.bindWallet = function(w) { var self = this; @@ -502,7 +372,6 @@ Identity.prototype.bindWallet = function(w) { */ Identity.prototype.createWallet = function(opts, cb) { preconditions.checkArgument(cb); - preconditions.checkState(this.profile); opts = opts || {}; opts.networkName = opts.networkName || 'testnet'; @@ -530,7 +399,7 @@ Identity.prototype.createWallet = function(opts, cb) { }); opts.publicKeyRing.addCopayer( opts.privateKey.deriveBIP45Branch().extendedPublicKeyString(), - opts.nickname || this.profile.getName() + opts.nickname || this.getName() ); log.debug('\t### PublicKeyRing Initialized'); @@ -550,35 +419,29 @@ Identity.prototype.createWallet = function(opts, cb) { opts.version = opts.version || this.version; var self = this; - var w = Identity._newWallet(opts); + var w = new Wallet(opts); this.addWallet(w, function(err) { if (err) return cb(err); self.bindWallet(w); - if (self.pluginManager.get && self.pluginManager.get('remote-backup')) { - self.pluginManager.get('remote-backup').store(self, self.insightSaveOpts, _.noop); - } - w.netStart(); - return cb(null, w); + self.storage.setItem(self.getId(), self.toObj(), function(error) { + if (error) { + return callback(error); + } + w.netStart(); + return cb(null, w); + }); }); }; - -// add wallet (import) Identity.prototype.addWallet = function(wallet, cb) { preconditions.checkArgument(wallet); preconditions.checkArgument(wallet.getId); preconditions.checkArgument(cb); - preconditions.checkState(this.profile); - var self = this; - self.profile.addWallet(wallet.getId(), { - name: wallet.name - }, function(err) { - if (err) return cb(err); - self.storeWallet(wallet, function(err) { - return cb(err); - }); - }); + this.wallets[wallet.getId()] = wallet; + + // TODO (eordano): Consider not saving automatically after this + this.storage.setItem(wallet.getStorageKey(), wallet.toObj(), cb); }; @@ -634,30 +497,38 @@ Identity.prototype.openWallet = function(walletId, cb) { }; -Identity.prototype.getOpenWallet = function(id) { - return this.wallets[id]; -}; - - -Identity.prototype.listWallets = function() { - var ret = this.profile.listWallets(); - return ret; +/** + * @param {string} walletId + * @returns {Wallet} + */ +Identity.prototype.getWalletById = function(walletId) { + return this.wallets[walletId]; }; /** - * @desc Deletes this wallet. This involves removing it from the storage instance + * @returns {Wallet[]} + */ +Identity.prototype.listWallets = function() { + return _.values(this.wallets); +}; + +/** + * @desc Deletes a wallet. This involves removing it from the storage instance + * * @param {string} walletId * @callback cb * @return {err} */ Identity.prototype.deleteWallet = function(walletId, cb) { var self = this; - Identity._walletDelete(walletId, this.storage, function(err) { - if (err) return cb(err); - self.profile.deleteWallet(walletId, function(err) { + + delete this.wallets[walletId]; + this.storage.deleteItem(walletId, function(err) { + if (err) { return cb(err); - }); - }) + } + self.store(cb); + }); }; /** @@ -671,6 +542,12 @@ Identity.prototype.decodeSecret = function(secret) { } }; +Identity.prototype.getLastFocusedWallet = function() { + return _.max(this.wallets, function(wallet) { + return wallet.lastTimestamp || 0; + }); +}; + /** * @callback walletCreationCallback * @param {?} err - an error, if any, that happened during the wallet creation @@ -720,7 +597,7 @@ Identity.prototype.joinWallet = function(opts, cb) { }; - var joinNetwork = Identity._newAsync(this.networkOpts[decodedSecret.networkName]); + var joinNetwork = opts.Async || new Async(this.networkOpts[decodedSecret.networkName]); // This is a hack to reconize if the connection was rejected or the peer wasn't there. var connectedOnce = false; @@ -751,7 +628,7 @@ Identity.prototype.joinWallet = function(opts, cb) { walletOpts.network = joinNetwork; walletOpts.privateKey = privateKey; - walletOpts.nickname = opts.nickname || self.profile.getName(); + walletOpts.nickname = opts.nickname || self.getName(); if (opts.password) walletOpts.password = opts.password; diff --git a/js/models/PluginManager.js b/js/models/PluginManager.js index 50e720e18..2a2788b61 100644 --- a/js/models/PluginManager.js +++ b/js/models/PluginManager.js @@ -30,7 +30,6 @@ var KIND_MULTIPLE = PluginManager.KIND_MULTIPLE = 2; PluginManager.TYPE = {}; PluginManager.TYPE['DB'] = KIND_UNIQUE; -PluginManager.TYPE['remote-backup'] = KIND_UNIQUE; PluginManager.prototype._register = function(obj, name) { preconditions.checkArgument(obj.type, 'Plugin has not type:' + name); diff --git a/js/models/Profile.js b/js/models/Profile.js deleted file mode 100644 index aeb2d6d79..000000000 --- a/js/models/Profile.js +++ /dev/null @@ -1,188 +0,0 @@ -'use strict'; -var preconditions = require('preconditions').singleton(); -var _ = require('underscore'); -var log = require('../log'); -var bitcore = require('bitcore'); - -function Profile(info, storage) { - preconditions.checkArgument(info.email); - preconditions.checkArgument(info.hash); - preconditions.checkArgument(storage); - preconditions.checkArgument(storage.setPassword, 'bad storage'); - - this.password = info.password; - this.hash = info.hash; - this.email = info.email; - this.extra = info.extra || {}; - this.walletInfos = info.walletInfos || {}; - - this.key = Profile.key(this.hash); - this.storage = storage; -}; - -Profile.hash = function(email) { - return bitcore.util.sha256ripe160(email).toString('hex'); -}; - -Profile.key = function(hash) { - return 'profile::' + hash; -}; - - -Profile.create = function(email, password, storage, cb) { - preconditions.checkArgument(cb); - preconditions.checkArgument(storage.setPassword); - - preconditions.checkState(storage.hasPassphrase()); - - var p = new Profile({ - email: email, - password: password, - hash: Profile.hash(email, password), - }, storage); - p.store({}, function(err) { - return cb(err, p); - }); -}; - - -Profile.open = function(email, password, storage, cb) { - preconditions.checkArgument(cb); - preconditions.checkState(storage.hasPassphrase()); - - var key = Profile.key(Profile.hash(email, password)); -console.log('[Profile.js.59:key:]',key); //TODO - storage.get(key, function(err, val) { - if (err || !val) - return cb(new Error('PNOTFOUND: Profile not found')); - - if (!val.email) - return cb(new Error('PERROR: Could not open profile')); - - return cb(null, new Profile(val, storage)); - }); -}; - -Profile.prototype.toObj = function() { - return _.clone(_.pick(this, 'password', 'hash', 'email', 'extra', 'walletInfos')); -}; - - -/* - * @desc Return a base64 encrypted version of the wallet - * @return {string} base64 encoded string - */ -Profile.prototype.export = function() { - var profObj = this.toObj(); - return this.storage.encrypt(profObj); -}; - -/* - * @desc Return a base64 encrypted version of the wallet - * @return {string} base64 encoded string - */ -Profile.import = function(str, password, storage) { - var obj = storage.decrypt(str,password) - return new Profile(obj, storage); -}; - -Profile.prototype.getWallet = function(walletId, cb) { - return this.walletInfos[walletId]; -}; - -Profile.prototype.listWallets = function() { - return _.sortBy(this.walletInfos, function(winfo) { - return winfo.createdTs; - }); -}; - - -Profile.prototype.deleteWallet = function(walletId, cb) { - if (!this.walletInfos[walletId]) - return cb(new Error('WNOEXIST: Wallet not on profile ')); - - delete this.walletInfos[walletId]; - this.store({ - overwrite: true - }, cb); -}; - -Profile.prototype.addToWallet = function(walletId, info, cb) { - if (!this.walletInfos[walletId]) - return cb(new Error('WNOEXIST: Wallet not on profile ')); - - this.walletInfos[walletId] = _.extend(this.walletInfos[walletId], info); - - this.store({ - overwrite: true - }, cb); -}; - - - -Profile.prototype.addWallet = function(walletId, info, cb) { - preconditions.checkArgument(cb); - - if (this.walletInfos[walletId]) - return cb(new Error('WEXIST: Wallet already on profile ')); - - this.walletInfos[walletId] = _.extend(info, { - createdTs: Date.now(), - id: walletId - }); - - this.store({ - overwrite: true - }, cb); -}; - - -Profile.prototype.setLastFocusedTs = function(walletId, cb) { - return this.addToWallet(walletId, { - lastFocusedTs: Date.now() - }, cb); -}; - -Profile.prototype.getLastFocusedWallet = function() { - var self = this; - var last; - var maxTs = _.max(_.pluck(self.walletInfos, 'lastFocusedTs')); - var wallets = _.values(self.walletInfos); - - last = _.findWhere(wallets, { - lastFocusedTs: maxTs - }); - - if (!last) { - last = _.last(wallets); - } - - return last ? last.id : null; -}; - -Profile.prototype.store = function(opts, cb) { - var self = this; - var val = self.toObj(); - var key = self.key; - - self.storage.get(key, function(val2) { - - if (val2 && !opts.overwrite) { - if (cb) - return cb(new Error('PEXISTS: Profile already exist ')) - } else { - self.storage.set(key, val, function(err) { - log.debug('Profile stored'); - if (cb) - cb(err); - }); - } - }); -}; - - -Profile.prototype.getName = function() { - return this.extra.nickname || this.email; -}; - -module.exports = Profile; diff --git a/js/models/Storage.js b/js/models/Storage.js deleted file mode 100644 index fe43d2580..000000000 --- a/js/models/Storage.js +++ /dev/null @@ -1,387 +0,0 @@ -'use strict'; -var preconditions = require('preconditions').singleton(); -var CryptoJS = require('node-cryptojs-aes').CryptoJS; -var bitcore = require('bitcore'); -var Passphrase = require('./Passphrase'); -var preconditions = require('preconditions').instance(); -var log = require('../log'); -var _ = require('underscore'); -var CACHE_DURATION = 1000 * 60 * 5; -var id = 0; - - -/* - * Storage wraps db plugin primitives - * with encryption functionalities - * and adds from some extra functionalities - * and a common interfase - */ -function Storage(opts) { - preconditions.checkArgument(opts); - preconditions.checkArgument(!opts.passphrase); - - this.wListCache = {}; - this.__uniqueid = ++id; - this.passphraseConfig = opts.passphraseConfig; - - if (opts.password) - this.setPassword(opts.password); - - try { - this.db = opts.db || localStorage; - this.sessionStorage = opts.sessionStorage || sessionStorage; - } catch (e) { - console.log('Error in storage:', e); - }; - - preconditions.checkState(this.db, 'No db defined'); - preconditions.checkState(this.sessionStorage, 'No sessionStorage defined'); -} - -var pps = {}; -Storage.prototype._getPassphrase = function() { - - if (!pps[this.__uniqueid]) - throw new Error('NOPASSPHRASE: No passphrase set'); - - return pps[this.__uniqueid]; -}; - -Storage.prototype.savePassphrase = function() { - if (!pps[this.__uniqueid]) - throw new Error('NOPASSPHRASE: No passphrase set'); - - this.savedPassphrase = this.savedPassphrase || {}; - this.savedPassphrase[this.__uniqueid] = { - pps: pps[this.__uniqueid], - iterations: this.iterations, - }; -}; - - -Storage.prototype.restorePassphrase = function() { - if (!this.savedPassphrase[this.__uniqueid]) - throw new Error('NOSTOREDPASSPHRASE: No stored passphrase'); - - this._setPassphrase(this.savedPassphrase[this.__uniqueid].pps, this.savedPassphrase[this.__uniqueid].iterations); -}; - -Storage.prototype.hasPassphrase = function() { - return pps[this.__uniqueid] ? true : false; -}; - - -Storage.prototype._setPassphrase = function(passphrase, iterations) { - pps[this.__uniqueid] = passphrase; - this.iterations = iterations; -}; - -Storage.prototype.setPassword = function(password, config) { - var passphraseConfig = _.extend(this.passphraseConfig, config); - var p = new Passphrase(passphraseConfig); - log.debug('Setting passphrase... Iterations:' + passphraseConfig.iterations); - this._setPassphrase(p.getBase64(password), passphraseConfig.iterations); - log.debug('done.') -} - -Storage.prototype._encrypt = function(string) { - var encrypted = CryptoJS.AES.encrypt(string, this._getPassphrase()); - var encryptedBase64 = encrypted.toString(); - return encryptedBase64; -}; - -Storage.prototype._decrypt = function(base64) { - var decryptedStr = null; - try { - var decrypted = CryptoJS.AES.decrypt(base64, this._getPassphrase()); - if (decrypted) - decryptedStr = decrypted.toString(CryptoJS.enc.Utf8); - } catch (e) { - // Error while decrypting - log.debug(e.message); - return null; - } - return decryptedStr; -}; - - -Storage.prototype._read = function(k, cb) { - preconditions.checkArgument(cb); - - var self = this; - this.db.getItem(k, function(ret) { - if (!ret) return cb(null); - ret = self._decrypt(ret); - if (!ret) return cb(null); - - ret = ret.toString(CryptoJS.enc.Utf8); - ret = JSON.parse(ret); - return cb(ret); - }); -}; - -Storage.prototype._write = function(k, v, cb) { - preconditions.checkArgument(cb); - - v = JSON.stringify(v); - v = this._encrypt(v); - this.db.setItem(k, v, cb); -}; - -// get value by key -Storage.prototype.getGlobal = function(k, cb) { - preconditions.checkArgument(cb); - - this.db.getItem(k, function(item) { - cb(item == 'undefined' ? undefined : item); - }); -}; - -// set value for key -Storage.prototype.setGlobal = function(k, v, cb) { - preconditions.checkArgument(cb); - this.db.setItem(k, typeof v === 'object' ? JSON.stringify(v) : v, cb); -}; - -// remove value for key -Storage.prototype.removeGlobal = function(k, cb) { - preconditions.checkArgument(cb); - this.db.removeItem(k, cb); -}; - -Storage.prototype.getSessionId = function(cb) { - preconditions.checkArgument(cb); - var self = this; - - self.sessionStorage.getItem('sessionId', function(sessionId) { - if (sessionId) - return cb(sessionId); - - sessionId = bitcore.SecureRandom.getRandomBuffer(8).toString('hex'); - self.sessionStorage.setItem('sessionId', sessionId, function() { - return cb(sessionId); - }); - }); -}; - -Storage.prototype.setSessionId = function(sessionId, cb) { - this.sessionStorage.setItem('sessionId', sessionId, cb); -}; - -Storage.prototype.get = function(key, cb) { - var self = this; - self._read(key, function(v) { - return cb(null, v); - }) -}; - -Storage.prototype.getFirst = function(prefix, opts, cb) { - opts = opts || {}; - - var self = this; - this.db.allKeys(function(allKeys) { - var keys = _.filter(allKeys, function(k) { - if ((k === prefix) || k.indexOf(prefix) === 0) return true; - }); - - if (keys.length === 0) - return cb(new Error('not found')); - - if (opts.onlyKey) - return cb(null, null, keys[0]); - - self._read(keys[0], function(v) { - if (_.isNull(v)) return cb(new Error('Could not decrypt data'), null, keys[0]); - return cb(null, v, keys[0]); - }) - }); -}; - -Storage.prototype.set = function(key, obj, cb) { - preconditions.checkArgument(key); - preconditions.checkArgument(cb); - this._write(key, obj, function() { - return cb(); - }); -}; - -Storage.prototype.delete = function(key, cb) { - preconditions.checkArgument(cb); - this.removeGlobal(key, function() { - return cb(); - }); -}; - -Storage.prototype.deletePrefix = function(prefix, cb) { - var self = this; - this.getFirst(prefix, {}, function(err, v, k) { - if (err || !v) return cb(err); - - self.delete(k, function(err) { - if (err) return cb(err); - self.deletePrefix(prefix, cb); - }) - }); -}; - - -Storage.prototype.clearAll = function(cb) { - this.sessionStorage.clear(); - this.db.clear(cb); -}; - -Storage.prototype.decrypt = function(base64, password, iterations) { - - if (password) { - this.savePassphrase(); - var opts = iterations ? {iterations: iterations} : {}; - - this.setPassword(password, opts); - } - - var decryptedStr = this._decrypt(base64); - var ret = JSON.parse(decryptedStr); - - if (password) - this.restorePassphrase(); - - return ret; -}; - -Storage.prototype.encrypt = function(obj) { - var string = JSON.stringify(obj); - return this._encrypt(string); -}; - -/* - * OLD functions, only for temporary backwards compatibility - */ - - -Storage.prototype.readWallet_Old = function(walletId, cb) { - var self = this; - this.db.allKeys(function(allKeys) { - var obj = {}; - var keys = _.filter(allKeys, function(k) { - if (k.indexOf(walletId + '::') === 0) return true; - }); - if (keys.length === 0) return cb(new Error('Wallet ' + walletId + ' not found')); - var count = keys.length; - _.each(keys, function(k) { - self._read(k, function(v) { - obj[k.split('::')[1]] = v; - if (--count === 0) return cb(null, obj); - }) - }); - }); -}; - - -Storage.prototype.deleteWallet_Old = function(walletId, cb) { - preconditions.checkArgument(walletId); - preconditions.checkArgument(cb); - var err; - var self = this; - - var toDelete = {}; - - this.db.allKeys(function(allKeys) { - for (var ii in allKeys) { - var key = allKeys[ii]; - var split = key.split('::'); - if (split.length == 2 && split[0] === walletId) { - toDelete[key] = 1; - }; - } - var l = Object.keys(toDelete).length, - j = 0; - if (!l) - return cb(new Error('WNOTFOUND: Wallet not found')); - - toDelete['nameFor::' + walletId] = 1; - l++; - - for (var i in toDelete) { - self.removeGlobal(i, function() { - if (++j == l) - return cb(err); - }); - - } - }); -}; - - -// TODO -Storage.prototype._getWalletIds_Old = function(cb) { - preconditions.checkArgument(cb); - var walletIds = []; - var uniq = {}; - this.db.allKeys(function(keys) { - for (var ii in keys) { - var key = keys[ii]; - var split = key.split('::'); - if (split.length == 2) { - var walletId = split[0]; - - if (!walletId || walletId === 'nameFor' || walletId === 'lock' || walletId === 'wallet') - continue; - - if (typeof uniq[walletId] === 'undefined') { - walletIds.push(walletId); - uniq[walletId] = 1; - } - } - } - return cb(walletIds); - }); -}; - -// TODO -Storage.prototype.getWallets1_Old = function(cb) { - preconditions.checkArgument(cb); - - if (this.wListCache.ts > Date.now()) - return cb(this.wListCache.data) - - var wallets = []; - var self = this; - - this._getWalletIds_Old(function(ids) { - var l = ids.length, - i = 0; - if (!l) - return cb([]); - - _.each(ids, function(id) { - self.getGlobal('nameFor::' + id, function(name) { - wallets.push({ - id: id, - name: name, - }); - if (++i == l) { - self.wListCache.data = wallets; - self.wListCache.ts = Date.now() + CACHE_DURATION; - return cb(wallets); - } - }); - }); - }); -}; - - -Storage.prototype.getWallets_Old = function(cb) { - var self = this; - self.getWallets2_Old(function(wallets) { - self.getWallets1_Old(function(wallets2) { - var ids = _.pluck(wallets, 'id'); - _.each(wallets2, function(w) { - if (!_.contains(ids, w.id)) - wallets.push(w); - }); - return cb(wallets); - }); - }) -}; - -module.exports = Storage; diff --git a/js/models/Wallet.js b/js/models/Wallet.js index 8ffcb34e7..68e664dd0 100644 --- a/js/models/Wallet.js +++ b/js/models/Wallet.js @@ -173,12 +173,14 @@ Wallet.COPAYER_PAIR_LIMITS = { 12: 1, }; - - Wallet.getStorageKey = function(str) { return 'wallet::' + str; }; +Wallet.prototype.getStorageKey = function() { + return Wallet.getStorageKey(this.getId()); +}; + /* for stubbing */ Wallet._newInsight = function(opts) { return new Insight(opts); @@ -219,27 +221,6 @@ Wallet.getMaxRequiredCopayers = function(totalCopayers) { return Wallet.COPAYER_PAIR_LIMITS[totalCopayers]; }; -/** - * delete - * - * @param walletId - * @param storage - * @param cb - * @return {undefined} - */ -// Wallet.delete = function(walletId, storage, cb) { -// preconditions.checkArgument(cb); -// storage.deletePrefix(Wallet.getStorageKey(walletId), function(err) { -// if (err && err.message != 'not found') return cb(err); -// storage.deletePrefix(walletId + '::', function(err) { -// if (err && err.message != 'not found') return cb(err); -// return cb(); -// }); -// }); -// }; -// - - /** * @desc obtain network name from serialized wallet * @param {Object} wallet object @@ -601,11 +582,14 @@ Wallet.prototype._onAddressBook = function(senderId, data) { * @desc Updates the wallet's last modified timestamp and triggers a save * @param {number} ts - the timestamp */ -Wallet.prototype.updateTimestamp = function(ts) { +Wallet.prototype.updateTimestamp = function(ts, callback) { preconditions.checkArgument(ts); preconditions.checkArgument(_.isNumber(ts)); this.lastTimestamp = ts; // we dont store here + if (callback) { + return callback(null); + } }; /** @@ -825,13 +809,10 @@ Wallet.prototype._lockIncomming = function() { this.network.lockIncommingConnections(this.publicKeyRing.getAllCopayerIds()); }; - - Wallet.prototype._setBlockchainListeners = function() { var self = this; self.blockchain.removeAllListeners(); - log.debug('Setting Blockchain listeners for', this.getId()); self.blockchain.on('reconnect', function(attempts) { log.debug('Wallet:' + self.id + 'blockchain reconnect event'); @@ -1018,8 +999,6 @@ Wallet.fromUntrustedObj = function(obj, readOpts) { return Wallet.fromObj(o,readOpts); }; - - /** * @desc Retrieve the wallet state from a trusted object * @@ -1110,15 +1089,6 @@ Wallet.fromObj = function(o, readOpts) { return new Wallet(opts); }; -/** - * @desc Return a base64 encrypted version of the wallet - * @return {string} base64 encoded string - */ -// Wallet.prototype.export = function() { -// var walletObj = this.toObj(); -// return this.storage.encrypt(walletObj); -// }; - /** * @desc Send a message to other peers * @param {string[]} recipients - the pubkey of the recipients of the message diff --git a/js/plugins/EncryptedInsightStorage.js b/js/plugins/EncryptedInsightStorage.js new file mode 100644 index 000000000..c1c3d998a --- /dev/null +++ b/js/plugins/EncryptedInsightStorage.js @@ -0,0 +1,27 @@ +var cryptoUtil = require('../util/crypto'); +var InsightStorage = require('./InsightStorage'); +var inherits = require('inherits'); + +function EncryptedInsightStorage(config) { + InsightStorage.apply(this, [config]); +} +inherits(EncryptedInsightStorage, InsightStorage); + +EncryptedInsightStorage.prototype.getItem = function(name, callback) { + var key = cryptoUtil.kdf(this.password, this.email); + InsightStorage.prototype.getItem.apply(this, [name, function(err, body) { + var decryptedJson = cryptoUtil.decrypt(key, body); + if (!decryptedJson) { + return callback('Internal Error'); + } + return callback(null, decryptedJson); + }]); +}; + +EncryptedInsightStorage.prototype.setItem = function(name, value, callback) { + var key = cryptoUtil.kdf(this.password, this.email); + var record = cryptoUtil.encrypt(key, value); + InsightStorage.prototype.setItem.apply(this, [name, record, callback]); +}; + +module.exports = EncryptedInsightStorage; diff --git a/plugins/GoogleDrive.js b/js/plugins/GoogleDrive.js similarity index 98% rename from plugins/GoogleDrive.js rename to js/plugins/GoogleDrive.js index 109fc2b8c..c98149584 100644 --- a/plugins/GoogleDrive.js +++ b/js/plugins/GoogleDrive.js @@ -3,7 +3,7 @@ var preconditions = require('preconditions').singleton(); var loaded = 0; var SCOPES = 'https://www.googleapis.com/auth/drive'; -var log = require('../js/log'); +var log = require('../log'); function GoogleDrive(config) { preconditions.checkArgument(config && config.clientId, 'No clientId at GoogleDrive config'); @@ -56,6 +56,9 @@ GoogleDrive.prototype.checkAuth = function() { this.handleAuthResult.bind(this)); }; +GoogleDrive.prototype.setCredentils = function(email, password, opts, callback) { +}; + /** * Called when authorization server replies. */ diff --git a/js/plugins/InsightStorage.js b/js/plugins/InsightStorage.js new file mode 100644 index 000000000..a4d13152a --- /dev/null +++ b/js/plugins/InsightStorage.js @@ -0,0 +1,75 @@ +var request = require('request'); +var cryptoUtil = require('../util/crypto'); +var querystring = require('querystring'); +var Identity = require('../models/Identity'); + +function InsightStorage(config) { + this.type = 'DB'; + this.storeUrl = config.url || 'https://insight.is/api/email'; + this.request = config.request || request; +} + +InsightStorage.prototype.init = function () {}; + +InsightStorage.prototype.setCredentials = function(email, password, opts) { + this.email = email; + this.password = password; +}; + +InsightStorage.prototype.getItem = function(name, callback) { + var key = cryptoUtil.kdf(this.password, this.email); + var secret = cryptoUtil.kdf(key, this.password); + var encodedEmail = encodeURIComponent(this.email); + var retrieveUrl = this.storeUrl + '/retrieve/' + encodedEmail; + this.request.get(retrieveUrl + '?' + querystring.encode({secret: secret, key: name}), + function(err, response, body) { + if (err) { + return callback('Connection error'); + } + if (response.statusCode !== 200) { + return callback('Connection error'); + } + return callback(null, body); + } + ); +}; + +InsightStorage.prototype.setItem = function(name, value, callback) { + var key = cryptoUtil.kdf(this.password, this.email); + var secret = cryptoUtil.kdf(key, this.password); + var registerUrl = this.storeUrl + '/register'; + this.request.post({ + url: registerUrl, + body: querystring.encode({ + key: name, + email: this.email, + secret: secret, + record: value + }) + }, function(err, response, body) { + if (err) { + return callback('Connection error'); + } + if (response.statusCode !== 200) { + return callback('Unable to store data on insight'); + } + return callback(); + }); +}; + +InsightStorage.prototype.removeItem = function(name, callback) { + this.setItem(name, '', callback); +}; + +InsightStorage.prototype.clear = function(callback) { + // NOOP + callback(); +}; + +InsightStorage.prototype.allKeys = function(callback) { + // NOOP + // TODO: Add functionality? + callback(); +}; + +module.exports = InsightStorage; diff --git a/plugins/LocalStorage.js b/js/plugins/LocalStorage.js similarity index 90% rename from plugins/LocalStorage.js rename to js/plugins/LocalStorage.js index d0fcebe84..b8af2ccd9 100644 --- a/plugins/LocalStorage.js +++ b/js/plugins/LocalStorage.js @@ -7,6 +7,9 @@ function LocalStorage() { LocalStorage.prototype.init = function() { }; +LocalStorage.prototype.setCredentials = function(email, password, opts) { +}; + LocalStorage.prototype.getItem = function(k,cb) { return cb(localStorage.getItem(k)); }; diff --git a/js/services/controllerUtils.js b/js/services/controllerUtils.js index eb5d46c39..211cfffc2 100644 --- a/js/services/controllerUtils.js +++ b/js/services/controllerUtils.js @@ -195,20 +195,19 @@ angular.module('copayApp.services') root.rebindWallets = function($scope, iden) { - _.each(iden.listWallets(), function(winfo) { - var w = iden.getOpenWallet(winfo.id); - preconditions.checkState(w); - root.installWalletHandlers($scope, w); + _.each(iden.listWallets(), function(wallet) { + preconditions.checkState(wallet); + root.installWalletHandlers($scope, wallet); }); }; root.setFocusedWallet = function(w) { if (!_.isObject(w)) - w = $rootScope.iden.getOpenWallet(w); + w = $rootScope.iden.getWalletById(w); preconditions.checkState(w && _.isObject(w)); $rootScope.wallet = w; - $rootScope.iden.profile.setLastFocusedTs(w.id, function() { + w.updateTimestamp(new Date().getTime(), function() { root.redirIfLogged(); root.updateBalance(w, function() { $rootScope.$digest(); @@ -226,8 +225,7 @@ angular.module('copayApp.services') } }; - - // On the focused wallet + // On the focused wallet root.updateAddressList = function(wid) { if (!wid || root.isFocusedWallet(wid)) { @@ -396,7 +394,7 @@ angular.module('copayApp.services') $rootScope.iden.deleteWallet(w.id, function() { notification.info('Wallet deleted', $filter('translate')('Wallet deleted')); $rootScope.wallet = null; - var lastFocused = $rootScope.iden.profile.getLastFocusedWallet(); + var lastFocused = $rootScope.iden.getLastFocusedWallet(); root.bindProfile($scope, $rootScope.iden, lastFocused); }); }; diff --git a/js/services/identityService.js b/js/services/identityService.js index c2fe34f93..dd94448c0 100644 --- a/js/services/identityService.js +++ b/js/services/identityService.js @@ -4,32 +4,17 @@ angular.module('copayApp.services') .factory('identityService', function($rootScope, $location, pluginManager, controllerUtils) { var root = {}; - root.check = function (scope) { - copay.Identity.anyProfile({ - pluginManager: pluginManager, - }, function(anyProfile) { - copay.Identity.anyWallet({ - pluginManager: pluginManager, - }, function(anyWallet) { - scope.retreiving = false; - scope.anyProfile = anyProfile ? true : false; - scope.anyWallet = anyWallet ? true : false; - - if (!scope.anyProfile) { - $location.path('/createProfile'); - } - }); - }); - }; - root.create = function (scope, form) { - copay.Identity.create(form.email.$modelValue, form.password.$modelValue, { + copay.Identity.create({ + email: form.email.$modelValue, + password: form.password.$modelValue, pluginManager: pluginManager, network: config.network, networkName: config.networkName, walletDefaults: config.wallet, passphraseConfig: config.passphraseConfig, - }, function(err, iden, firstWallet) { + }, function(err, iden) { + var firstWallet = iden.getLastFocusedWallet(); controllerUtils.bindProfile(scope, iden, firstWallet); scope.loading = false; }); @@ -37,19 +22,22 @@ angular.module('copayApp.services') root.open = function (scope, form) { - copay.Identity.open(form.email.$modelValue, form.password.$modelValue, { + copay.Identity.open({ + email: form.email.$modelValue, + password: form.password.$modelValue, pluginManager: pluginManager, network: config.network, networkName: config.networkName, walletDefaults: config.wallet, passphraseConfig: config.passphraseConfig, - }, function(err, iden, lastFocusedWallet) { + }, function(err, iden) { if (err && !iden) { console.log('Error:' + err) controllerUtils.onErrorDigest( scope, (err.toString() || '').match('PNOTFOUND') ? 'Profile not found' : 'Unknown error'); } else { - controllerUtils.bindProfile(scope, iden, lastFocusedWallet); + var firstWallet = iden.getLastFocusedWallet(); + controllerUtils.bindProfile(scope, iden, firstWallet); } scope.loading = false; }); diff --git a/js/services/rate.js b/js/services/rate.js index 68f96c313..6599905c1 100644 --- a/js/services/rate.js +++ b/js/services/rate.js @@ -1,18 +1,19 @@ 'use strict'; +var MINS_IN_HOUR = 60; +var MILLIS_IN_SECOND = 1000; + var RateService = function(request) { this.isAvailable = false; this.UNAVAILABLE_ERROR = 'Service is not available - check for service.isAvailable or use service.whenAvailable'; this.SAT_TO_BTC = 1 / 1e8; this.BTC_TO_SAT = 1e8; - var MINS_IN_HOUR = 60; - var MILLIS_IN_SECOND = 1000; var rateServiceConfig = config.rate; var updateFrequencySeconds = rateServiceConfig.updateFrequencySeconds || 60 * MINS_IN_HOUR; var rateServiceUrl = rateServiceConfig.url || 'https://bitpay.com/api/rates'; this.queued = []; this.alternatives = []; - var that = this; + var self = this; var backoffSeconds = 5; var retrieve = function() { request.get({ @@ -27,15 +28,15 @@ var RateService = function(request) { var rates = {}; listOfCurrencies.forEach(function(element) { rates[element.code] = element.rate; - that.alternatives.push({ + self.alternatives.push({ name: element.name, isoCode: element.code, rate: element.rate }); }); - that.isAvailable = true; - that.rates = rates; - that.queued.forEach(function(callback) { + self.isAvailable = true; + self.rates = rates; + self.queued.forEach(function(callback) { setTimeout(callback, 1); }); setTimeout(retrieve, updateFrequencySeconds * MILLIS_IN_SECOND); diff --git a/js/util/crypto.js b/js/util/crypto.js index 1a55dc57a..089d50263 100644 --- a/js/util/crypto.js +++ b/js/util/crypto.js @@ -1,7 +1,7 @@ /** * Small module for some helpers that wrap sjcl with some good practices. */ -var sjcl = require('../../lib/sjcl'); +var sjcl = require('sjcl'); var log = require('../log.js'); var _ = require('underscore'); @@ -26,6 +26,9 @@ module.exports = { * Encrypts symmetrically using a passphrase */ encrypt: function(key, message) { + if (!_.isString(message)) { + message = JSON.stringify(message); + } return sjcl.encrypt(key, message); }, diff --git a/plugins/InsightStorage.js b/plugins/InsightStorage.js deleted file mode 100644 index 5c0295462..000000000 --- a/plugins/InsightStorage.js +++ /dev/null @@ -1,60 +0,0 @@ -var request = require('request'); -var cryptoUtil = require('../js/util/crypto'); -var querystring = require('querystring'); -var Identity = require('../js/models/Identity'); - -function InsightStorage(config) { - this.type = 'remote-backup'; - this.storeUrl = config.url || 'https://insight.is/api/email'; - this.request = config.request || request; -} - -InsightStorage.prototype.init = function () {}; - -InsightStorage.prototype.retrieve = function(email, password, opts, callback) { - var key = cryptoUtil.kdf(password, email); - var secret = cryptoUtil.kdf(key, password); - var encodedEmail = encodeURIComponent(email); - var retrieveUrl = this.storeUrl + '/retrieve/' + encodedEmail; - this.request.get(retrieveUrl + '?' + querystring.encode({secret: secret}), - function(err, response, body) { - if (err) { - return callback('Connection error'); - } - if (response.statusCode !== 200) { - return callback('Connection error'); - } - var decryptedJson = cryptoUtil.decrypt(key, body); - if (!decryptedJson) { - return callback('Internal Error'); - } - return Identity.importFromJson(decryptedJson, password, opts, callback); - } - ); -}; - -InsightStorage.prototype.store = function(identity, opts, callback) { - var password = identity.profile.password; - var key = cryptoUtil.kdf(password, identity.profile.email); - var secret = cryptoUtil.kdf(key, password); - var record = cryptoUtil.encrypt(key, identity.exportAsJson()); - var registerUrl = this.storeUrl + '/register'; - this.request.post({ - url: registerUrl, - body: querystring.encode({ - email: identity.profile.email, - secret: secret, - record: record - }) - }, function(err, response, body) { - if (err) { - return callback('Connection error'); - } - if (response.statusCode !== 200) { - return callback('Unable to store data on insight'); - } - return callback(); - }); -}; - -module.exports = InsightStorage; diff --git a/util/build.js b/util/build.js index 1b9c4f276..4eeb8d252 100644 --- a/util/build.js +++ b/util/build.js @@ -62,9 +62,6 @@ var createBundle = function(opts) { b.require('./js/models/Identity', { expose: '../js/models/Identity' }); - b.require('./js/models/Profile', { - expose: '../js/models/Profile' - }); b.require('./js/models/Wallet'); b.require('./js/models/Wallet', { expose: '../../js/models/Wallet' @@ -95,15 +92,18 @@ var createBundle = function(opts) { }); if (!opts.disablePlugins) { - b.require('./plugins/GoogleDrive', { + b.require('./js/plugins/GoogleDrive', { expose: '../plugins/GoogleDrive' }); - b.require('./plugins/InsightStorage', { + b.require('./js/plugins/InsightStorage', { expose: '../plugins/InsightStorage' }); - b.require('./plugins/LocalStorage', { + b.require('./js/plugins/LocalStorage', { expose: '../plugins/LocalStorage' }); + b.require('./js/plugins/EncryptedInsightStorage', { + expose: '../plugins/EncryptedInsightStorage' + }); } b.require('./config', {