Merge pull request #272 from isocolsky/expire_email
[EmailStorage] Added expiration date to pending email registration
This commit is contained in:
commit
cd7c344125
@ -13,6 +13,7 @@
|
|||||||
var levelup = require('levelup');
|
var levelup = require('levelup');
|
||||||
var nodemailer = require('nodemailer');
|
var nodemailer = require('nodemailer');
|
||||||
var querystring = require('querystring');
|
var querystring = require('querystring');
|
||||||
|
var moment = require('moment');
|
||||||
|
|
||||||
var logger = require('../lib/logger').logger;
|
var logger = require('../lib/logger').logger;
|
||||||
var globalConfig = require('../config/config');
|
var globalConfig = require('../config/config');
|
||||||
@ -50,7 +51,11 @@
|
|||||||
OVER_QUOTA: {
|
OVER_QUOTA: {
|
||||||
code: 406,
|
code: 406,
|
||||||
message: 'User quota exceeded',
|
message: 'User quota exceeded',
|
||||||
}
|
},
|
||||||
|
REGISTRATION_EXPIRED: {
|
||||||
|
code: 400,
|
||||||
|
message: 'Registration expired',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
var EMAIL_TO_PASSPHRASE = 'email-to-passphrase-';
|
var EMAIL_TO_PASSPHRASE = 'email-to-passphrase-';
|
||||||
@ -69,6 +74,8 @@
|
|||||||
|
|
||||||
var POST_LIMIT = 1024 * 300 /* Max POST 300 kb */ ;
|
var POST_LIMIT = 1024 * 300 /* Max POST 300 kb */ ;
|
||||||
|
|
||||||
|
var DAYS_TO_EXPIRATION = 7; // An email can be awaiting validation for this long before expiring
|
||||||
|
|
||||||
var valueKey = function(email, key) {
|
var valueKey = function(email, key) {
|
||||||
return STORED_VALUE + bitcore.util.twoSha256(email + SEPARATOR + key).toString('hex');
|
return STORED_VALUE + bitcore.util.twoSha256(email + SEPARATOR + key).toString('hex');
|
||||||
};
|
};
|
||||||
@ -361,9 +368,20 @@
|
|||||||
*/
|
*/
|
||||||
emailPlugin.createVerificationSecret = function(email, callback) {
|
emailPlugin.createVerificationSecret = function(email, callback) {
|
||||||
emailPlugin.db.get(pendingKey(email), function(err, value) {
|
emailPlugin.db.get(pendingKey(email), function(err, value) {
|
||||||
if (err && err.notFound) {
|
var available = false;
|
||||||
|
|
||||||
|
var notFound = err && err.notFound;
|
||||||
|
var expired = !err && _.isObject(value) && moment().unix() > value.expires;
|
||||||
|
|
||||||
|
var available = notFound || expired;
|
||||||
|
|
||||||
|
if (available) {
|
||||||
var secret = emailPlugin.crypto.randomBytes(16).toString('hex');
|
var secret = emailPlugin.crypto.randomBytes(16).toString('hex');
|
||||||
emailPlugin.db.put(pendingKey(email), secret, function(err) {
|
var value = {
|
||||||
|
secret: secret,
|
||||||
|
expires: moment().add(DAYS_TO_EXPIRATION, 'days').unix(),
|
||||||
|
};
|
||||||
|
emailPlugin.db.put(pendingKey(email), value, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
logger.error('error saving pending data:', email, secret);
|
logger.error('error saving pending data:', email, secret);
|
||||||
return callback(emailPlugin.errors.INTERNAL_ERROR);
|
return callback(emailPlugin.errors.INTERNAL_ERROR);
|
||||||
@ -741,29 +759,39 @@
|
|||||||
code: 500,
|
code: 500,
|
||||||
message: err
|
message: err
|
||||||
}, response);
|
}, response);
|
||||||
} else if (value !== secret) {
|
|
||||||
return emailPlugin.returnError(emailPlugin.errors.INVALID_CODE, response);
|
|
||||||
} else {
|
|
||||||
emailPlugin.db.put(validatedKey(email), true, function(err, value) {
|
|
||||||
if (err) {
|
|
||||||
return emailPlugin.returnError({
|
|
||||||
code: 500,
|
|
||||||
message: err
|
|
||||||
}, response);
|
|
||||||
} else {
|
|
||||||
emailPlugin.db.del(pendingKey(email), function(err, value) {
|
|
||||||
if (err) {
|
|
||||||
return emailPlugin.returnError({
|
|
||||||
code: 500,
|
|
||||||
message: err
|
|
||||||
}, response);
|
|
||||||
} else {
|
|
||||||
response.redirect(emailPlugin.redirectUrl);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_.isObject(value)) {
|
||||||
|
if (moment().unix() > value.expires) {
|
||||||
|
return emailPlugin.returnError(emailPlugin.errors.REGISTRATION_EXPIRED, response);
|
||||||
|
} else {
|
||||||
|
value = value.secret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value !== secret) {
|
||||||
|
return emailPlugin.returnError(emailPlugin.errors.INVALID_CODE, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
emailPlugin.db.put(validatedKey(email), true, function(err, value) {
|
||||||
|
if (err) {
|
||||||
|
return emailPlugin.returnError({
|
||||||
|
code: 500,
|
||||||
|
message: err
|
||||||
|
}, response);
|
||||||
|
} else {
|
||||||
|
emailPlugin.db.del(pendingKey(email), function(err, value) {
|
||||||
|
if (err) {
|
||||||
|
return emailPlugin.returnError({
|
||||||
|
code: 500,
|
||||||
|
message: err
|
||||||
|
}, response);
|
||||||
|
} else {
|
||||||
|
response.redirect(emailPlugin.redirectUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@ var bitcore = require('bitcore');
|
|||||||
var logger = require('../lib/logger').logger;
|
var logger = require('../lib/logger').logger;
|
||||||
var should = chai.should;
|
var should = chai.should;
|
||||||
var expect = chai.expect;
|
var expect = chai.expect;
|
||||||
|
var moment = require('moment');
|
||||||
|
|
||||||
logger.transports.console.level = 'non';
|
logger.transports.console.level = 'non';
|
||||||
|
|
||||||
@ -225,9 +226,12 @@ describe('emailstore test', function() {
|
|||||||
|
|
||||||
it('saves data under the expected key', function(done) {
|
it('saves data under the expected key', function(done) {
|
||||||
setupLevelDb();
|
setupLevelDb();
|
||||||
|
var clock = sinon.useFakeTimers();
|
||||||
plugin.createVerificationSecretAndSendEmail(fakeEmail, function(err) {
|
plugin.createVerificationSecretAndSendEmail(fakeEmail, function(err) {
|
||||||
leveldb_stub.put.firstCall.args[1].should.equal(fakeRandom);
|
var arg = leveldb_stub.put.firstCall.args[1];
|
||||||
|
arg.secret.should.equal(fakeRandom);
|
||||||
|
arg.expires.should.equal(moment().add(7, 'days').unix());
|
||||||
|
clock.restore();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -363,7 +367,7 @@ describe('emailstore test', function() {
|
|||||||
response.json.returnsThis();
|
response.json.returnsThis();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should validate correctly an email if the secret matches', function() {
|
it('should validate correctly an email if the secret matches (without expiration date)', function() {
|
||||||
leveldb_stub.get.onFirstCall().callsArgWith(1, null, secret);
|
leveldb_stub.get.onFirstCall().callsArgWith(1, null, secret);
|
||||||
leveldb_stub.del = sinon.stub().yields(null);
|
leveldb_stub.del = sinon.stub().yields(null);
|
||||||
response.redirect = sinon.stub();
|
response.redirect = sinon.stub();
|
||||||
@ -373,6 +377,19 @@ describe('emailstore test', function() {
|
|||||||
assert(response.redirect.firstCall.calledWith(plugin.redirectUrl));
|
assert(response.redirect.firstCall.calledWith(plugin.redirectUrl));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should validate correctly an email if the secret matches (using expiration date)', function() {
|
||||||
|
leveldb_stub.get.onFirstCall().callsArgWith(1, null, {
|
||||||
|
secret: secret,
|
||||||
|
expires: moment().add(7, 'days').unix(),
|
||||||
|
});
|
||||||
|
leveldb_stub.del = sinon.stub().yields(null);
|
||||||
|
response.redirect = sinon.stub();
|
||||||
|
|
||||||
|
plugin.validate(request, response);
|
||||||
|
|
||||||
|
assert(response.redirect.firstCall.calledWith(plugin.redirectUrl));
|
||||||
|
});
|
||||||
|
|
||||||
it('should fail to validate an email if the secret doesn\'t match', function() {
|
it('should fail to validate an email if the secret doesn\'t match', function() {
|
||||||
var invalid = '3';
|
var invalid = '3';
|
||||||
leveldb_stub.get.onFirstCall().callsArgWith(1, null, invalid);
|
leveldb_stub.get.onFirstCall().callsArgWith(1, null, invalid);
|
||||||
@ -387,6 +404,23 @@ describe('emailstore test', function() {
|
|||||||
}));
|
}));
|
||||||
assert(response.end.calledOnce);
|
assert(response.end.calledOnce);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should fail to validate an email if the secret has expired', function() {
|
||||||
|
leveldb_stub.get.onFirstCall().callsArgWith(1, null, {
|
||||||
|
secret: secret,
|
||||||
|
expires: moment().subtract(2, 'days').unix(),
|
||||||
|
});
|
||||||
|
response.status.returnsThis();
|
||||||
|
response.json.returnsThis();
|
||||||
|
|
||||||
|
plugin.validate(request, response);
|
||||||
|
|
||||||
|
assert(response.status.firstCall.calledWith(plugin.errors.REGISTRATION_EXPIRED.code));
|
||||||
|
assert(response.json.firstCall.calledWith({
|
||||||
|
error: 'Registration expired'
|
||||||
|
}));
|
||||||
|
assert(response.end.calledOnce);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('removing items', function() {
|
describe('removing items', function() {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user