diff --git a/config/routes.js b/config/routes.js index d4ed474..1ad874c 100644 --- a/config/routes.js +++ b/config/routes.js @@ -63,6 +63,9 @@ module.exports = function(app) { app.post(apiPrefix + '/email/register', emailPlugin.oldSave); app.get(apiPrefix + '/email/retrieve/:email', emailPlugin.oldRetrieve); + + app.post(apiPrefix + '/email/delete/profile', emailPlugin.eraseProfile); + app.post(apiPrefix + '/email/delete/item/:key', emailPlugin.erase); } // Address routes diff --git a/plugins/emailstore.js b/plugins/emailstore.js index 5ca0641..b3d5db2 100644 --- a/plugins/emailstore.js +++ b/plugins/emailstore.js @@ -315,6 +315,48 @@ }); }; + emailPlugin.deleteByEmailAndKey = function deleteByEmailAndKey(email, key, callback) { + emailPlugin.db.del(valueKey(email, key), function(error) { + if (error) { + if (error.notFound) { + return callback(emailPlugin.errors.NOT_FOUND); + } else { + logger.error(error); + return callback(emailPlugin.errors.INTERNAL_ERROR); + } + } + return callback(); + }); + }; + + emailPlugin.deleteWholeProfile = function deleteWholeProfile(email, callback) { + var dismissNotFound = function(callback) { + return function(error, result) { + if (error && error.notFound) { + return callback(); + } + return callback(error, result); + }; + }; + async.parallel([ + function(callback) { + emailPlugin.db.del(emailToPassphrase(email), dismissNotFound(callback)); + }, + function(callback) { + emailPlugin.db.del(pendingKey(email), dismissNotFound(callback)); + }, + function(callback) { + emailPlugin.db.del(validatedKey(email), dismissNotFound(callback)); + } + ], function(err) { + if (err) { + logger.error(err); + return callback(emailPlugin.errors.INTERNAL_ERROR); + } + return callback(); + }); + }; + /** * Store a record in the database. The underlying database is merely a levelup instance (a key * value store) that uses the email concatenated with the secret as a key to store the record. @@ -424,7 +466,6 @@ }); }; - emailPlugin.getCredentialsFromRequest = function(request) { var auth = request.header('authorization'); if (!auth) { @@ -444,51 +485,103 @@ }; }; - - emailPlugin.addValidationHeader = function(response, email, callback) { - emailPlugin.db.get(validatedKey(email), function(err, value) { - if (err && !err.notFound) + if (err && !err.notFound) { return callback(err); + } - if (value) + if (value) { return callback(); + } response.set('X-Email-Needs-Validation', 'true'); - return callback(null, value || false); + return callback(null, value); }); }; + function authorizedRequest(withKey, callback) { + return function(request, response) { + var credentialsResult = emailPlugin.getCredentialsFromRequest(request); + if (_.contains(emailPlugin.errors, credentialsResult)) { + return emailPlugin.returnError(credentialsResult, response); + } + var email = credentialsResult.email; + var passphrase = credentialsResult.passphrase; + var key; + if (withKey) { + key = request.param('key'); + } + + if (!passphrase || !email || (withKey && !key)) { + return emailPlugin.returnError(emailPlugin.errors.MISSING_PARAMETER, response); + } + return callback(email, passphrase, key, request, response); + }; + } + /** * Retrieve a record from the database */ - emailPlugin.retrieve = function(request, response) { - var credentialsResult = emailPlugin.getCredentialsFromRequest(request); - if (_.contains(emailPlugin.errors, credentialsResult)) { - return emailPlugin.returnError(credentialsResult); - - } - var email = credentialsResult.email; - var passphrase = credentialsResult.passphrase; - - var key = request.param('key'); - if (!passphrase || !email || !key) { - return emailPlugin.returnError(emailPlugin.errors.MISSING_PARAMETER, response); - } - - emailPlugin.retrieveDataByEmailAndPassphrase(email, key, passphrase, function(err, value) { - if (err) - return emailPlugin.returnError(err, response); - - emailPlugin.addValidationHeader(response, email, function(err) { - if (err) + emailPlugin.retrieve = authorizedRequest(true, + function(email, passphrase, key, request, response) { + emailPlugin.retrieveDataByEmailAndPassphrase(email, key, passphrase, function(err, value) { + if (err) { return emailPlugin.returnError(err, response); - - response.send(value).end(); + } + emailPlugin.addValidationHeader(response, email, function(err) { + if (err) { + return emailPlugin.returnError(err, response); + } + response.send(value).end(); + }); }); - }); - }; + } + ); + + /** + * Remove a record from the database + */ + emailPlugin.erase = authorizedRequest(true, + function(email, passphrase, key, request, response) { + emailPlugin.checkPassphrase(email, passphrase, function(err, matches) { + if (err || !matches) { + return emailPlugin.returnError(emailPlugin.errors.INVALID_CODE, response); + } + emailPlugin.deleteByEmailAndKey(email, key, function(err, value) { + if (err) { + return emailPlugin.returnError(err, response); + } else { + return response.json({success: true}).end(); + }; + }); + }); + } + ); + + /** + * Remove a whole profile from the database + * + * @TODO: This looks very similar to the method above + */ + emailPlugin.eraseProfile = authorizedRequest(false, + function(email, passphrase, unused_key, request, response) { + + emailPlugin.checkPassphrase(email, passphrase, function(err, matches) { + if (err || !matches) { + return emailPlugin.returnError(emailPlugin.errors.INVALID_CODE, response); + } + emailPlugin.deleteWholeProfile(email, function(err, value) { + if (err) { + return emailPlugin.returnError(err, response); + } else { + return response.json({success: true}).end(); + }; + }); + }); + } + ); + /** * Marks an email as validated diff --git a/test/test.EmailStore.js b/test/test.EmailStore.js index 4cedf13..4281ba7 100644 --- a/test/test.EmailStore.js +++ b/test/test.EmailStore.js @@ -377,6 +377,59 @@ describe('emailstore test', function() { }); }); + describe('removing items', function() { + var fakeEmail = 'fake@email.com'; + var fakeKey = 'nameForData'; + beforeEach(function() { + leveldb_stub.del = sinon.stub(); + }); + it('deletes a stored element (key)', function(done) { + leveldb_stub.del.onFirstCall().callsArg(1); + plugin.deleteByEmailAndKey(fakeEmail, fakeKey, function(err) { + expect(err).to.be.undefined; + done(); + }); + }); + it('returns NOT FOUND if trying to delete a stored element by key', function(done) { + leveldb_stub.del.onFirstCall().callsArgWith(1, {notFound: true}); + plugin.deleteByEmailAndKey(fakeEmail, fakeKey, function(err) { + err.should.equal(plugin.errors.NOT_FOUND); + done(); + }); + }); + it('returns INTERNAL_ERROR if an unexpected error ocurrs', function(done) { + leveldb_stub.del.onFirstCall().callsArgWith(1, {unexpected: true}); + plugin.deleteByEmailAndKey(fakeEmail, fakeKey, function(err) { + err.should.equal(plugin.errors.INTERNAL_ERROR); + done(); + }); + }); + it('can delete a whole profile (validation data and passphrase)', function(done) { + leveldb_stub.del.callsArg(1); + plugin.deleteWholeProfile(fakeEmail, function(err) { + expect(err).to.be.undefined; + leveldb_stub.del.callCount.should.equal(3); + done(); + }); + }); + it('dismisses not found errors', function(done) { + leveldb_stub.del.callsArg(1); + leveldb_stub.del.onSecondCall().callsArgWith(1, {notFound: true}); + plugin.deleteWholeProfile(fakeEmail, function(err) { + expect(err).to.be.undefined; + done(); + }); + }); + it('returns internal error if something goes awry', function(done) { + leveldb_stub.del.callsArg(1); + leveldb_stub.del.onSecondCall().callsArgWith(1, {unexpected: true}); + plugin.deleteWholeProfile(fakeEmail, function(err) { + err.should.equal(plugin.errors.INTERNAL_ERROR); + done(); + }); + }); + }); + describe('when retrieving data', function() { it('should validate the secret and return the data', function() {