add quota headers + quota per item

This commit is contained in:
Matias Alejo Garcia 2014-12-01 15:05:31 -03:00
parent ebf5996ee1
commit ca907bc8b3
2 changed files with 212 additions and 84 deletions

View File

@ -67,7 +67,7 @@ module.exports = function(app) {
app.get(apiPrefix + '/email/retrieve/:email', emailPlugin.oldRetrieve); app.get(apiPrefix + '/email/retrieve/:email', emailPlugin.oldRetrieve);
app.post(apiPrefix + '/email/delete/profile', emailPlugin.eraseProfile); app.post(apiPrefix + '/email/delete/profile', emailPlugin.eraseProfile);
app.post(apiPrefix + '/email/delete/item/:key', emailPlugin.erase); app.get(apiPrefix + '/email/delete/item', emailPlugin.erase);
} }
// Currency rates plugin // Currency rates plugin

View File

@ -46,21 +46,38 @@
INVALID_CODE: { INVALID_CODE: {
code: 403, code: 403,
message: 'The provided code is invalid' message: 'The provided code is invalid'
},
OVER_QUOTA: {
code: 406,
message: 'User quota exceeded',
} }
}; };
var EMAIL_TO_PASSPHRASE = 'email-to-passphrase-'; var EMAIL_TO_PASSPHRASE = 'email-to-passphrase-';
var STORED_VALUE = 'emailstore-'; var STORED_VALUE = 'emailstore-';
var ITEMS_COUNT = 'itemscount-';
var PENDING = 'pending-'; var PENDING = 'pending-';
var VALIDATED = 'validated-'; var VALIDATED = 'validated-';
var SEPARATOR = '#'; var SEPARATOR = '#';
var MAX_ALLOWED_STORAGE = 1024 * 100 /* no more than 100 kb */ ;
var UNCONFIRMED_PER_ITEM_QUOTA = 1024 * 50; /* 50 kb */
var CONFIRMED_PER_ITEM_QUOTA = 1024 * 250; /* 250 kb */
var UNCONFIRMED_ITEMS_LIMIT = 5;
var CONFIRMED_ITEMS_LIMIT = 20;
var POST_LIMIT = 1024 * 250 /* Max POST 250 kb */ ;
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');
}; };
var countKey = function(email) {
return ITEMS_COUNT + bitcore.util.twoSha256(email).toString('hex');
};
var pendingKey = function(email) { var pendingKey = function(email) {
return PENDING + email; return PENDING + email;
}; };
@ -234,6 +251,79 @@
}); });
}; };
/**
* checkSizeQuota
*
* @param email
* @param size
* @param isConfirmed
* @param callback
*/
emailPlugin.checkSizeQuota = function(email, size, isConfirmed, callback) {
var err;
if (size > (isConfirmed ? CONFIRMED_PER_ITEM_QUOTA : UNCONFIRMED_PER_ITEM_QUOTA))
err = emailPlugin.errors.OVER_QUOTA;
logger.info('Storage size:', size);
return callback(err);
};
emailPlugin.checkAndUpdateItemCounter = function(email, isConfirmed, isAdd, callback) {
// this is a new item... Check User's Items quota.
emailPlugin.db.get(countKey(email), function(err, counter) {
if (err && !err.notFound) {
return callback(emailPlugin.errors.INTERNAL_ERROR);
}
counter = (parseInt(counter) || 0)
if (isAdd) {
counter++;
logger.info('User counter quota:', counter);
if (counter > (isConfirmed ? CONFIRMED_ITEMS_LIMIT : UNCONFIRMED_ITEMS_LIMIT)) {
return callback(emailPlugin.errors.OVER_QUOTA);
}
} else {
if (counter > 0) counter--;
}
emailPlugin.db.put(countKey(email), counter, function(err) {
if (err) {
logger.error('error saving counter');
return callback(emailPlugin.errors.INTERNAL_ERROR);
}
return callback();
});
});
};
/**
* @param {string} email
* @param {string} key
* @param {Function(err)} callback
*/
emailPlugin.checkAndUpdateItemQuota = function(email, key, isConfirmed, callback) {
emailPlugin.db.get(valueKey(email, key), function(err) {
//existing item?
if (!err)
return callback();
if (err.notFound) {
//new item
return emailPlugin.checkAndUpdateItemCounter(email, isConfirmed, 1, callback);
} else {
return callback(emailPlugin.errors.INTERNAL_ERROR);
}
});
};
/** /**
* @param {string} email * @param {string} email
* @param {string} key * @param {string} key
@ -308,7 +398,7 @@
logger.error(error); logger.error(error);
return callback(emailPlugin.errors.INTERNAL_ERROR); return callback(emailPlugin.errors.INTERNAL_ERROR);
} }
return callback(); return emailPlugin.checkAndUpdateItemCounter(email, null, null, callback);
}); });
}; };
@ -356,7 +446,7 @@
request.on('data', function(data) { request.on('data', function(data) {
queryData += data; queryData += data;
if (queryData.length > MAX_ALLOWED_STORAGE) { if (queryData.length > POST_LIMIT) {
queryData = ''; queryData = '';
response.writeHead(413, { response.writeHead(413, {
'Content-Type': 'text/plain' 'Content-Type': 'text/plain'
@ -377,70 +467,87 @@
}; };
emailPlugin.processPost = function(request, response, email, key, passphrase, record) { emailPlugin.processPost = function(request, response, email, key, passphrase, record) {
var isConfirmed = false;
async.series([ async.series([
/** /**
* Try to fetch this user's email. If it exists, check the secret is the same. * Try to fetch this user's email. If it exists, check the secret is the same.
*/ */
function(callback) { function(callback) {
emailPlugin.exists(email, function(err, exists) { emailPlugin.exists(email, function(err, exists) {
if (err) { if (err) {
return callback(err); return callback(err);
} else if (exists) { } else if (exists) {
emailPlugin.checkPassphrase(email, passphrase, function(err, match) { emailPlugin.checkPassphrase(email, passphrase, function(err, match) {
if (err) { if (err) {
return callback(err);
}
if (match) {
return callback();
} else {
return callback(emailPlugin.errors.EMAIL_TAKEN);
}
});
} else {
emailPlugin.savePassphrase(email, passphrase, function(err) {
return callback(err); return callback(err);
} });
if (match) { }
return callback(); });
} else { },
return callback(emailPlugin.errors.EMAIL_TAKEN); function(callback) {
} emailPlugin.isConfirmed(email, function(err, inIsConfirmed) {
}); if (err) return callback(err);
} else { isConfirmed = inIsConfirmed;
emailPlugin.savePassphrase(email, passphrase, function(err) { return callback();
if (err) { });
return callback(err); },
} function(callback) {
return callback(); emailPlugin.checkSizeQuota(email, record.length, isConfirmed, function(err) {
});
}
});
},
/**
* Save the encrypted private key in the storage.
*/
function(callback) {
emailPlugin.saveEncryptedData(email, key, record, function(err) {
if (err) {
return callback(err); return callback(err);
} });
return callback(); },
}); function(callback) {
}, emailPlugin.checkAndUpdateItemQuota(email, key, isConfirmed, function(err) {
/** return callback(err);
* Create and store the verification secret. If successful, send a verification email. });
*/ },
function(callback) { /**
emailPlugin.createVerificationSecretAndSendEmail(email, function(err) { * Save the encrypted private key in the storage.
if (err) { */
callback({ function(callback) {
code: 500, emailPlugin.saveEncryptedData(email, key, record, function(err) {
message: err if (err) {
}); return callback(err);
} else { }
callback(); return callback();
} });
}); },
} /**
], function(err) { * Create and store the verification secret. If successful, send a verification email.
if (err) { */
emailPlugin.returnError(err, response); function(callback) {
} else { emailPlugin.createVerificationSecretAndSendEmail(email, function(err) {
response.json({ if (err) {
success: true callback({
}).end(); code: 500,
} message: err
}); });
} else {
callback();
}
});
}
],
function(err) {
if (err) {
emailPlugin.returnError(err, response);
} else {
response.json({
success: true
}).end();
}
});
}; };
emailPlugin.getCredentialsFromRequest = function(request) { emailPlugin.getCredentialsFromRequest = function(request) {
@ -462,18 +569,41 @@
}; };
}; };
emailPlugin.addValidationHeader = function(response, email, callback) {
emailPlugin.db.get(validatedKey(email), function(err, value) { /**
if (err && !err.notFound) { * @param {string} email
return callback(err); * @param {Function(err, boolean)} callback
*/
emailPlugin.isConfirmed = function(email, callback) {
emailPlugin.db.get(validatedKey(email), function(err, isConfirmed) {
if (err && err.notFound) {
return callback(null, false);
} else if (err) {
return callback(emailPlugin.errors.INTERNAL_ERROR);
}
return callback(null, !!isConfirmed);
});
};
/**
* addValidationAndQuotaHeader
*
* @param response
* @param email
* @param {Function(err, boolean)} callback
*/
emailPlugin.addValidationAndQuotaHeader = function(response, email, callback) {
emailPlugin.isConfirmed(email, function(err, isConfirmed) {
if (err) return callback(err);
if (!isConfirmed) {
response.set('X-Email-Needs-Validation', 'true');
} }
if (value) { response.set('X-Quota-Per-Item', isConfirmed ? CONFIRMED_PER_ITEM_QUOTA : UNCONFIRMED_PER_ITEM_QUOTA);
return callback(); response.set('X-Quota-Items-Limit', isConfirmed ? CONFIRMED_ITEMS_LIMIT : UNCONFIRMED_ITEMS_LIMIT);
} return callback();
response.set('X-Email-Needs-Validation', 'true');
return callback(null, value);
}); });
}; };
@ -520,19 +650,17 @@
*/ */
emailPlugin.retrieve = function(request, response) { emailPlugin.retrieve = function(request, response) {
emailPlugin.authorizeRequestWithKey(request, function(err, email, key) { emailPlugin.authorizeRequestWithKey(request, function(err, email, key) {
if (err) { if (err)
return emailPlugin.returnError(err, response); return emailPlugin.returnError(err, response);
}
emailPlugin.retrieveByEmailAndKey(email, key, function(err, value) { emailPlugin.retrieveByEmailAndKey(email, key, function(err, value) {
if (err) { if (err)
return emailPlugin.returnError(err, response); return emailPlugin.returnError(err, response);
}
emailPlugin.addValidationHeader(response, email, function(err) {
if (err) { emailPlugin.addValidationAndQuotaHeader(response, email, function(err) {
if (err)
return emailPlugin.returnError(err, response); return emailPlugin.returnError(err, response);
}
response.send(value).end(); response.send(value).end();
}); });
@ -653,7 +781,7 @@
var queryData = ''; var queryData = '';
request.on('data', function(data) { request.on('data', function(data) {
queryData += data; queryData += data;
if (queryData.length > MAX_ALLOWED_STORAGE) { if (queryData.length > POST_LIMIT) {
queryData = ''; queryData = '';
response.writeHead(413, { response.writeHead(413, {
'Content-Type': 'text/plain' 'Content-Type': 'text/plain'
@ -718,7 +846,7 @@
request.on('data', function(data) { request.on('data', function(data) {
queryData += data; queryData += data;
if (queryData.length > MAX_ALLOWED_STORAGE) { if (queryData.length > UNCONFIRMED_PER_ITEM_QUOTA) {
queryData = ''; queryData = '';
response.writeHead(413, { response.writeHead(413, {
'Content-Type': 'text/plain' 'Content-Type': 'text/plain'