diff --git a/src/js/app.js b/src/js/app.js
index d0ec6ac..e37b86c 100644
--- a/src/js/app.js
+++ b/src/js/app.js
@@ -7,6 +7,7 @@ requirejs([
'js/controller/popover',
'js/controller/add-account',
'js/controller/account',
+ 'js/controller/contacts',
'js/controller/login',
'js/controller/login-initial',
'js/controller/login-new-device',
@@ -24,6 +25,7 @@ requirejs([
PopoverCtrl,
AddAccountCtrl,
AccountCtrl,
+ ContactsCtrl,
LoginCtrl,
LoginInitialCtrl,
LoginNewDeviceCtrl,
@@ -32,14 +34,25 @@ requirejs([
ReadCtrl,
WriteCtrl,
NavigationCtrl,
- util) {
+ util
+) {
'use strict';
// reset window.name
window.name = util.UUID();
// init main angular module including dependencies
- var app = angular.module('mail', ['ngRoute', 'ngTouch', 'navigation', 'mail-list', 'write', 'read', 'login-new-device', 'popover']);
+ var app = angular.module('mail', [
+ 'ngRoute',
+ 'ngTouch',
+ 'navigation',
+ 'mail-list',
+ 'write',
+ 'read',
+ 'contacts',
+ 'login-new-device',
+ 'popover'
+ ]);
// set router paths
app.config(function($routeProvider) {
@@ -77,6 +90,7 @@ requirejs([
app.controller('WriteCtrl', WriteCtrl);
app.controller('MailListCtrl', MailListCtrl);
app.controller('AccountCtrl', AccountCtrl);
+ app.controller('ContactsCtrl', ContactsCtrl);
app.controller('DialogCtrl', DialogCtrl);
app.controller('PopoverCtrl', PopoverCtrl);
diff --git a/src/js/controller/contacts.js b/src/js/controller/contacts.js
new file mode 100644
index 0000000..d1162f2
--- /dev/null
+++ b/src/js/controller/contacts.js
@@ -0,0 +1,124 @@
+define(function(require) {
+ 'use strict';
+
+ var angular = require('angular'),
+ _ = require('underscore'),
+ appController = require('js/app-controller'),
+ keychain, pgp;
+
+ //
+ // Controller
+ //
+
+ var ContactsCtrl = function($scope) {
+ keychain = appController._keychain,
+ pgp = appController._crypto;
+
+ $scope.state.contacts = {
+ open: false,
+ toggle: function(to) {
+ this.open = to;
+ $scope.listKeys();
+ }
+ };
+
+ // set default value so that the popover height is correct on init
+ $scope.fingerprint = 'XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX';
+
+ //
+ // scope functions
+ //
+
+ $scope.listKeys = function() {
+ keychain.listLocalPublicKeys(function(err, keys) {
+ if (err) {
+ $scope.onError(err);
+ return;
+ }
+
+ keys.forEach(addParams);
+
+ $scope.keys = keys;
+ $scope.$apply();
+
+ function addParams(key) {
+ var params = pgp.getKeyParams(key.publicKey);
+ _.extend(key, params);
+ }
+ });
+ };
+
+ $scope.getFingerprint = function(key) {
+ var fpr = key.fingerprint;
+ var formatted = fpr.slice(0, 4) + ' ' + fpr.slice(4, 8) + ' ' + fpr.slice(8, 12) + ' ' + fpr.slice(12, 16) + ' ' + fpr.slice(16, 20) + ' ... ' + fpr.slice(20, 24) + ' ' + fpr.slice(24, 28) + ' ' + fpr.slice(28, 32) + ' ' + fpr.slice(32, 36) + ' ' + fpr.slice(36);
+
+ $scope.fingerprint = formatted;
+ };
+
+ $scope.importKey = function(publicKeyArmored) {
+ var keyParams = pgp.getKeyParams(publicKeyArmored);
+ var pubkey = {
+ _id: keyParams._id,
+ userId: keyParams.userId,
+ publicKey: publicKeyArmored
+ };
+
+ keychain.saveLocalPublicKey(pubkey, function(err) {
+ if (err) {
+ $scope.onError(err);
+ return;
+ }
+
+ // update displayed keys
+ $scope.listKeys();
+ });
+ };
+
+ $scope.removeKey = function(key) {
+ keychain.removeLocalPublicKey(key._id, function(err) {
+ if (err) {
+ $scope.onError(err);
+ return;
+ }
+
+ // update displayed keys
+ $scope.listKeys();
+ });
+ };
+ };
+
+ //
+ // Directives
+ //
+
+ var ngModule = angular.module('contacts', []);
+
+ ngModule.directive('keyfileInput', function() {
+ return function(scope, elm) {
+ elm.on('change', function(e) {
+ for (var i = 0; i < e.target.files.length; i++) {
+ importKey(e.target.files.item(i));
+ }
+ });
+
+ function importKey(file) {
+ var reader = new FileReader();
+ reader.onload = function(e) {
+ scope.importKey(e.target.result);
+ };
+ reader.readAsText(file);
+ }
+ };
+ });
+
+ ngModule.directive('keyfileBtn', function() {
+ return function(scope, elm) {
+ elm.on('click touchstart', function(e) {
+ e.preventDefault();
+ document.querySelector('#keyfile-input').click();
+ });
+ };
+ });
+
+ return ContactsCtrl;
+});
\ No newline at end of file
diff --git a/src/js/controller/navigation.js b/src/js/controller/navigation.js
index 5a3af95..73f3c5e 100644
--- a/src/js/controller/navigation.js
+++ b/src/js/controller/navigation.js
@@ -172,6 +172,11 @@ define(function(require) {
e.preventDefault();
scope.state.account.toggle(false);
+ } else if (e.keyCode === 27 && scope.state.contacts.open) {
+ // escape -> close contacts view
+ e.preventDefault();
+ scope.state.contacts.toggle(false);
+
} else if (e.keyCode === 27 && scope.state.nav.open) {
// escape -> close nav view
e.preventDefault();
diff --git a/src/js/crypto/pgp.js b/src/js/crypto/pgp.js
index 023672c..a515047 100644
--- a/src/js/crypto/pgp.js
+++ b/src/js/crypto/pgp.js
@@ -68,13 +68,8 @@ define(function(require) {
return fingerprint(this._publicKey);
};
- PGP.prototype.getUserId = function(keyArmored) {
- var key = openpgp.key.readArmored(keyArmored).keys[0];
- return key.getUserIds()[0];
- };
-
/**
- * Show a user's key id
+ * Show a user's key id.
*/
PGP.prototype.getKeyId = function(keyArmored) {
var key, pubKeyId, privKeyId;
@@ -100,6 +95,23 @@ define(function(require) {
return pubKeyId;
};
+ /**
+ * Read all relevant params of an armored key.
+ */
+ PGP.prototype.getKeyParams = function(keyArmored) {
+ var key = openpgp.key.readArmored(keyArmored).keys[0],
+ packet = key.getKeyPacket();
+
+ return {
+ _id: packet.getKeyId().toHex().toUpperCase(),
+ userId: key.getUserIds()[0].split('<')[1].split('>')[0],
+ fingerprint: util.hexstrdump(packet.getFingerprint()).toUpperCase(),
+ algorithm: packet.algorithm,
+ bitSize: packet.getBitSize(),
+ created: packet.created,
+ };
+ };
+
/**
* Import the user's key pair
*/
diff --git a/src/js/dao/email-dao.js b/src/js/dao/email-dao.js
index aeb0d9c..b6668a5 100644
--- a/src/js/dao/email-dao.js
+++ b/src/js/dao/email-dao.js
@@ -156,8 +156,8 @@ define(function(require) {
}
// check if the key's user ID matches the current account
- pubUserID = self._crypto.getUserId(keypair.publicKey.publicKey);
- privUserID = self._crypto.getUserId(keypair.privateKey.encryptedKey);
+ pubUserID = self._crypto.getKeyParams(keypair.publicKey.publicKey).userId;
+ privUserID = self._crypto.getKeyParams(keypair.privateKey.encryptedKey).userId;
if (pubUserID.indexOf(self._account.emailAddress) === -1 || privUserID.indexOf(self._account.emailAddress) === -1) {
callback({
errMsg: 'User IDs dont match!'
@@ -1342,4 +1342,4 @@ define(function(require) {
};
return EmailDAO;
-});
+});
\ No newline at end of file
diff --git a/src/js/dao/keychain-dao.js b/src/js/dao/keychain-dao.js
index c98a760..7669460 100644
--- a/src/js/dao/keychain-dao.js
+++ b/src/js/dao/keychain-dao.js
@@ -256,31 +256,43 @@ define(function(require) {
return;
}
- if (!pubkey) {
- // fetch from cloud storage
- self._publicKeyDao.get(id, function(err, cloudPubkey) {
+ if (pubkey) {
+ callback(null, pubkey);
+ return;
+ }
+
+ // fetch from cloud storage
+ self._publicKeyDao.get(id, function(err, cloudPubkey) {
+ if (err) {
+ callback(err);
+ return;
+ }
+
+ // cache public key in cache
+ self.saveLocalPublicKey(cloudPubkey, function(err) {
if (err) {
callback(err);
return;
}
- // cache public key in cache
- self.saveLocalPublicKey(cloudPubkey, function(err) {
- if (err) {
- callback(err);
- return;
- }
-
- callback(null, cloudPubkey);
- });
+ callback(null, cloudPubkey);
});
-
- } else {
- callback(null, pubkey);
- }
+ });
});
};
+ /**
+ * List all the locally stored public keys
+ */
+ KeychainDAO.prototype.listLocalPublicKeys = function(callback) {
+ // search local keyring for public key
+ this._localDbDao.list('publickey', 0, null, callback);
+ };
+
+ KeychainDAO.prototype.removeLocalPublicKey = function(id, callback) {
+ this._localDbDao.remove('publickey_' + id, callback);
+ };
+
KeychainDAO.prototype.lookupPrivateKey = function(id, callback) {
// lookup in local storage
this._localDbDao.read('privatekey_' + id, callback);
diff --git a/src/sass/all.scss b/src/sass/all.scss
index 5c5f594..cb33f9d 100755
--- a/src/sass/all.scss
+++ b/src/sass/all.scss
@@ -24,6 +24,7 @@
@import "views/shared";
@import "views/add-account";
@import "views/account";
+@import "views/contacts";
@import "views/dialog";
@import "views/navigation";
@import "views/mail-list";
diff --git a/src/sass/components/_buttons.scss b/src/sass/components/_buttons.scss
index 024ce38..13051dd 100755
--- a/src/sass/components/_buttons.scss
+++ b/src/sass/components/_buttons.scss
@@ -20,6 +20,7 @@
transition: background-color 0.3s;
text-decoration: none;
font-weight: normal;
+ outline: 0;
&:hover,
&:focus {
@@ -28,7 +29,6 @@
&:active,
&.active {
- outline: 0;
background-image: none;
box-shadow: none;
top: 1px;
diff --git a/src/sass/views/_contacts.scss b/src/sass/views/_contacts.scss
new file mode 100644
index 0000000..a4bd33b
--- /dev/null
+++ b/src/sass/views/_contacts.scss
@@ -0,0 +1,42 @@
+.view-contacts {
+
+ .key-controls {
+ margin: 30px;
+
+ input[type=text] {
+ line-height: 23px;
+ }
+
+ input[type=file] {
+ visibility: hidden;
+ width: 0;
+ height: 0;
+ }
+ }
+
+ .key-list {
+ max-height: 400px;
+ margin: 20px;
+ overflow-y: scroll;
+
+ table {
+ th, td {
+ padding: 5px 10px;
+ }
+
+ .hover {
+ cursor: pointer;
+ }
+
+ button.remove {
+ font-family: $font-family-icons;
+ font-size: 0.75em;
+ color: $color-grey-input;
+ border: none;
+ background: none;
+ outline: none;
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/sass/views/_navigation.scss b/src/sass/views/_navigation.scss
index ba9ce68..753df15 100755
--- a/src/sass/views/_navigation.scss
+++ b/src/sass/views/_navigation.scss
@@ -79,6 +79,8 @@
margin-top: em(50, 16);
padding: 0 $nav-padding;
li {
+ margin-bottom: 0.25em;
+
a {
font-size: $font-size-big;
}
diff --git a/src/tpl/account.html b/src/tpl/account.html
index 34cb5c4..3c805ba 100644
--- a/src/tpl/account.html
+++ b/src/tpl/account.html
@@ -6,7 +6,7 @@
-
+
Email |
diff --git a/src/tpl/contacts.html b/src/tpl/contacts.html
new file mode 100644
index 0000000..0e69e2b
--- /dev/null
+++ b/src/tpl/contacts.html
@@ -0,0 +1,45 @@
+
\ No newline at end of file
diff --git a/src/tpl/desktop.html b/src/tpl/desktop.html
index 2ab6f22..1573e6b 100644
--- a/src/tpl/desktop.html
+++ b/src/tpl/desktop.html
@@ -25,6 +25,9 @@
+
\ No newline at end of file
diff --git a/src/tpl/login-new-device.html b/src/tpl/login-new-device.html
index 9a2995f..2f68c87 100644
--- a/src/tpl/login-new-device.html
+++ b/src/tpl/login-new-device.html
@@ -8,7 +8,7 @@