mirror of https://github.com/moparisthebest/mail
[WO-860] Introduce publickey-verifier
parent
0304bbf8fe
commit
1d4a9414bb
@ -0,0 +1,100 @@
|
||||
'use strict';
|
||||
|
||||
var RETRY_INTERVAL = 10000;
|
||||
|
||||
var PublicKeyVerifierCtrl = function($scope, $location, $q, $timeout, $interval, auth, publickeyVerifier, keychain) {
|
||||
$scope.retries = 0;
|
||||
|
||||
/**
|
||||
* Runs a verification attempt
|
||||
*/
|
||||
$scope.verify = function() {
|
||||
if ($scope.busy) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.busy = true;
|
||||
disarmTimeouts();
|
||||
|
||||
return $q(function(resolve) {
|
||||
// updates the GUI
|
||||
resolve();
|
||||
|
||||
}).then(function() {
|
||||
// pre-flight check: is there already a public key for the user?
|
||||
return keychain.getUserKeyPair(auth.emailAddress);
|
||||
|
||||
}).then(function(keypair) {
|
||||
if (!keypair || !keypair.publicKey) {
|
||||
// no pubkey, need to do the roundtrip
|
||||
return verifyImap();
|
||||
}
|
||||
|
||||
// pubkey has already been verified, we're done here
|
||||
return success();
|
||||
|
||||
}).catch(function(error) {
|
||||
$scope.busy = false;
|
||||
$scope.errMsg = error.message; // display error
|
||||
|
||||
scheduleVerification(); // schedule next verification attempt
|
||||
});
|
||||
|
||||
function verifyImap() {
|
||||
// retrieve the credentials
|
||||
return auth.getCredentials().then(function(credentials) {
|
||||
return publickeyVerifier.configure(credentials); // configure imap
|
||||
|
||||
}).then(function() {
|
||||
return publickeyVerifier.verify(); // connect to imap to look for the message
|
||||
|
||||
}).then(function() {
|
||||
return success();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function success() {
|
||||
return $q(function(resolve) {
|
||||
resolve();
|
||||
|
||||
}).then(function() {
|
||||
// persist keypair
|
||||
return publickeyVerifier.persistKeypair();
|
||||
|
||||
}).then(function() {
|
||||
// persist credentials locally (needs private key to encrypt imap password)
|
||||
return auth.storeCredentials();
|
||||
|
||||
}).then(function() {
|
||||
$location.path('/account'); // go to main account screen
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* schedules next verification attempt in RETRY_INTERVAL ms (scope.timeout)
|
||||
* and sets up a countdown timer for the ui (scope.countdown)
|
||||
*/
|
||||
function scheduleVerification() {
|
||||
$scope.timeout = setTimeout($scope.verify, RETRY_INTERVAL);
|
||||
|
||||
// shows the countdown timer, decrements each second
|
||||
$scope.countdown = RETRY_INTERVAL / 1000;
|
||||
$scope.countdownDecrement = setInterval(function() {
|
||||
if ($scope.countdown > 0) {
|
||||
$timeout(function() {
|
||||
$scope.countdown--;
|
||||
}, 0);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function disarmTimeouts() {
|
||||
clearTimeout($scope.timeout);
|
||||
clearInterval($scope.countdownDecrement);
|
||||
}
|
||||
|
||||
scheduleVerification();
|
||||
};
|
||||
|
||||
module.exports = PublicKeyVerifierCtrl;
|
@ -0,0 +1,233 @@
|
||||
'use strict';
|
||||
|
||||
var MSG_PART_ATTR_CONTENT = 'content';
|
||||
var MSG_PART_TYPE_TEXT = 'text';
|
||||
|
||||
var ngModule = angular.module('woServices');
|
||||
ngModule.service('publickeyVerifier', PublickeyVerifier);
|
||||
module.exports = PublickeyVerifier;
|
||||
|
||||
var ImapClient = require('imap-client');
|
||||
|
||||
function PublickeyVerifier(auth, appConfig, mailreader, keychain) {
|
||||
this._appConfig = appConfig;
|
||||
this._mailreader = mailreader;
|
||||
this._keychain = keychain;
|
||||
this._auth = auth;
|
||||
this._workerPath = appConfig.config.workerPath + '/tcp-socket-tls-worker.min.js';
|
||||
this._keyServerUrl = this._appConfig.config.keyServerUrl;
|
||||
}
|
||||
|
||||
//
|
||||
// Public API
|
||||
//
|
||||
|
||||
PublickeyVerifier.prototype.configure = function() {
|
||||
var self = this;
|
||||
|
||||
return self._auth.getCredentials().then(function(credentials) {
|
||||
// tls socket worker path for multithreaded tls in non-native tls environments
|
||||
credentials.imap.tlsWorkerPath = self._appConfig.config.workerPath + '/tcp-socket-tls-worker.min.js';
|
||||
self._imap = new ImapClient(credentials.imap);
|
||||
});
|
||||
};
|
||||
|
||||
PublickeyVerifier.prototype.persistKeypair = function() {
|
||||
return this._keychain.putUserKeyPair(this.keypair);
|
||||
};
|
||||
|
||||
PublickeyVerifier.prototype.verify = function() {
|
||||
var self = this,
|
||||
verificationSuccessful = false;
|
||||
|
||||
// have to wrap it in a promise to catch .onError of imap-client
|
||||
return new Promise(function(resolve, reject) {
|
||||
self._imap.onError = reject;
|
||||
|
||||
// login
|
||||
self._imap.login().then(function() {
|
||||
// list folders
|
||||
return self._imap.listWellKnownFolders();
|
||||
}).then(function(wellKnownFolders) {
|
||||
var paths = []; // gathers paths
|
||||
|
||||
// extract the paths from the folder arrays
|
||||
for (var folderType in wellKnownFolders) {
|
||||
if (wellKnownFolders.hasOwnProperty(folderType) && Array.isArray(wellKnownFolders[folderType])) {
|
||||
paths = paths.concat(_.pluck(wellKnownFolders[folderType], 'path'));
|
||||
}
|
||||
}
|
||||
return paths;
|
||||
|
||||
}).then(function(paths) {
|
||||
return self._searchAll(paths); // search
|
||||
|
||||
}).then(function(candidates) {
|
||||
if (!candidates.length) {
|
||||
// nothing here to potentially verify
|
||||
verificationSuccessful = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// verify everything that looks like a verification mail
|
||||
return self._verifyAll(candidates).then(function(success) {
|
||||
verificationSuccessful = success;
|
||||
});
|
||||
|
||||
}).then(function() {
|
||||
// at this point, we don't care about errors anymore
|
||||
self._imap.onError = function() {};
|
||||
self._imap.logout();
|
||||
|
||||
if (!verificationSuccessful) {
|
||||
// nothing unexpected went wrong, but no public key could be verified
|
||||
throw new Error('Could not verify public key');
|
||||
}
|
||||
|
||||
resolve(); // we're done
|
||||
|
||||
}).catch(reject);
|
||||
});
|
||||
};
|
||||
|
||||
PublickeyVerifier.prototype._searchAll = function(paths) {
|
||||
var self = this,
|
||||
candidates = []; // gather matching uids
|
||||
|
||||
// async for-loop inside a then-able
|
||||
return new Promise(next);
|
||||
|
||||
// search each path for the relevant email
|
||||
function next(resolve) {
|
||||
if (!paths.length) {
|
||||
resolve(candidates);
|
||||
return;
|
||||
}
|
||||
|
||||
var path = paths.shift();
|
||||
self._imap.search({
|
||||
path: path,
|
||||
header: ['Subject', self._appConfig.string.verificationSubject]
|
||||
}).then(function(uids) {
|
||||
uids.forEach(function(uid) {
|
||||
candidates.push({
|
||||
path: path,
|
||||
uid: uid
|
||||
});
|
||||
});
|
||||
next(resolve); // keep on searching
|
||||
}).catch(function() {
|
||||
next(resolve); // if there's an error, just search the next inbox
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
PublickeyVerifier.prototype._verifyAll = function(candidates) {
|
||||
var self = this;
|
||||
|
||||
// async for-loop inside a then-able
|
||||
return new Promise(next);
|
||||
|
||||
function next(resolve) {
|
||||
if (!candidates.length) {
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var candidate = candidates.shift();
|
||||
self._verify(candidate.path, candidate.uid).then(function(success) {
|
||||
if (success) {
|
||||
resolve(success); // we're done here
|
||||
} else {
|
||||
next(resolve);
|
||||
}
|
||||
}).catch(function() {
|
||||
next(resolve); // ignore
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
PublickeyVerifier.prototype._verify = function(path, uid) {
|
||||
var self = this,
|
||||
message;
|
||||
|
||||
// get the metadata for the message
|
||||
return self._imap.listMessages({
|
||||
path: path,
|
||||
firstUid: uid,
|
||||
lastUid: uid
|
||||
}).then(function(messages) {
|
||||
if (!messages.length) {
|
||||
// message has been deleted in the meantime
|
||||
throw new Error('Message has already been deleted');
|
||||
}
|
||||
|
||||
// remember in scope
|
||||
message = messages[0];
|
||||
|
||||
}).then(function() {
|
||||
// get the body for the message
|
||||
return self._imap.getBodyParts({
|
||||
path: path,
|
||||
uid: uid,
|
||||
bodyParts: message.bodyParts
|
||||
});
|
||||
|
||||
}).then(function() {
|
||||
// parse the message
|
||||
return new Promise(function(resolve, reject) {
|
||||
self._mailreader.parse(message, function(err, root) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(root);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}).then(function(root) {
|
||||
// extract the nonce
|
||||
var body = _.pluck(filterBodyParts(root, MSG_PART_TYPE_TEXT), MSG_PART_ATTR_CONTENT).join('\n'),
|
||||
verificationUrlPrefix = self._keyServerUrl + self._appConfig.config.verificationUrl,
|
||||
uuid = body.split(verificationUrlPrefix).pop().substr(0, self._appConfig.config.verificationUuidLength),
|
||||
uuidRegex = /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/;
|
||||
|
||||
// there's no valid uuid in the message, so forget about it
|
||||
if (!uuidRegex.test(uuid)) {
|
||||
throw new Error('No public key verifier found!');
|
||||
}
|
||||
|
||||
// there's a valid uuid in the message, so try to verify it
|
||||
return self._keychain.verifyPublicKey(uuid).catch(function(err) {
|
||||
throw new Error('Verifying your public key failed: ' + err.message);
|
||||
});
|
||||
|
||||
}).then(function() {
|
||||
return self._imap.deleteMessage({
|
||||
path: path,
|
||||
uid: uid
|
||||
}).catch(function() {}); // ignore error here
|
||||
}).then(function() {
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function that recursively traverses the body parts tree. Looks for bodyParts that match the provided type and aggregates them
|
||||
*
|
||||
* @param {Array} bodyParts The bodyParts array
|
||||
* @param {String} type The type to look up
|
||||
* @param {undefined} result Leave undefined, only used for recursion
|
||||
*/
|
||||
function filterBodyParts(bodyParts, type, result) {
|
||||
result = result || [];
|
||||
bodyParts.forEach(function(part) {
|
||||
if (part.type === type) {
|
||||
result.push(part);
|
||||
} else if (Array.isArray(part.content)) {
|
||||
filterBodyParts(part.content, type, result);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
<section class="page" ng-class="{'u-waiting-cursor': busy}">
|
||||
<div class="page__canvas">
|
||||
<header class="page__header">
|
||||
<img src="img/whiteout_logo.svg" alt="whiteout.io">
|
||||
</header>
|
||||
<main class="page__main">
|
||||
<h2 class="typo-title">Email address verification</h2>
|
||||
<p class="typo-paragraph">
|
||||
We will now automatically verify your email address with a confirmation message we've sent you.
|
||||
</p>
|
||||
<div ng-show="!busy">
|
||||
<p class="typo-paragraph">
|
||||
Verifying your email address in {{countdown}} seconds.
|
||||
</p>
|
||||
|
||||
<form class="form" name="form">
|
||||
<p class="form__error-message" ng-show="errMsg">{{errMsg}}</p>
|
||||
<div class="form__row">
|
||||
<button type="submit" ng-click="verify()" class="btn" tabindex="1">Verify now</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div ng-show="busy">
|
||||
<p class="typo-paragraph">
|
||||
This could take a moment. Please be patient, you'll be forwarded to your inbox when verification is successful.
|
||||
</p>
|
||||
<div class="spinner-block spinner-block--standalone">
|
||||
<span class="spinner spinner--big"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
<div ng-include="'tpl/page-footer.html'"></div>
|
||||
</div>
|
||||
</section>
|
@ -0,0 +1,153 @@
|
||||
'use strict';
|
||||
|
||||
var ImapClient = require('imap-client'),
|
||||
BrowserCrow = require('browsercrow'),
|
||||
mailreader = require('mailreader'),
|
||||
config = require('../../src/js/app-config'),
|
||||
str = config.string;
|
||||
|
||||
describe('Public-Key Verifier integration tests', function() {
|
||||
this.timeout(10 * 1000);
|
||||
|
||||
var verifier; // SUT
|
||||
var imapServer, keyId, workingUUID, outdatedUUID; // fixture
|
||||
var imapClient, auth, keychain; // stubs
|
||||
|
||||
beforeEach(function(done) {
|
||||
|
||||
//
|
||||
// Test data
|
||||
//
|
||||
|
||||
keyId = '1234DEADBEEF';
|
||||
workingUUID = '8314D2BF-82E5-4862-A614-1EA8CD582485';
|
||||
outdatedUUID = 'CA8BD44B-E4C5-4D48-82AB-33DA2E488CF7';
|
||||
|
||||
//
|
||||
// Test server setup
|
||||
//
|
||||
|
||||
var testAccount = {
|
||||
user: 'safewithme.testuser@gmail.com',
|
||||
pass: 'passphrase',
|
||||
xoauth2: 'testtoken'
|
||||
};
|
||||
|
||||
var serverUsers = {};
|
||||
serverUsers[testAccount.user] = {
|
||||
password: testAccount.pass,
|
||||
xoauth2: {
|
||||
accessToken: testAccount.xoauth2,
|
||||
sessionTimeout: 3600 * 1000
|
||||
}
|
||||
};
|
||||
|
||||
imapServer = new BrowserCrow({
|
||||
debug: false,
|
||||
plugins: ['sasl-ir', 'xoauth2', 'special-use', 'id', 'idle', 'unselect', 'enable', 'condstore'],
|
||||
id: {
|
||||
name: 'browsercrow',
|
||||
version: '0.1.0'
|
||||
},
|
||||
storage: {
|
||||
'INBOX': {
|
||||
messages: [{
|
||||
raw: 'Message-id: <a>\r\nSubject: ' + str.verificationSubject + '\r\n\r\nhttps://keys.whiteout.io/verify/' + outdatedUUID,
|
||||
uid: 100
|
||||
}, {
|
||||
raw: 'Message-id: <a>\r\nSubject: ' + str.verificationSubject + '\r\n\r\nhttps://keys.whiteout.io/verify/' + workingUUID,
|
||||
uid: 200
|
||||
}]
|
||||
},
|
||||
'': {
|
||||
separator: '/',
|
||||
folders: {
|
||||
'[Gmail]': {
|
||||
flags: ['\\Noselect'],
|
||||
folders: {
|
||||
'All Mail': {
|
||||
'special-use': '\\All'
|
||||
},
|
||||
Drafts: {
|
||||
'special-use': '\\Drafts'
|
||||
},
|
||||
Important: {
|
||||
'special-use': '\\Important'
|
||||
},
|
||||
'Sent Mail': {
|
||||
'special-use': '\\Sent'
|
||||
},
|
||||
Spam: {
|
||||
'special-use': '\\Junk'
|
||||
},
|
||||
Starred: {
|
||||
'special-use': '\\Flagged'
|
||||
},
|
||||
Trash: {
|
||||
'special-use': '\\Trash'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
users: serverUsers
|
||||
});
|
||||
|
||||
// don't multithread, Function.prototype.bind() is broken in phantomjs in web workers
|
||||
window.Worker = undefined;
|
||||
sinon.stub(mailreader, 'startWorker', function() {});
|
||||
|
||||
// build and inject angular services
|
||||
angular.module('email-integration-test', ['woEmail']);
|
||||
angular.mock.module('email-integration-test');
|
||||
angular.mock.inject(function($injector) {
|
||||
verifier = $injector.get('publickeyVerifier');
|
||||
setup();
|
||||
});
|
||||
|
||||
function setup() {
|
||||
auth = verifier._auth;
|
||||
auth.setCredentials({
|
||||
emailAddress: testAccount.user,
|
||||
password: 'asd',
|
||||
smtp: {}, // host and port don't matter here since we're using
|
||||
imap: {} // a preconfigured smtpclient with mocked tcp sockets
|
||||
});
|
||||
|
||||
// avoid firing up a whole http
|
||||
keychain = verifier._keychain;
|
||||
keychain.verifyPublicKey = function(uuid) {
|
||||
return new Promise(function(res, rej) {
|
||||
if (uuid === workingUUID) {
|
||||
res();
|
||||
} else {
|
||||
rej();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// create imap/smtp clients with stubbed tcp sockets
|
||||
imapClient = new ImapClient({
|
||||
auth: {
|
||||
user: testAccount.user,
|
||||
xoauth2: testAccount.xoauth2
|
||||
},
|
||||
secure: true
|
||||
});
|
||||
imapClient._client.client._TCPSocket = imapServer.createTCPSocket();
|
||||
|
||||
auth._initialized = true;
|
||||
verifier._imap = imapClient;
|
||||
verifier._keyId = keyId;
|
||||
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
describe('#verify', function() {
|
||||
it('should verify a key', function(done) {
|
||||
verifier.verify(keyId).then(done);
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,113 @@
|
||||
'use strict';
|
||||
|
||||
var Auth = require('../../../../src/js/service/auth'),
|
||||
Dialog = require('../../../../src/js/util/dialog'),
|
||||
PublicKeyVerifier = require('../../../../src/js/service/publickey-verifier'),
|
||||
KeychainDAO = require('../../../../src/js/service/keychain'),
|
||||
PublicKeyVerifierCtrl = require('../../../../src/js/controller/login/login-verify-public-key');
|
||||
|
||||
describe('Public Key Verification Controller unit test', function() {
|
||||
// Angular parameters
|
||||
var scope, location;
|
||||
|
||||
// Stubs & Fixture
|
||||
var auth, verifier, dialogStub, keychain;
|
||||
var emailAddress = 'foo@foo.com';
|
||||
|
||||
// SUT
|
||||
var verificationCtrl;
|
||||
|
||||
beforeEach(function() {
|
||||
// remeber pre-test state to restore later
|
||||
auth = sinon.createStubInstance(Auth);
|
||||
verifier = sinon.createStubInstance(PublicKeyVerifier);
|
||||
dialogStub = sinon.createStubInstance(Dialog);
|
||||
keychain = sinon.createStubInstance(KeychainDAO);
|
||||
|
||||
auth.emailAddress = emailAddress;
|
||||
|
||||
// setup the controller
|
||||
angular.module('publickeyverificationtest', []);
|
||||
angular.mock.module('publickeyverificationtest');
|
||||
angular.mock.inject(function($rootScope, $controller, $location) {
|
||||
scope = $rootScope.$new();
|
||||
location = $location;
|
||||
|
||||
verificationCtrl = $controller(PublicKeyVerifierCtrl, {
|
||||
$scope: scope,
|
||||
$q: window.qMock,
|
||||
auth: auth,
|
||||
publickeyVerifier: verifier,
|
||||
dialog: dialogStub,
|
||||
keychain: keychain,
|
||||
appConfig: {
|
||||
string: {
|
||||
publickeyVerificationSkipTitle: 'foo',
|
||||
publickeyVerificationSkipMessage: 'bar'
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {});
|
||||
|
||||
describe('#verify', function() {
|
||||
it('should verify', function(done) {
|
||||
var credentials = {};
|
||||
|
||||
keychain.getUserKeyPair.withArgs(emailAddress).returns(resolves({}));
|
||||
auth.getCredentials.returns(resolves(credentials));
|
||||
verifier.configure.withArgs(credentials).returns(resolves());
|
||||
verifier.verify.withArgs().returns(resolves());
|
||||
verifier.persistKeypair.returns(resolves());
|
||||
|
||||
scope.verify().then(function() {
|
||||
expect(keychain.getUserKeyPair.calledOnce).to.be.true;
|
||||
expect(auth.getCredentials.calledOnce).to.be.true;
|
||||
expect(verifier.configure.calledOnce).to.be.true;
|
||||
expect(verifier.verify.calledOnce).to.be.true;
|
||||
expect(verifier.persistKeypair.calledOnce).to.be.true;
|
||||
expect(location.$$path).to.equal('/account');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should skip verification when key is already verified', function(done) {
|
||||
keychain.getUserKeyPair.withArgs(emailAddress).returns(resolves({
|
||||
publicKey: {}
|
||||
}));
|
||||
|
||||
scope.verify().then(function() {
|
||||
expect(keychain.getUserKeyPair.calledOnce).to.be.true;
|
||||
expect(auth.getCredentials.called).to.be.false;
|
||||
expect(verifier.configure.called).to.be.false;
|
||||
expect(verifier.verify.called).to.be.false;
|
||||
expect(location.$$path).to.equal('/account');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not verify', function(done) {
|
||||
var credentials = {};
|
||||
|
||||
auth.getCredentials.returns(resolves(credentials));
|
||||
verifier.configure.withArgs(credentials).returns(resolves());
|
||||
verifier.verify.withArgs().returns(rejects(new Error('foo')));
|
||||
|
||||
scope.verify().then(function() {
|
||||
expect(auth.getCredentials.calledOnce).to.be.true;
|
||||
expect(verifier.configure.calledOnce).to.be.true;
|
||||
expect(verifier.verify.calledOnce).to.be.true;
|
||||
expect(scope.errMsg).to.equal('foo');
|
||||
|
||||
clearTimeout(scope.timeout);
|
||||
clearInterval(scope.countdownDecrement);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,210 @@
|
||||
'use strict';
|
||||
|
||||
var mailreader = require('mailreader'),
|
||||
KeychainDAO = require('../../../src/js/service/keychain'),
|
||||
ImapClient = require('imap-client'),
|
||||
PublickeyVerifier = require('../../../src/js/service/publickey-verifier'),
|
||||
appConfig = require('../../../src/js/app-config');
|
||||
|
||||
describe('Public-Key Verifier', function() {
|
||||
var verifier;
|
||||
var imapStub, parseStub, keychainStub, credentials, workerPath;
|
||||
|
||||
beforeEach(function() {
|
||||
//
|
||||
// Stubs
|
||||
//
|
||||
|
||||
workerPath = '../lib/tcp-socket-tls-worker.min.js';
|
||||
imapStub = sinon.createStubInstance(ImapClient);
|
||||
parseStub = sinon.stub(mailreader, 'parse');
|
||||
keychainStub = sinon.createStubInstance(KeychainDAO);
|
||||
|
||||
//
|
||||
// Fixture
|
||||
//
|
||||
credentials = {
|
||||
imap: {
|
||||
host: 'asd',
|
||||
port: 1234,
|
||||
secure: true,
|
||||
auth: {
|
||||
user: 'user',
|
||||
pass: 'pass'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Setup SUT
|
||||
//
|
||||
verifier = new PublickeyVerifier({}, appConfig, mailreader, keychainStub);
|
||||
verifier._imap = imapStub;
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
mailreader.parse.restore();
|
||||
});
|
||||
|
||||
describe('#check', function() {
|
||||
var FOLDER_TYPE_INBOX = 'Inbox',
|
||||
FOLDER_TYPE_SENT = 'Sent',
|
||||
FOLDER_TYPE_DRAFTS = 'Drafts',
|
||||
FOLDER_TYPE_TRASH = 'Trash',
|
||||
FOLDER_TYPE_FLAGGED = 'Flagged';
|
||||
|
||||
var messages,
|
||||
folders,
|
||||
searches,
|
||||
workingUUID,
|
||||
outdatedUUID;
|
||||
|
||||
beforeEach(function() {
|
||||
folders = {};
|
||||
searches = {};
|
||||
|
||||
[FOLDER_TYPE_INBOX, FOLDER_TYPE_SENT, FOLDER_TYPE_DRAFTS, FOLDER_TYPE_TRASH, FOLDER_TYPE_FLAGGED].forEach(function(type) {
|
||||
folders[type] = [{
|
||||
path: type
|
||||
}];
|
||||
searches[type] = {
|
||||
path: type,
|
||||
header: ['Subject', appConfig.string.verificationSubject]
|
||||
};
|
||||
});
|
||||
|
||||
workingUUID = '8314D2BF-82E5-4862-A614-1EA8CD582485';
|
||||
outdatedUUID = 'CA8BD44B-E4C5-4D48-82AB-33DA2E488CF7';
|
||||
messages = [{
|
||||
uid: 123,
|
||||
bodyParts: [{
|
||||
type: 'text',
|
||||
content: 'https://keys.whiteout.io/verify/' + workingUUID
|
||||
}]
|
||||
}, {
|
||||
uid: 456,
|
||||
bodyParts: [{
|
||||
type: 'text',
|
||||
content: 'https://keys.whiteout.io/verify/' + outdatedUUID
|
||||
}]
|
||||
}, {
|
||||
uid: 789,
|
||||
bodyParts: [{
|
||||
type: 'text',
|
||||
content: 'foobar'
|
||||
}]
|
||||
}];
|
||||
});
|
||||
|
||||
it('should verify a key', function(done) {
|
||||
// log in
|
||||
imapStub.login.returns(resolves());
|
||||
|
||||
// list the folders
|
||||
imapStub.listWellKnownFolders.returns(resolves(folders));
|
||||
|
||||
// return matching uids for inbox, flagged, and sent, otherwise no matches
|
||||
imapStub.search.returns(resolves([]));
|
||||
imapStub.search.withArgs(searches[FOLDER_TYPE_INBOX]).returns(resolves([messages[1].uid]));
|
||||
imapStub.search.withArgs(searches[FOLDER |