From 15b902acf60d9d49140cb6395e4d9b100d14aaa2 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Thu, 11 Dec 2014 19:07:04 +0100 Subject: [PATCH] Refactor admin and privatekey dao --- src/js/service/admin.js | 61 ++++--- src/js/service/privatekey.js | 203 ++++++++++++----------- test/unit/service/admin-dao-test.js | 35 ++-- test/unit/service/privatekey-dao-test.js | 105 +++++++----- 4 files changed, 212 insertions(+), 192 deletions(-) diff --git a/src/js/service/admin.js b/src/js/service/admin.js index af794bb..a36bdfa 100644 --- a/src/js/service/admin.js +++ b/src/js/service/admin.js @@ -13,27 +13,24 @@ function Admin(adminRestDao) { * @param {String} options.emailAddress The desired email address * @param {String} options.password The password to be used for the account. * @param {String} options.phone The user's mobile phone number (required for verification and password reset). - * @param {Function} callback(error) */ -Admin.prototype.createUser = function(options, callback) { - var uri; +Admin.prototype.createUser = function(options) { + var self = this; + return new Promise(function(resolve) { + if (!options.emailAddress || !options.password || !options.phone) { + throw new Error('Incomplete arguments!'); + } + resolve(); - if (!options.emailAddress || !options.password || !options.phone) { - callback(new Error('Incomplete arguments!')); - return; - } + }).then(function() { + return self._restDao.post(options, '/user'); - uri = '/user'; - this._restDao.post(options, uri, function(err) { + }).catch(function(err) { if (err && err.code === 409) { - callback(new Error('User name is already taken!')); - return; - } else if (err) { - callback(new Error('Error creating new user!')); - return; + throw new Error('User name is already taken!'); } - callback(); + throw new Error('Error creating new user!'); }); }; @@ -41,23 +38,25 @@ Admin.prototype.createUser = function(options, callback) { * Verify a user's phone number by confirming a token to the server. * @param {String} options.emailAddress The desired email address * @param {String} options.token The validation token. - * @param {Function} callback(error) */ -Admin.prototype.validateUser = function(options, callback) { - var uri; - - if (!options.emailAddress || !options.token) { - callback(new Error('Incomplete arguments!')); - return; - } - - uri = '/user/validate'; - this._restDao.post(options, uri, function(err) { - if (!err || (err && err.code === 202)) { - // success - callback(); - } else { - callback(new Error('Validation failed!')); +Admin.prototype.validateUser = function(options) { + var self = this; + return new Promise(function(resolve) { + if (!options.emailAddress || !options.token) { + throw new Error('Incomplete arguments!'); } + resolve(); + + }).then(function() { + var uri = '/user/validate'; + return self._restDao.post(options, uri); + + }).catch(function(err) { + if (err && err.code === 202) { + // success + return; + } + + throw new Error('Validation failed!'); }); }; \ No newline at end of file diff --git a/src/js/service/privatekey.js b/src/js/service/privatekey.js index e15d81f..27d3135 100644 --- a/src/js/service/privatekey.js +++ b/src/js/service/privatekey.js @@ -16,19 +16,20 @@ function PrivateKey(privateKeyRestDao) { * Request registration of a new device by fetching registration session key. * @param {String} options.userId The user's email address * @param {String} options.deviceName The device's memorable name - * @param {Function} callback(error, regSessionKey) * @return {Object} {encryptedRegSessionKey:[base64]} */ -PrivateKey.prototype.requestDeviceRegistration = function(options, callback) { - var uri; +PrivateKey.prototype.requestDeviceRegistration = function(options) { + var self = this; + return new Promise(function(resolve) { + if (!options.userId || !options.deviceName) { + throw new Error('Incomplete arguments!'); + } + resolve(); - if (!options.userId || !options.deviceName) { - callback(new Error('Incomplete arguments!')); - return; - } - - uri = '/device/user/' + options.userId + '/devicename/' + options.deviceName; - this._restDao.post(undefined, uri, callback); + }).then(function() { + var uri = '/device/user/' + options.userId + '/devicename/' + options.deviceName; + return self._restDao.post(undefined, uri); + }); }; /** @@ -37,18 +38,19 @@ PrivateKey.prototype.requestDeviceRegistration = function(options, callback) { * @param {String} options.deviceName The device's memorable name * @param {String} options.encryptedDeviceSecret The base64 encoded encrypted device secret * @param {String} options.iv The iv used for encryption - * @param {Function} callback(error) */ -PrivateKey.prototype.uploadDeviceSecret = function(options, callback) { - var uri; +PrivateKey.prototype.uploadDeviceSecret = function(options) { + var self = this; + return new Promise(function(resolve) { + if (!options.userId || !options.deviceName || !options.encryptedDeviceSecret || !options.iv) { + throw new Error('Incomplete arguments!'); + } + resolve(); - if (!options.userId || !options.deviceName || !options.encryptedDeviceSecret || !options.iv) { - callback(new Error('Incomplete arguments!')); - return; - } - - uri = '/device/user/' + options.userId + '/devicename/' + options.deviceName; - this._restDao.put(options, uri, callback); + }).then(function() { + var uri = '/device/user/' + options.userId + '/devicename/' + options.deviceName; + return self._restDao.put(options, uri); + }); }; // @@ -58,19 +60,20 @@ PrivateKey.prototype.uploadDeviceSecret = function(options, callback) { /** * Request authSessionKeys required for upload the encrypted private PGP key. * @param {String} options.userId The user's email address - * @param {Function} callback(error, authSessionKey) * @return {Object} {sessionId, encryptedAuthSessionKey:[base64 encoded], encryptedChallenge:[base64 encoded]} */ -PrivateKey.prototype.requestAuthSessionKey = function(options, callback) { - var uri; +PrivateKey.prototype.requestAuthSessionKey = function(options) { + var self = this; + return new Promise(function(resolve) { + if (!options.userId) { + throw new Error('Incomplete arguments!'); + } + resolve(); - if (!options.userId) { - callback(new Error('Incomplete arguments!')); - return; - } - - uri = '/auth/user/' + options.userId; - this._restDao.post(undefined, uri, callback); + }).then(function() { + var uri = '/auth/user/' + options.userId; + return self._restDao.post(undefined, uri); + }); }; /** @@ -79,18 +82,19 @@ PrivateKey.prototype.requestAuthSessionKey = function(options, callback) { * @param {String} options.encryptedChallenge The server's base64 encoded challenge encrypted using the authSessionKey * @param {String} options.encryptedDeviceSecret The server's base64 encoded deviceSecret encrypted using the authSessionKey * @param {String} options.iv The iv used for encryption - * @param {Function} callback(error) */ -PrivateKey.prototype.verifyAuthentication = function(options, callback) { - var uri; +PrivateKey.prototype.verifyAuthentication = function(options) { + var self = this; + return new Promise(function(resolve) { + if (!options.userId || !options.sessionId || !options.encryptedChallenge || !options.encryptedDeviceSecret || !options.iv) { + throw new Error('Incomplete arguments!'); + } + resolve(); - if (!options.userId || !options.sessionId || !options.encryptedChallenge || !options.encryptedDeviceSecret || !options.iv) { - callback(new Error('Incomplete arguments!')); - return; - } - - uri = '/auth/user/' + options.userId + '/session/' + options.sessionId; - this._restDao.put(options, uri, callback); + }).then(function() { + var uri = '/auth/user/' + options.userId + '/session/' + options.sessionId; + return self._restDao.put(options, uri); + }); }; /** @@ -99,48 +103,50 @@ PrivateKey.prototype.verifyAuthentication = function(options, callback) { * @param {String} options.userId The user's email address * @param {String} options.encryptedPrivateKey The base64 encoded encrypted private PGP key * @param {String} options.sessionId The session id - * @param {Function} callback(error) */ -PrivateKey.prototype.upload = function(options, callback) { - var uri; +PrivateKey.prototype.upload = function(options) { + var self = this; + return new Promise(function(resolve) { + if (!options._id || !options.userId || !options.encryptedPrivateKey || !options.sessionId || !options.salt || !options.iv) { + throw new Error('Incomplete arguments!'); + } + resolve(); - if (!options._id || !options.userId || !options.encryptedPrivateKey || !options.sessionId || !options.salt || !options.iv) { - callback(new Error('Incomplete arguments!')); - return; - } - - uri = '/privatekey/user/' + options.userId + '/session/' + options.sessionId; - this._restDao.post(options, uri, callback); + }).then(function() { + var uri = '/privatekey/user/' + options.userId + '/session/' + options.sessionId; + return self._restDao.post(options, uri); + }); }; /** * Query if an encrypted private PGP key exists on the server without initializing the recovery procedure. * @param {String} options.userId The user's email address * @param {String} options.keyId The private PGP key id - * @param {Function} callback(error, found) * @return {Boolean} whether the key was found on the server or not. */ -PrivateKey.prototype.hasPrivateKey = function(options, callback) { - if (!options.userId || !options.keyId) { - callback(new Error('Incomplete arguments!')); - return; - } +PrivateKey.prototype.hasPrivateKey = function(options) { + var self = this; + return new Promise(function(resolve) { + if (!options.userId || !options.keyId) { + throw new Error('Incomplete arguments!'); + } + resolve(); - this._restDao.get({ - uri: '/privatekey/user/' + options.userId + '/key/' + options.keyId + '?ignoreRecovery=true', - }, function(err) { + }).then(function() { + return self._restDao.get({ + uri: '/privatekey/user/' + options.userId + '/key/' + options.keyId + '?ignoreRecovery=true', + }); + + }).then(function() { + return true; + + }).catch(function(err) { // 404: there is no encrypted private key on the server - if (err && err.code !== 200) { - callback(null, false); - return; + if (err.code && err.code !== 200) { + return false; } - if (err) { - callback(err); - return; - } - - callback(null, true); + throw err; }); }; @@ -148,30 +154,31 @@ PrivateKey.prototype.hasPrivateKey = function(options, callback) { * Request download for the encrypted private PGP key. * @param {String} options.userId The user's email address * @param {String} options.keyId The private PGP key id - * @param {Function} callback(error, found) * @return {Boolean} whether the key was found on the server or not. */ -PrivateKey.prototype.requestDownload = function(options, callback) { - if (!options.userId || !options.keyId) { - callback(new Error('Incomplete arguments!')); - return; - } +PrivateKey.prototype.requestDownload = function(options) { + var self = this; + return new Promise(function(resolve) { + if (!options.userId || !options.keyId) { + throw new Error('Incomplete arguments!'); + } + resolve(); - this._restDao.get({ - uri: '/privatekey/user/' + options.userId + '/key/' + options.keyId - }, function(err) { + }).then(function() { + return self._restDao.get({ + uri: '/privatekey/user/' + options.userId + '/key/' + options.keyId + }); + + }).then(function() { + return true; + + }).catch(function(err) { // 404: there is no encrypted private key on the server - if (err && err.code !== 200) { - callback(null, false); - return; + if (err.code && err.code !== 200) { + return false; } - if (err) { - callback(err); - return; - } - - callback(null, true); + throw err; }); }; @@ -180,19 +187,19 @@ PrivateKey.prototype.requestDownload = function(options, callback) { * @param {String} options.userId The user's email address * @param {String} options.keyId The private key id * @param {String} options.recoveryToken The token proving the user own the email account - * @param {Function} callback(error, encryptedPrivateKey) * @return {Object} {_id:[hex encoded capital 16 char key id], encryptedPrivateKey:[base64 encoded], encryptedUserId: [base64 encoded]} */ -PrivateKey.prototype.download = function(options, callback) { - var uri; +PrivateKey.prototype.download = function(options) { + var self = this; + return new Promise(function(resolve) { + if (!options.userId || !options.keyId || !options.recoveryToken) { + throw new Error('Incomplete arguments!'); + } + resolve(); - if (!options.userId || !options.keyId || !options.recoveryToken) { - callback(new Error('Incomplete arguments!')); - return; - } - - uri = '/privatekey/user/' + options.userId + '/key/' + options.keyId + '/recovery/' + options.recoveryToken; - this._restDao.get({ - uri: uri - }, callback); + }).then(function() { + return self._restDao.get({ + uri: '/privatekey/user/' + options.userId + '/key/' + options.keyId + '/recovery/' + options.recoveryToken + }); + }); }; \ No newline at end of file diff --git a/test/unit/service/admin-dao-test.js b/test/unit/service/admin-dao-test.js index 2f41e8f..e4f4b8a 100644 --- a/test/unit/service/admin-dao-test.js +++ b/test/unit/service/admin-dao-test.js @@ -23,7 +23,7 @@ describe('Admin DAO unit tests', function() { emailAddress: emailAddress }; - adminDao.createUser(opt, function(err) { + adminDao.createUser(opt).catch(function(err) { expect(err).to.exist; done(); }); @@ -36,11 +36,11 @@ describe('Admin DAO unit tests', function() { phone: '12345' }; - restDaoStub.post.withArgs(opt, '/user').yields({ + restDaoStub.post.withArgs(opt, '/user').returns(rejects({ code: 409 - }); + })); - adminDao.createUser(opt, function(err) { + adminDao.createUser(opt).catch(function(err) { expect(err.message).to.contain('already taken'); expect(restDaoStub.post.calledOnce).to.be.true; done(); @@ -54,9 +54,9 @@ describe('Admin DAO unit tests', function() { phone: '12345' }; - restDaoStub.post.withArgs(opt, '/user').yields(new Error()); + restDaoStub.post.withArgs(opt, '/user').returns(rejects(new Error())); - adminDao.createUser(opt, function(err) { + adminDao.createUser(opt).catch(function(err) { expect(err).to.exist; expect(restDaoStub.post.calledOnce).to.be.true; done(); @@ -70,10 +70,9 @@ describe('Admin DAO unit tests', function() { phone: '12345' }; - restDaoStub.post.withArgs(opt, '/user').yields(); + restDaoStub.post.withArgs(opt, '/user').returns(resolves()); - adminDao.createUser(opt, function(err) { - expect(err).to.not.exist; + adminDao.createUser(opt).then(function() { expect(restDaoStub.post.calledOnce).to.be.true; done(); }); @@ -86,7 +85,7 @@ describe('Admin DAO unit tests', function() { emailAddress: emailAddress }; - adminDao.validateUser(opt, function(err) { + adminDao.validateUser(opt).catch(function(err) { expect(err).to.exist; done(); }); @@ -98,9 +97,9 @@ describe('Admin DAO unit tests', function() { token: 'H45Z6D' }; - restDaoStub.post.withArgs(opt, '/user/validate').yields(new Error()); + restDaoStub.post.withArgs(opt, '/user/validate').returns(rejects(new Error())); - adminDao.validateUser(opt, function(err) { + adminDao.validateUser(opt).catch(function(err) { expect(err).to.exist; expect(restDaoStub.post.calledOnce).to.be.true; done(); @@ -113,10 +112,9 @@ describe('Admin DAO unit tests', function() { token: 'H45Z6D' }; - restDaoStub.post.withArgs(opt, '/user/validate').yields(); + restDaoStub.post.withArgs(opt, '/user/validate').returns(resolves()); - adminDao.validateUser(opt, function(err) { - expect(err).to.not.exist; + adminDao.validateUser(opt).then(function() { expect(restDaoStub.post.calledOnce).to.be.true; done(); }); @@ -128,12 +126,11 @@ describe('Admin DAO unit tests', function() { token: 'H45Z6D' }; - restDaoStub.post.withArgs(opt, '/user/validate').yields({ + restDaoStub.post.withArgs(opt, '/user/validate').returns(rejects({ code: 202 - }); + })); - adminDao.validateUser(opt, function(err) { - expect(err).to.not.exist; + adminDao.validateUser(opt).then(function() { expect(restDaoStub.post.calledOnce).to.be.true; done(); }); diff --git a/test/unit/service/privatekey-dao-test.js b/test/unit/service/privatekey-dao-test.js index d8a9ea6..e3de859 100644 --- a/test/unit/service/privatekey-dao-test.js +++ b/test/unit/service/privatekey-dao-test.js @@ -19,23 +19,21 @@ describe('Private Key DAO unit tests', function() { describe('requestDeviceRegistration', function() { it('should fail due to invalid args', function(done) { - privkeyDao.requestDeviceRegistration({}, function(err, sessionKey) { + privkeyDao.requestDeviceRegistration({}).catch(function(err) { expect(err).to.exist; - expect(sessionKey).to.not.exist; done(); }); }); it('should work', function(done) { - restDaoStub.post.yields(null, { + restDaoStub.post.returns(resolves({ encryptedRegSessionKey: 'asdf' - }); + })); privkeyDao.requestDeviceRegistration({ userId: emailAddress, deviceName: deviceName - }, function(err, sessionKey) { - expect(err).to.not.exist; + }).then(function(sessionKey) { expect(sessionKey).to.exist; done(); }); @@ -44,50 +42,44 @@ describe('Private Key DAO unit tests', function() { describe('uploadDeviceSecret', function() { it('should fail due to invalid args', function(done) { - privkeyDao.uploadDeviceSecret({}, function(err) { + privkeyDao.uploadDeviceSecret({}).catch(function(err) { expect(err).to.exist; done(); }); }); it('should work', function(done) { - restDaoStub.put.yields(); + restDaoStub.put.returns(resolves()); privkeyDao.uploadDeviceSecret({ userId: emailAddress, deviceName: deviceName, encryptedDeviceSecret: 'asdf', iv: 'iv' - }, function(err) { - expect(err).to.not.exist; - done(); - }); + }).then(done); }); }); describe('requestAuthSessionKey', function() { it('should fail due to invalid args', function(done) { - privkeyDao.requestAuthSessionKey({}, function(err) { + privkeyDao.requestAuthSessionKey({}).catch(function(err) { expect(err).to.exist; done(); }); }); it('should work', function(done) { - restDaoStub.post.withArgs(undefined, '/auth/user/' + emailAddress).yields(); + restDaoStub.post.withArgs(undefined, '/auth/user/' + emailAddress).returns(resolves()); privkeyDao.requestAuthSessionKey({ userId: emailAddress - }, function(err) { - expect(err).to.not.exist; - done(); - }); + }).then(done); }); }); describe('verifyAuthentication', function() { it('should fail due to invalid args', function(done) { - privkeyDao.verifyAuthentication({}, function(err) { + privkeyDao.verifyAuthentication({}).catch(function(err) { expect(err).to.exist; done(); }); @@ -104,18 +96,15 @@ describe('Private Key DAO unit tests', function() { iv: ' iv' }; - restDaoStub.put.withArgs(options, '/auth/user/' + emailAddress + '/session/' + sessionId).yields(); + restDaoStub.put.withArgs(options, '/auth/user/' + emailAddress + '/session/' + sessionId).returns(resolves()); - privkeyDao.verifyAuthentication(options, function(err) { - expect(err).to.not.exist; - done(); - }); + privkeyDao.verifyAuthentication(options).then(done); }); }); describe('upload', function() { it('should fail due to invalid args', function(done) { - privkeyDao.upload({}, function(err) { + privkeyDao.upload({}).catch(function(err) { expect(err).to.exist; done(); }); @@ -131,35 +120,49 @@ describe('Private Key DAO unit tests', function() { iv: 'iv' }; - restDaoStub.post.withArgs(options, '/privatekey/user/' + emailAddress + '/session/' + options.sessionId).yields(); + restDaoStub.post.withArgs(options, '/privatekey/user/' + emailAddress + '/session/' + options.sessionId).returns(resolves()); - privkeyDao.upload(options, function(err) { - expect(err).to.not.exist; - done(); - }); + privkeyDao.upload(options).then(done); }); }); describe('requestDownload', function() { it('should fail due to invalid args', function(done) { - privkeyDao.requestDownload({}, function(err) { + privkeyDao.requestDownload({}).catch(function(err) { expect(err).to.exist; done(); }); }); + it('should not find a key', function(done) { + var keyId = '12345'; + + restDaoStub.get.withArgs({ + uri: '/privatekey/user/' + emailAddress + '/key/' + keyId + }).returns(rejects({ + code: 404 + })); + + privkeyDao.requestDownload({ + userId: emailAddress, + keyId: keyId + }).then(function(found) { + expect(found).to.be.false; + done(); + }); + }); + it('should work', function(done) { var keyId = '12345'; restDaoStub.get.withArgs({ uri: '/privatekey/user/' + emailAddress + '/key/' + keyId - }).yields(); + }).returns(resolves()); privkeyDao.requestDownload({ userId: emailAddress, keyId: keyId - }, function(err, found) { - expect(err).to.not.exist; + }).then(function(found) { expect(found).to.be.true; done(); }); @@ -168,24 +171,41 @@ describe('Private Key DAO unit tests', function() { describe('hasPrivateKey', function() { it('should fail due to invalid args', function(done) { - privkeyDao.hasPrivateKey({}, function(err) { + privkeyDao.hasPrivateKey({}).catch(function(err) { expect(err).to.exist; done(); }); }); + it('should not find a key', function(done) { + var keyId = '12345'; + + restDaoStub.get.withArgs({ + uri: '/privatekey/user/' + emailAddress + '/key/' + keyId + '?ignoreRecovery=true' + }).returns(rejects({ + code: 404 + })); + + privkeyDao.hasPrivateKey({ + userId: emailAddress, + keyId: keyId + }).then(function(found) { + expect(found).to.be.false; + done(); + }); + }); + it('should work', function(done) { var keyId = '12345'; restDaoStub.get.withArgs({ uri: '/privatekey/user/' + emailAddress + '/key/' + keyId + '?ignoreRecovery=true' - }).yields(); + }).returns(resolves()); privkeyDao.hasPrivateKey({ userId: emailAddress, keyId: keyId - }, function(err, found) { - expect(err).to.not.exist; + }).then(function(found) { expect(found).to.be.true; done(); }); @@ -194,7 +214,7 @@ describe('Private Key DAO unit tests', function() { describe('download', function() { it('should fail due to invalid args', function(done) { - privkeyDao.download({}, function(err) { + privkeyDao.download({}).catch(function(err) { expect(err).to.exist; done(); }); @@ -207,16 +227,13 @@ describe('Private Key DAO unit tests', function() { restDaoStub.get.withArgs({ uri: '/privatekey/user/' + emailAddress + '/key/' + key._id + '/recovery/token' - }).yields(); + }).returns(resolves()); privkeyDao.download({ userId: emailAddress, keyId: key._id, recoveryToken: 'token' - }, function(err) { - expect(err).to.not.exist; - done(); - }); + }).then(done); }); });