Add Insight save and register
This commit is contained in:
parent
6af23a995e
commit
5d980af518
9 changed files with 168 additions and 43 deletions
15
.jshint
Normal file
15
.jshint
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"camelcase": true,
|
||||||
|
"curly": true,
|
||||||
|
"eqeqeq": true,
|
||||||
|
"freeze": true,
|
||||||
|
"indent": 2,
|
||||||
|
"newcap": true,
|
||||||
|
"quotmark": "single",
|
||||||
|
"maxdepth": 3,
|
||||||
|
"maxstatements": 15,
|
||||||
|
"maxlen": 80,
|
||||||
|
"eqnull": true,
|
||||||
|
"funcscope": true,
|
||||||
|
"node": true
|
||||||
|
}
|
||||||
|
|
@ -3,15 +3,7 @@
|
||||||
angular.module('copayApp.controllers').controller('HomeController', function($scope, $rootScope, $location, notification, controllerUtils, pluginManager) {
|
angular.module('copayApp.controllers').controller('HomeController', function($scope, $rootScope, $location, notification, controllerUtils, pluginManager) {
|
||||||
controllerUtils.redirIfLogged();
|
controllerUtils.redirIfLogged();
|
||||||
|
|
||||||
$scope.retreiving = true;
|
$scope.retreiving = false;
|
||||||
copay.Identity.anyProfile({
|
|
||||||
pluginManager: pluginManager,
|
|
||||||
}, function(any) {
|
|
||||||
$scope.retreiving = false;
|
|
||||||
if (!any)
|
|
||||||
$location.path('/createProfile');
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
$scope.openProfile = function(form) {
|
$scope.openProfile = function(form) {
|
||||||
if (form && form.$invalid) {
|
if (form && form.$invalid) {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@ var preconditions = require('preconditions').singleton();
|
||||||
var _ = require('underscore');
|
var _ = require('underscore');
|
||||||
var log = require('../log');
|
var log = require('../log');
|
||||||
|
|
||||||
|
var querystring = require('querystring');
|
||||||
|
var request = require('request');
|
||||||
|
var cryptoUtil = require('../util/crypto');
|
||||||
var version = require('../../version').version;
|
var version = require('../../version').version;
|
||||||
var TxProposals = require('./TxProposals');
|
var TxProposals = require('./TxProposals');
|
||||||
var PublicKeyRing = require('./PublicKeyRing');
|
var PublicKeyRing = require('./PublicKeyRing');
|
||||||
|
|
@ -27,6 +30,8 @@ var Storage = module.exports.Storage = require('./Storage');
|
||||||
function Identity(password, opts) {
|
function Identity(password, opts) {
|
||||||
preconditions.checkArgument(opts);
|
preconditions.checkArgument(opts);
|
||||||
|
|
||||||
|
opts = _.extend({}, opts);
|
||||||
|
this.request = opts.request || request;
|
||||||
this.storage = Identity._getStorage(opts, password);
|
this.storage = Identity._getStorage(opts, password);
|
||||||
this.networkOpts = {
|
this.networkOpts = {
|
||||||
'livenet': opts.network.livenet,
|
'livenet': opts.network.livenet,
|
||||||
|
|
@ -37,6 +42,7 @@ function Identity(password, opts) {
|
||||||
'testnet': opts.network.testnet,
|
'testnet': opts.network.testnet,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.insightSaveOpts = opts.insightSave || {};
|
||||||
this.walletDefaults = opts.walletDefaults || {};
|
this.walletDefaults = opts.walletDefaults || {};
|
||||||
this.version = opts.version || version;
|
this.version = opts.version || version;
|
||||||
|
|
||||||
|
|
@ -50,8 +56,6 @@ Identity._createProfile = function(email, password, storage, cb) {
|
||||||
Profile.create(email, password, storage, cb);
|
Profile.create(email, password, storage, cb);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Identity._newStorage = function(opts) {
|
Identity._newStorage = function(opts) {
|
||||||
return new Storage(opts);
|
return new Storage(opts);
|
||||||
};
|
};
|
||||||
|
|
@ -82,8 +86,6 @@ Identity._newAsync = function(opts) {
|
||||||
return new Async(opts);
|
return new Async(opts);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Identity._getStorage = function(opts, password) {
|
Identity._getStorage = function(opts, password) {
|
||||||
var storageOpts = {};
|
var storageOpts = {};
|
||||||
|
|
||||||
|
|
@ -150,10 +152,16 @@ Identity.create = function(email, password, opts, cb) {
|
||||||
requiredCopayers: 1,
|
requiredCopayers: 1,
|
||||||
totalCopayers: 1,
|
totalCopayers: 1,
|
||||||
password: password,
|
password: password,
|
||||||
name: 'general',
|
name: 'general'
|
||||||
});
|
});
|
||||||
iden.createWallet(wopts, function(err, w) {
|
iden.createWallet(wopts, function(err, w) {
|
||||||
return cb(null, iden, w);
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
iden.registerOnInsight(iden.insightSaveOpts, function(error) {
|
||||||
|
// Ignore error
|
||||||
|
return cb(null, iden, w);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -186,7 +194,12 @@ Identity.open = function(email, password, opts, cb) {
|
||||||
var iden = new Identity(password, opts);
|
var iden = new Identity(password, opts);
|
||||||
|
|
||||||
Identity._openProfile(email, password, iden.storage, function(err, profile) {
|
Identity._openProfile(email, password, iden.storage, function(err, profile) {
|
||||||
if (err) return cb(err);
|
if (err) {
|
||||||
|
if (err.message && err.message.indexOf('PNOTFOUND') !== -1) {
|
||||||
|
return Identity.readFromInsight(email, password, opts, cb);
|
||||||
|
}
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
iden.profile = profile;
|
iden.profile = profile;
|
||||||
|
|
||||||
var wids = _.pluck(iden.listWallets(), 'id');
|
var wids = _.pluck(iden.listWallets(), 'id');
|
||||||
|
|
@ -335,15 +348,14 @@ Identity.prototype.closeWallet = function(wid, cb) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Identity.importFromJson = function(str, password, opts, cb) {
|
||||||
|
|
||||||
/**
|
|
||||||
* @desc Return a base64 encrypted version of the wallet
|
|
||||||
* @return {string} base64 encoded string
|
|
||||||
*/
|
|
||||||
Identity.import = function(str, password, opts, cb) {
|
|
||||||
preconditions.checkArgument(str);
|
preconditions.checkArgument(str);
|
||||||
var json = JSON.parse(str);
|
var json;
|
||||||
|
try {
|
||||||
|
json = JSON.parse(str);
|
||||||
|
} catch (e) {
|
||||||
|
return cb('Unable to retrieve json from string', str);
|
||||||
|
}
|
||||||
|
|
||||||
if (!_.isNumber(json.iterations))
|
if (!_.isNumber(json.iterations))
|
||||||
return cb('BADSTR: Missing iterations');
|
return cb('BADSTR: Missing iterations');
|
||||||
|
|
@ -351,25 +363,34 @@ Identity.import = function(str, password, opts, cb) {
|
||||||
if (!json.profile)
|
if (!json.profile)
|
||||||
return cb('BADSTR: Missing profile');
|
return cb('BADSTR: Missing profile');
|
||||||
|
|
||||||
|
|
||||||
var iden = new Identity(password, opts);
|
var iden = new Identity(password, opts);
|
||||||
iden.profile = Profile.import(json.profile, password, iden.storage);
|
iden.profile = Profile.import(json.profile, password, iden.storage);
|
||||||
|
|
||||||
json.wallets = json.wallets || {};
|
json.wallets = json.wallets || {};
|
||||||
|
var walletInfoBackup = iden.profile.walletInfos;
|
||||||
|
iden.profile.walletInfos = {};
|
||||||
|
|
||||||
var l = json.wallets.length,
|
var l = _.size(json.wallets),
|
||||||
i = 0;
|
i = 0;
|
||||||
|
|
||||||
if (!l)
|
if (!l)
|
||||||
return cb(null, iden);
|
return cb(null, iden);
|
||||||
|
|
||||||
_.each(this.wallets, function(wstr) {
|
_.each(json.wallets, function(wstr) {
|
||||||
iden.importWallet(wstr, password, skipFields, function(err, w) {
|
iden.importWallet(wstr, password, opts.skipFields, function(err, w) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
log.debug('Wallet ' + w.getId() + ' imported');
|
log.debug('Wallet ' + w.getId() + ' imported');
|
||||||
|
|
||||||
if (++i == l)
|
if (++i == l) {
|
||||||
iden.store(cb);
|
iden.profile.walletInfos = walletInfoBackup;
|
||||||
|
iden.store(opts, function(err) {
|
||||||
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
} else {
|
||||||
|
return cb(null, iden, iden.openWallets[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -378,7 +399,7 @@ Identity.import = function(str, password, opts, cb) {
|
||||||
* @desc Return JSON with base64 encoded strings for wallets and profile, and iteration count
|
* @desc Return JSON with base64 encoded strings for wallets and profile, and iteration count
|
||||||
* @return {string} Stringify JSON
|
* @return {string} Stringify JSON
|
||||||
*/
|
*/
|
||||||
Identity.prototype.export = function() {
|
Identity.prototype.exportAsJson = function() {
|
||||||
var ret = {};
|
var ret = {};
|
||||||
ret.iterations = this.storage.iterations;
|
ret.iterations = this.storage.iterations;
|
||||||
ret.profile = this.profile.export();
|
ret.profile = this.profile.export();
|
||||||
|
|
@ -470,8 +491,59 @@ Identity.prototype.createWallet = function(opts, cb) {
|
||||||
this.addWallet(w, function(err) {
|
this.addWallet(w, function(err) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
self.openWallets.push(w);
|
self.openWallets.push(w);
|
||||||
w.netStart();
|
self.triggerInsightSave(self.insightSaveOpts, function(error) {
|
||||||
return cb(err, w);
|
// Ignore error
|
||||||
|
w.netStart();
|
||||||
|
return cb(null, w);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Identity.readFromInsight = function(email, password, opts, callback) {
|
||||||
|
var key = cryptoUtil.kdf(password, email);
|
||||||
|
var secret = cryptoUtil.kdf(key, password);
|
||||||
|
var useRequest = opts.request || request;
|
||||||
|
var encodedEmail = encodeURIComponent(email);
|
||||||
|
var retrieveUrl = opts.retrieveUrl || 'http://localhost:3001/api/email/retrieve/' + encodedEmail;
|
||||||
|
useRequest.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);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Identity.prototype.triggerInsightSave = Identity.prototype.registerOnInsight = function(opts, callback) {
|
||||||
|
var password = this.profile.password;
|
||||||
|
var key = cryptoUtil.kdf(password, this.profile.email);
|
||||||
|
var secret = cryptoUtil.kdf(key, password);
|
||||||
|
var exportData = this.exportAsJson
|
||||||
|
var record = cryptoUtil.encrypt(key, this.exportAsJson());
|
||||||
|
var registerUrl = opts.registerUrl || 'http://localhost:3001/api/email/register';
|
||||||
|
this.request.post({
|
||||||
|
url: registerUrl,
|
||||||
|
body: querystring.encode({
|
||||||
|
email: this.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();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -486,11 +558,8 @@ Identity.prototype.addWallet = function(wallet, cb) {
|
||||||
self.profile.addWallet(wallet.getId(), {
|
self.profile.addWallet(wallet.getId(), {
|
||||||
name: wallet.name
|
name: wallet.name
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
|
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
wallet.store(function(err) {
|
cb();
|
||||||
return cb(err);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ function Profile(info, storage) {
|
||||||
preconditions.checkArgument(storage);
|
preconditions.checkArgument(storage);
|
||||||
preconditions.checkArgument(storage.setPassword, 'bad storage');
|
preconditions.checkArgument(storage.setPassword, 'bad storage');
|
||||||
|
|
||||||
|
this.password = info.password;
|
||||||
this.hash = info.hash;
|
this.hash = info.hash;
|
||||||
this.email = info.email;
|
this.email = info.email;
|
||||||
this.extra = info.extra || {};
|
this.extra = info.extra || {};
|
||||||
|
|
@ -19,8 +20,8 @@ function Profile(info, storage) {
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
};
|
};
|
||||||
|
|
||||||
Profile.hash = function(email, password) {
|
Profile.hash = function(email) {
|
||||||
return bitcore.util.sha256ripe160(email + password).toString('hex');
|
return bitcore.util.sha256ripe160(email).toString('hex');
|
||||||
};
|
};
|
||||||
|
|
||||||
Profile.key = function(hash) {
|
Profile.key = function(hash) {
|
||||||
|
|
@ -36,6 +37,7 @@ Profile.create = function(email, password, storage, cb) {
|
||||||
|
|
||||||
var p = new Profile({
|
var p = new Profile({
|
||||||
email: email,
|
email: email,
|
||||||
|
password: password,
|
||||||
hash: Profile.hash(email, password),
|
hash: Profile.hash(email, password),
|
||||||
}, storage);
|
}, storage);
|
||||||
p.store({}, function(err) {
|
p.store({}, function(err) {
|
||||||
|
|
@ -67,7 +69,7 @@ Profile.open = function(email, password, storage, cb) {
|
||||||
};
|
};
|
||||||
|
|
||||||
Profile.prototype.toObj = function() {
|
Profile.prototype.toObj = function() {
|
||||||
return _.clone(_.pick(this, 'hash', 'email', 'extra', 'walletInfos'));
|
return _.clone(_.pick(this, 'password', 'hash', 'email', 'extra', 'walletInfos'));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('copayApp.services').factory('pluginManager', function(angularLoad){
|
angular.module('copayApp.services').factory('pluginManager', function(angularLoad) {
|
||||||
var pm = new copay.PluginManager(config);
|
var pm = new copay.PluginManager(config);
|
||||||
var scripts = pm.scripts;
|
var scripts = pm.scripts;
|
||||||
|
|
||||||
|
|
|
||||||
44
js/util/crypto.js
Normal file
44
js/util/crypto.js
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Small module for some helpers that wrap CryptoJS with some good practices.
|
||||||
|
*/
|
||||||
|
var sjcl = require('sjcl');
|
||||||
|
var log = require('../log.js');
|
||||||
|
var _ = require('underscore');
|
||||||
|
|
||||||
|
var SALT = 'copay random string NWRlNmExMTE4NzIzYzYyYWMwODU1MTdkN';
|
||||||
|
var SEPARATOR = '&';
|
||||||
|
var defaultOptions = {
|
||||||
|
adata: '',
|
||||||
|
cipher: 'aes',
|
||||||
|
ks: 128,
|
||||||
|
iter: 2000,
|
||||||
|
mode: 'ccm',
|
||||||
|
ts: 64
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
kdf: function(value1, value2) {
|
||||||
|
return sjcl.codec.base64.fromBits(sjcl.misc.pbkdf2(value1 + value2, SALT));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypts symmetrically using a passphrase
|
||||||
|
*/
|
||||||
|
encrypt: function(key, message) {
|
||||||
|
return sjcl.encrypt(key, message);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypts symmetrically using a passphrase
|
||||||
|
*/
|
||||||
|
decrypt: function(key, cypher) {
|
||||||
|
var output = {};
|
||||||
|
try {
|
||||||
|
return sjcl.decrypt(key, cypher);
|
||||||
|
} catch (e) {
|
||||||
|
log.error('Decryption failed due to error: ' + e.message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -25,6 +25,7 @@
|
||||||
"inherits": "^2.0.1",
|
"inherits": "^2.0.1",
|
||||||
"optimist": "^0.6.1",
|
"optimist": "^0.6.1",
|
||||||
"preconditions": "^1.0.7",
|
"preconditions": "^1.0.7",
|
||||||
|
"querystring": "^0.2.0",
|
||||||
"request": "^2.40.0",
|
"request": "^2.40.0",
|
||||||
"underscore": "^1.7.0"
|
"underscore": "^1.7.0"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ function LocalStorage() {
|
||||||
LocalStorage.prototype.init = function() {
|
LocalStorage.prototype.init = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
LocalStorage.prototype.getItem = function(k,cb) {
|
LocalStorage.prototype.getItem = function(k,cb) {
|
||||||
return cb(localStorage.getItem(k));
|
return cb(localStorage.getItem(k));
|
||||||
};
|
};
|
||||||
|
|
@ -37,5 +36,4 @@ LocalStorage.prototype.allKeys = function(cb) {
|
||||||
return cb(ret);
|
return cb(ret);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
module.exports = LocalStorage;
|
module.exports = LocalStorage;
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ var createBundle = function(opts) {
|
||||||
expose: 'request'
|
expose: 'request'
|
||||||
});
|
});
|
||||||
b.require('underscore');
|
b.require('underscore');
|
||||||
|
b.require('querystring');
|
||||||
b.require('assert');
|
b.require('assert');
|
||||||
b.require('preconditions');
|
b.require('preconditions');
|
||||||
|
|
||||||
|
|
@ -86,6 +87,9 @@ var createBundle = function(opts) {
|
||||||
b.require('./js/models/PluginManager', {
|
b.require('./js/models/PluginManager', {
|
||||||
expose: '../js/models/PluginManager'
|
expose: '../js/models/PluginManager'
|
||||||
});
|
});
|
||||||
|
b.require('./js/util/crypto', {
|
||||||
|
expose: '../util/crypto'
|
||||||
|
});
|
||||||
|
|
||||||
if (!opts.disablePlugins) {
|
if (!opts.disablePlugins) {
|
||||||
b.require('./plugins/GoogleDrive', {
|
b.require('./plugins/GoogleDrive', {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue