login working on the UX

This commit is contained in:
Matias Alejo Garcia 2014-10-01 08:35:17 -03:00
commit 92f1bacf82
10 changed files with 125 additions and 72 deletions

View file

@ -1,12 +1,28 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('HomeController', function($scope, $rootScope, $location, notification, controllerUtils) { angular.module('copayApp.controllers').controller('HomeController', function($scope, $rootScope, $location, notification, controllerUtils, pluginManager) {
controllerUtils.redirIfLogged(); controllerUtils.redirIfLogged();
//$scope.retreiving = true; $scope.openProfile = function(form) {
// identity.getWallets(function(err,ret) { $scope.loading = true;
// $scope.retreiving = false; copay.Identity.open(form.email.$modelValue, form.password.$modelValue, {
// $scope.hasWallets = (ret && ret.length > 0) ? true : false; pluginManager: pluginManager,
// }); network: config.network,
networkName: config.networkName,
walletDefaults: config.wallet,
passphrase: config.passphrase,
}, function(err, iden, w) {
if (err && !w) {
console.log('Error:' + err)
controllerUtils.onErrorDigest(
$scope, (err.toString()||'').match('PNOTFOUND') ? 'Profile not found' : 'Unknown error');
} else {
$scope.loading = false;
$rootScope.iden = iden;
$rootScope.wallet = w;
controllerUtils.bindWallet(w, $scope);
}
});
}
}); });

View file

@ -98,6 +98,13 @@ Identity._walletDelete = function(id, cb) {
return Wallet.delete(id, cb); return Wallet.delete(id, cb);
}; };
/* for stubbing */
Identity._openProfile = function(email, password, storage, cb) {
Profile.open(email, password, storage, cb);
};
/** /**
* creates and Identity * creates and Identity
* *
@ -160,10 +167,14 @@ Identity.prototype.validate = function(authcode, cb) {
Identity.open = function(email, password, opts, cb) { Identity.open = function(email, password, opts, cb) {
var iden = new Identity(email, password, opts); var iden = new Identity(email, password, opts);
Identity._createProfile(email, password, iden.storage, function(err, profile) { Identity._openProfile(email, password, iden.storage, function(err, profile) {
if (err) return cb(err); if (err) return cb(err);
iden.profile = profile; iden.profile = profile;
return cb(null, iden); var wid = iden.listWallets()[0].id;
iden.openWallet(wid, password, function(err, w) {
return cb(err, iden, w);
})
}); });
}; };
@ -217,15 +228,17 @@ Identity.prototype.store = function(opts, cb) {
Identity.prototype.close = function(cb) { Identity.prototype.close = function(cb) {
preconditions.checkState(this.profile); preconditions.checkState(this.profile);
var l = self.openWallets.length, var l = this.openWallets.length,
i = 0; i = 0;
if (!l) return cb(); if (!l) {
return cb ? cb() : null;
}
_.each(self.openWallets, function(w) { _.each(this.openWallets, function(w) {
w.close(function(err) { w.close(function(err) {
if (err) return cb(err); if (err) return cb(err);
if (++i == l) if (++i == l && cb)
return cb(); return cb();
}) })
}); });
@ -390,18 +403,19 @@ Identity.prototype.openWallet = function(walletId, password, cb) {
var self = this; var self = this;
self.storage.setPassword(password); self.storage.setPassword(password);
self.migrateWallet(walletId, password, function() { // TODO
// self.migrateWallet(walletId, password, function() {
Identity._walletRead(walletId, self.storage, self.networks, self.blockchains, [], function(err, w) { Identity._walletRead(walletId, self.storage, self.networks, self.blockchains, [], function(err, w) {
if (err) return cb(err); if (err) return cb(err);
w.store(function(err) { w.store(function(err) {
self.profile.setLastOpenedTs(walletId, function() { self.profile.setLastOpenedTs(walletId, function() {
return cb(err, w); return cb(err, w);
});
}); });
}); });
}); });
// });
}; };

View file

@ -13,9 +13,9 @@ function Profile(info, storage) {
this.hash = info.hash; this.hash = info.hash;
this.email = info.email; this.email = info.email;
this.extra = info.extra; this.extra = info.extra;
this.walletInfos = info.walletInfos || {};
this.key = Profile.key(this.hash); this.key = Profile.key(this.hash);
this.walletInfos = {};
this.storage = storage; this.storage = storage;
}; };
@ -36,29 +36,31 @@ Profile.create = function(email, password, storage, cb) {
var p = new Profile({ var p = new Profile({
email: email, email: email,
hash: Profile.hash(email,password), hash: Profile.hash(email, password),
}, storage); }, storage);
p.store({}, function(err) { p.store({}, function(err) {
return cb(err,p); return cb(err, p);
}); });
}; };
Profile.open = function(email, password, storage, cb) { Profile.open = function(email, password, storage, cb) {
preconditions.checkArgument(cb); preconditions.checkArgument(cb);
preconditions.checkState(storage.hasPassphrase());
var key = Profile.key(Profile.hash(email, password)); var key = Profile.key(Profile.hash(email, password));
storage.getGlobal(key, function(err, val) { storage.get(key, function(err, val) {
if (err) return cb(err); if (err || !val)
if (!val)
return cb(new Error('PNOTFOUND: Profile not found')); return cb(new Error('PNOTFOUND: Profile not found'));
return cb(new Profile(val, storage)); if (!val.email)
return cb(new Error('PERROR: Could not open profile'));
return cb(null, new Profile(val, storage));
}); });
}; };
Profile.prototype.toObj = function() { Profile.prototype.toObj = function() {
return JSON.parse(JSON.stringify(this)); return _.clone(_.pick(this, 'hash', 'email', 'extra', 'walletInfos'));
}; };
Profile.prototype.getWallet = function(walletId, cb) { Profile.prototype.getWallet = function(walletId, cb) {
@ -74,7 +76,7 @@ Profile.prototype.listWallets = function(opts, cb) {
Profile.prototype.deleteWallet = function(walletId, cb) { Profile.prototype.deleteWallet = function(walletId, cb) {
if (!this.walletInfos[walletId]) if (!this.walletInfos[walletId])
return cb(new Error('WNOEXIST: Wallet not on profile')); return cb(new Error('WNOEXIST: Wallet not on profile '));
delete this.walletInfos[walletId]; delete this.walletInfos[walletId];
@ -85,7 +87,7 @@ Profile.prototype.deleteWallet = function(walletId, cb) {
Profile.prototype.addToWallet = function(walletId, info, cb) { Profile.prototype.addToWallet = function(walletId, info, cb) {
if (!this.walletInfos[walletId]) if (!this.walletInfos[walletId])
return cb(new Error('WNOEXIST: Wallet not on profile')); return cb(new Error('WNOEXIST: Wallet not on profile '));
this.walletInfos[walletId] = _.extend(this.walletInfos[walletId], info); this.walletInfos[walletId] = _.extend(this.walletInfos[walletId], info);
@ -100,7 +102,7 @@ Profile.prototype.addWallet = function(walletId, info, cb) {
preconditions.checkArgument(cb); preconditions.checkArgument(cb);
if (this.walletInfos[walletId]) if (this.walletInfos[walletId])
return cb(new Error('WEXIST: Wallet already on profile')); return cb(new Error('WEXIST: Wallet already on profile '));
this.walletInfos[walletId] = _.extend(info, { this.walletInfos[walletId] = _.extend(info, {
createdTs: Date.now(), createdTs: Date.now(),
@ -128,7 +130,7 @@ Profile.prototype.store = function(opts, cb) {
if (val2 && !opts.overwrite) { if (val2 && !opts.overwrite) {
if (cb) if (cb)
return cb(new Error('PEXISTS: Profile already exist')) return cb(new Error('PEXISTS: Profile already exist '))
} else { } else {
self.storage.set(key, val, function(err) { self.storage.set(key, val, function(err) {
log.debug('Profile stored'); log.debug('Profile stored');

View file

@ -89,7 +89,7 @@ Storage.prototype._read = function(k, cb) {
var self = this; var self = this;
this.db.getItem(k, function(ret) { this.db.getItem(k, function(ret) {
if (!ret) return cb(null); if (!ret) return cb(null);
var ret = self._decrypt(ret); ret = self._decrypt(ret);
if (!ret) return cb(null); if (!ret) return cb(null);
ret = ret.toString(CryptoJS.enc.Utf8); ret = ret.toString(CryptoJS.enc.Utf8);

View file

@ -71,6 +71,7 @@ function Wallet(opts) {
self[k] = opts[k]; self[k] = opts[k];
}); });
this.id = opts.id || Wallet.getRandomId(); this.id = opts.id || Wallet.getRandomId();
this.secretNumber = opts.secretNumber || Wallet.getRandomNumber(); this.secretNumber = opts.secretNumber || Wallet.getRandomNumber();
this.lock = new WalletLock(this.storage, this.id, opts.lockTimeOutMin); this.lock = new WalletLock(this.storage, this.id, opts.lockTimeOutMin);
@ -94,10 +95,12 @@ function Wallet(opts) {
this.paymentRequests = opts.paymentRequests || {}; this.paymentRequests = opts.paymentRequests || {};
var networkName = Wallet.obtainNetworkName(opts);
this.network = networkName && this.network[networkName] ? this.network[networkName] : this.network;
this.blockchain = networkName && this.blockchain[networkName] ? this.blockchain[networkName] : this.blockchain;
var networkName = Wallet.obtainNetworkName(this); preconditions.checkArgument(this.network.setHexNonce, 'Incorrect network parameter');
this.network = _.isArray(this.network)? this.network[networkName] : this.network; preconditions.checkArgument(this.blockchain.getTransaction, 'Incorrect blockchain parameter');
this.blockchain = _.isArray(this.blockchain) ? this.blockchain[networkName] : this.blockchain;
this.network.maxPeers = this.totalCopayers; this.network.maxPeers = this.totalCopayers;
@ -217,8 +220,9 @@ Wallet.delete = function(walletId, storage, cb) {
* @param {function} callback - {err, Wallet} * @param {function} callback - {err, Wallet}
* @return {undefined} * @return {undefined}
*/ */
Wallet.read = function(walletId, storage, network, blockchain, skipFields, cb) { Wallet.read = function(walletId, storage, network, blockchain, skipFields, cb) {
preconditions.checkArgument(cb); preconditions.checkArgument(cb);
preconditions.checkArgument(storage.setPassword);
var self = this, var self = this,
err; err;
@ -237,10 +241,12 @@ Wallet.read = function(walletId, storage, network, blockchain, skipFields, cb)
var w, err; var w, err;
obj.id = walletId; obj.id = walletId;
try { try {
console.log('[Wallet.js.218:network:]',network); //TODO
w = self.fromObj(obj, storage, network, blockchain, skipFields); w = self.fromObj(obj, storage, network, blockchain, skipFields);
} catch (e) { } catch (e) {
log.debug("ERROR: ", e.message);
if (e && e.message && e.message.indexOf('MISSOPTS')) { if (e && e.message && e.message.indexOf('MISSOPTS')) {
err = new Error('WERROR: Could not read: ' + walletId); err = new Error('WERROR: Could not read: ' + walletId + ': ' + e.message);
} else { } else {
err = e; err = e;
} }
@ -1637,6 +1643,7 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) {
options = {}; options = {};
} }
console.log('[Wallet.js.1613:ntxid:]',ntxid); //TODO
var txp = this.txProposals.get(ntxid); var txp = this.txProposals.get(ntxid);
if (!txp) return; if (!txp) return;

View file

@ -83,6 +83,14 @@ describe('PayPro (in Wallet) model', function() {
c.networkName = walletConfig.networkName; c.networkName = walletConfig.networkName;
c.version = '0.0.1'; c.version = '0.0.1';
c.network = sinon.stub();
c.network.setHexNonce = sinon.stub();
c.network.setHexNonces = sinon.stub();
c.network.getHexNonce = sinon.stub();
c.network.getHexNonces = sinon.stub();
c.network.send = sinon.stub();
return new Wallet(c); return new Wallet(c);
} }
@ -683,9 +691,11 @@ describe('PayPro (in Wallet) model', function() {
}); });
}); });
it('#add tx proposal based on payment message via model', function(done) { it('#add tx proposal based on payment message via model ', function(done) {
var w = ppw; var w = ppw;
should.exist(w); should.exist(w);
w.sendPaymentTx(w._ntxid, function(txid, merchantData) { w.sendPaymentTx(w._ntxid, function(txid, merchantData) {
should.exist(txid); should.exist(txid);
should.exist(merchantData); should.exist(merchantData);

View file

@ -1894,12 +1894,13 @@ describe('Wallet model', function() {
var storage = new s(); var storage = new s();
var network = new Network(walletConfig.network); var network = new Network(walletConfig.network);
var blockchain = new Blockchain(walletConfig.blockchain); var blockchain = new Blockchain(walletConfig.blockchain);
storage.setPassword = sinon.stub();
it('should fail to read an unexisting wallet', function(done) { it('should fail to read an unexisting wallet', function(done) {
s.getFirst = sinon.stub().yields(null); storage.getFirst = sinon.stub().yields(null);
Wallet.read('123', s, network, blockchain, [], function(err, w) { Wallet.read('123', storage, network, blockchain, [], function(err, w) {
err.toString().should.contain('WNOTFOUND'); err.toString().should.contain('WNOTFOUND');
done(); done();
}); });
@ -1907,25 +1908,25 @@ describe('Wallet model', function() {
it('should not read a corrupted wallet', function(done) { it('should not read a corrupted wallet', function(done) {
s.getFirst = sinon.stub().yields(null, '{hola:1}'); storage.getFirst = sinon.stub().yields(null, '{hola:1}');
Wallet.read('123', s, network, blockchain, [], function(err, w) { Wallet.read('123', storage, network, blockchain, [], function(err, w) {
err.toString().should.contain('WERROR'); err.toString().should.contain('WERROR');
done(); done();
}); });
}); });
it('should read a wallet', function(done) { it('should read a wallet', function(done) {
s.getFirst = sinon.stub().yields(null, JSON.parse(o)); storage.getFirst = sinon.stub().yields(null, JSON.parse(o));
Wallet.read('123', s, network, blockchain, [], function(err, w) { Wallet.read('123', storage, network, blockchain, [], function(err, w) {
should.not.exist(err); should.not.exist(err);
done(); done();
}); });
}); });
it('should be able to import unencrypted legacy wallet TxProposal: v0', function(done) { it('should be able to import unencrypted legacy wallet TxProposal: v0', function(done) {
s.getFirst = sinon.stub().yields(null, JSON.parse(legacyO)); storage.getFirst = sinon.stub().yields(null, JSON.parse(legacyO));
Wallet.read('123', s, network, blockchain, [], function(err, w) { Wallet.read('123', storage, network, blockchain, [], function(err, w) {
should.exist(w); should.exist(w);
w.id.should.equal('55d4bd062d32f90a'); w.id.should.equal('55d4bd062d32f90a');
should.exist(w.publicKeyRing.getCopayerId); should.exist(w.publicKeyRing.getCopayerId);
@ -1936,9 +1937,9 @@ describe('Wallet model', function() {
}); });
it('should be able to import simple 1-of-1 encrypted legacy testnet wallet', function(done) { it('should be able to import simple 1-of-1 encrypted legacy testnet wallet', function(done) {
s.getFirst = sinon.stub().yields(null, JSON.parse(legacy1)); storage.getFirst = sinon.stub().yields(null, JSON.parse(legacy1));
Wallet.read('123', s, network, blockchain, [], function(err, w) { Wallet.read('123', storage, network, blockchain, [], function(err, w) {
should.exist(w); should.exist(w);
w.isReady().should.equal(true); w.isReady().should.equal(true);
var wo = w.toObj(); var wo = w.toObj();

View file

@ -33,10 +33,10 @@ describe('Identity model', function() {
beforeEach(function(done) { beforeEach(function(done) {
storage = sinon.stub(); storage = sinon.stub();
storage.getItem = sinon.stub(); storage.getItem = sinon.stub();
storage.setPassphrase = sinon.spy(); storage.setPassword = sinon.spy();
storage.hasPassphrase = sinon.stub().returns(true);
storage.getSessionId = sinon.spy(); storage.getSessionId = sinon.spy();
storage.setFromObj = sinon.spy(); storage.setFromObj = sinon.spy();
storage.setLastOpened = sinon.stub().yields(null);
storage.deletePrefix = sinon.stub().yields(null); storage.deletePrefix = sinon.stub().yields(null);
Identity._newStorage = sinon.stub().returns(storage); Identity._newStorage = sinon.stub().returns(storage);
@ -127,14 +127,17 @@ describe('Identity model', function() {
describe('#open', function(done) { describe('#open', function(done) {
beforeEach(function() { beforeEach(function() {
Identity._createProfile = sinon.stub().callsArgWith(3, null, 'kk'); storage.getFirst = sinon.stub().yields('wallet1234');
profile.listWallets = sinon.stub().returns([{id:'walletid'}]);
Identity._openProfile = sinon.stub().callsArgWith(3, null, profile);
Identity._walletRead = sinon.stub().callsArgWith(5, null, wallet);
}); });
it('should call ._createProfile', function(done) { it('should call ._openProfile', function(done) {
Identity.open(email, password, config, function(err, iden) { Identity.open(email, password, config, function(err, iden, w) {
Identity._openProfile.calledOnce.should.equal(true);
should.not.exist(err); should.not.exist(err);
iden.profile.should.equal('kk'); iden.profile.should.equal(profile);
Identity._createProfile.calledOnce.should.equal(true);
done(); done();
}); });
}); });
@ -154,7 +157,7 @@ describe('Identity model', function() {
it('should call .store from profile and wallets (2)', function(done) { it('should call .store from profile and wallets (2)', function(done) {
iden.profile.store = sinon.stub().yields(null); iden.profile.store = sinon.stub().yields(null);
iden.wallets = [{ iden.openWallets = [{
store: sinon.stub().yields(null) store: sinon.stub().yields(null)
}, { }, {
store: sinon.stub().yields(null) store: sinon.stub().yields(null)
@ -162,8 +165,8 @@ describe('Identity model', function() {
iden.store({}, function(err) { iden.store({}, function(err) {
should.not.exist(err); should.not.exist(err);
iden.profile.store.calledOnce.should.equal(true); iden.profile.store.calledOnce.should.equal(true);
iden.wallets[0].store.calledOnce.should.equal(true); iden.openWallets[0].store.calledOnce.should.equal(true);
iden.wallets[1].store.calledOnce.should.equal(true); iden.openWallets[1].store.calledOnce.should.equal(true);
done(); done();
}); });
}); });
@ -226,8 +229,7 @@ describe('Identity model', function() {
beforeEach(function() { beforeEach(function() {
iden.migrateWallet = sinon.stub().yields(null); iden.migrateWallet = sinon.stub().yields(null);
iden.profile.setLastOpened = sinon.stub().yields(null); storage.setPassword = sinon.spy();
iden.storage.setPassphrase = sinon.spy();
storage.getFirst = sinon.stub().yields('wallet1234'); storage.getFirst = sinon.stub().yields('wallet1234');
var wallet = sinon.stub(); var wallet = sinon.stub();
@ -236,14 +238,14 @@ describe('Identity model', function() {
Identity._walletRead = sinon.stub().callsArgWith(5, null, wallet); Identity._walletRead = sinon.stub().callsArgWith(5, null, wallet);
}); });
it('should call setPassphrase', function(done) { it('should call setPassword', function(done) {
var s1 = sinon.stub(); var s1 = sinon.stub();
s1.store = sinon.stub().yields(null); s1.store = sinon.stub().yields(null);
iden.openWallet('id123', 'xxx', function(err, w) { iden.openWallet('id123', 'xxx', function(err, w) {
iden.storage.setPassphrase.calledOnce.should.equal(true); iden.storage.setPassword.calledOnce.should.equal(true);
iden.storage.setPassphrase.getCall(0).args[0].should.equal('xxx'); iden.storage.setPassword.getCall(0).args[0].should.equal('xxx');
done(); done();
}); });
}); });
@ -254,7 +256,7 @@ describe('Identity model', function() {
should.not.exist(err); should.not.exist(err);
w.store.calledOnce.should.equal(true); w.store.calledOnce.should.equal(true);
iden.profile.setLastOpenedTs.calledTwice.should.equal(true); iden.profile.setLastOpenedTs.calledTwice.should.equal(true);
iden.migrateWallet.calledOnce.should.equal(true); // iden.migrateWallet.calledOnce.should.equal(true);
done(); done();
}); });
}); });
@ -332,7 +334,7 @@ describe('Identity model', function() {
var opts = { var opts = {
secret: '8WtTuiFTkhP5ao7AF2QErSwV39Cbur6pdMebKzQXFqL59RscXM', secret: '8WtTuiFTkhP5ao7AF2QErSwV39Cbur6pdMebKzQXFqL59RscXM',
nickname: 'test', nickname: 'test',
passphrase: 'pass' password: 'pass'
}; };
it('should yield bad network error', function(done) { it('should yield bad network error', function(done) {

View file

@ -19,7 +19,7 @@ describe('Profile model', function() {
}; };
beforeEach(function() { beforeEach(function() {
storage.setPassphrase = sinon.stub(); storage.setPassword = sinon.stub();
storage.set = sinon.stub(); storage.set = sinon.stub();
storage.set.yields(null); storage.set.yields(null);
storage.get = sinon.stub().yields(null); storage.get = sinon.stub().yields(null);

View file

@ -1,7 +1,8 @@
<div class="home" ng-controller="HomeController"> <div class="home" ng-controller="HomeController">
<P>( TODO1: only this form if there is any profile:: key) <P>( TODO: only show this login form if there is any profile:: key)
<p>( TODO2: if user has wallets (wallet::) show message: Copay now needs a profile to ... , you can import your wallets after creating your profile )
<p>( TODO: if user has wallets (wallet::) show message: Copay now needs a profile to ... , you can import your wallets after creating your profile )
<div data-alert class="loading-screen" ng-show="retreiving"> <div data-alert class="loading-screen" ng-show="retreiving">
<i class="size-60 fi-bitcoin-circle icon-rotate spinner"></i> <i class="size-60 fi-bitcoin-circle icon-rotate spinner"></i>
Retreiving information from storage... Retreiving information from storage...
@ -14,12 +15,12 @@
<div class="large-8 columns line-dashed-setup-v"> <div class="large-8 columns line-dashed-setup-v">
<div class="button-setup"> <div class="button-setup">
<h1 class="text-white line-sidebar-b" translate >Login </h1> <h1 class="text-white line-sidebar-b" translate >Login </h1>
<form name="settingsForm"> <form name="loginForm" ng-submit="openProfile(loginForm)" novalidate>
<fieldset> <fieldset>
<label for="insight-livenet">Email</label> <label for="insight-livenet">Email</label>
<input type="text" ng-model="profile.email" class="form-control" name="profile-email"> <input type="text" ng-model="email" class="form-control" name="email">
<label for="insight-testnet">Password</label> <label for="insight-testnet">Password</label>
<input type="text" ng-model="profile.password" class="form-control" name="profile-password"> <input type="text" ng-model="password" class="form-control" name="password">
</fieldset> </fieldset>
<div class="text-right"> <div class="text-right">
<button translate type="submit" class="button primary m0 ng-binding" ng-disabled="setupForm.$invalid || loading" disabled="disabled" ng-click="save()"> <button translate type="submit" class="button primary m0 ng-binding" ng-disabled="setupForm.$invalid || loading" disabled="disabled" ng-click="save()">