diff --git a/lib/client.js b/lib/client.js index 809c81a..b0aabe3 100644 --- a/lib/client.js +++ b/lib/client.js @@ -25,6 +25,24 @@ var ipSchema = Joi.string().ip({ cidr: false }); +var emailSchema = Joi.string().email(); + +var validate = function(type, obj) { + if (type === 'ip') { + if (Joi.validate(obj, ipSchema).error !== null) { + return new Error('Invalid IP.'); + } + } else if (type === 'email') { + if (Joi.validate(obj, emailSchema).error !== null) { + return new Error('Invalid Email.'); + } + } else { + return new Error('Invalid type: '+type); + } + return null; +}; + + /** * @class IPReputationServiceClient * @constructor @@ -64,55 +82,73 @@ var client = function(config) { }; /** - * @method get - * @param {String} ip a Joi strip.ip IP address to fetch reputation for + * @method getTyped + * @param {String} type Object type to get reputation for (ip or email) + * @param {String} obj Object to get reputation for * @return {Promise} */ -client.prototype.get = function (ip) { - if (Joi.validate(ip, ipSchema).error !== null) { - return Promise.reject(new Error('Invalid IP.')); +client.prototype.getTyped = function (type, obj) { + if (validate(type, obj) !== null) { + return Promise.reject(validate(type, obj)); } return this.baseRequest({ method: 'GET', - uri: '/' + ip, + uri: '/type/' + type + '/' + obj, hawk: { contentType: null } - }).timeout(this.timeout); + }).timeout(this.timeout).then(function (response) { + if (response && response.body && response.statusCode) { + if (type === 'ip') { + response.body.ip = obj; + } + } + return response; + }); }; /** - * @method update - * @param {String} ip an IP address to change a reputation for + * @method updateTyped + * @param {String} type Object type to update the reputation for (ip or email) + * @param {String} obj Object to update the reputation for * @param {Number} reputation a reputation/trust value from 0 to 100 inclusive (higher is more trustworthy) * @return {Promise} */ -client.prototype.update = function (ip, reputation) { - if (Joi.validate(ip, ipSchema).error !== null) { - return Promise.reject(new Error('Invalid IP.')); +client.prototype.updateTyped = function (type, obj, reputation) { + if (validate(type, obj) !== null) { + return Promise.reject(validate(type, obj)); } return this.baseRequest({ method: 'PUT', - uri: '/' + ip, + uri: '/type/' + type + '/' + obj, hawk: { - payload: JSON.stringify({'reputation': reputation}) + payload: JSON.stringify({ + 'reputation': reputation, + 'object': obj, + 'type': type + }) }, - json: {'reputation': reputation} + json: { + 'reputation': reputation, + 'object': obj, + 'type': type + } }).timeout(this.timeout); }; /** - * @method remove - * @param {String} ip an IP address to remove an associated reputation for + * @method removeTyped + * @param {String} type Object type to remove an associated reputation for (ip or email) + * @param {String} obj Object to remove an associated reputation for * @return {Promise} */ -client.prototype.remove = function (ip) { - if (Joi.validate(ip, ipSchema).error !== null) { - return Promise.reject(new Error('Invalid IP.')); +client.prototype.removeTyped = function (type, obj) { + if (validate(type, obj) !== null) { + return Promise.reject(validate(type, obj)); } return this.baseRequest({ method: 'DELETE', - uri: '/' + ip, + uri: '/type/' + type + '/' + obj, hawk: { payload: '', // force sending a hawk hash that reputation service requires contentType: null @@ -121,23 +157,71 @@ client.prototype.remove = function (ip) { }; /** - * @method sendViolation - * @param {String} ip an IP address to record a violation for + * @method sendViolationTyped + * @param {String} type Object type to record a violation for + * @param {String} obj Object to record a violation for * @param {String} violationType an violation type to save lookup the reputation penalty for * @return {Promise} */ -client.prototype.sendViolation = function (ip, violationType) { - if (Joi.validate(ip, ipSchema).error !== null) { - return Promise.reject(new Error('Invalid IP.')); +client.prototype.sendViolationTyped = function (type, obj, violationType) { + if (validate(type, obj) !== null) { + return Promise.reject(validate(type, obj)); } return this.baseRequest({ method: 'PUT', - uri: '/violations/' + ip, + uri: '/violations/type/' + type + '/' + obj, hawk: { - payload: JSON.stringify({'ip': ip, 'violation': violationType}) + payload: JSON.stringify({ + 'violation': violationType, + 'type': type, + 'object': obj + }) }, - json: {'ip': ip, 'violation': violationType} + json: { + 'violation': violationType, + 'type': type, + 'object': obj, + } }).timeout(this.timeout); }; + +/** + * @method get + * @param {String} ip a Joi strip.ip IP address to fetch reputation for + * @return {Promise} + */ +client.prototype.get = function (ip) { + return this.getTyped('ip', ip); +}; + +/** + * @method update + * @param {String} ip an IP address to change a reputation for + * @param {Number} reputation a reputation/trust value from 0 to 100 inclusive (higher is more trustworthy) + * @return {Promise} + */ +client.prototype.update = function (ip, reputation) { + return this.updateTyped('ip', ip, reputation); +}; + +/** + * @method remove + * @param {String} ip an IP address to remove an associated reputation for + * @return {Promise} + */ +client.prototype.remove = function (ip) { + return this.removeTyped('ip', ip); +}; + +/** + * @method sendViolation + * @param {String} ip an IP address to record a violation for + * @param {String} violationType an violation type to save lookup the reputation penalty for + * @return {Promise} + */ +client.prototype.sendViolation = function (ip, violationType) { + return this.sendViolationTyped('ip', ip, violationType); +}; + module.exports = client; diff --git a/package-lock.json b/package-lock.json index 7096668..c3a1857 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5014,9 +5014,9 @@ } }, "yargs-parser": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.0.tgz", - "integrity": "sha512-Yq+32PrijHRri0vVKQEm+ys8mbqWjLiwQkMFNXEENutzLPP0bE4Lcd4iA3OQY5HF+GD3xXxf0MEHb8E4/SA3AA==", + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", "dev": true, "requires": { "camelcase": "^5.0.0", diff --git a/test/local/reputation_service_client_tests.js b/test/local/reputation_service_client_tests.js index 80fa2f6..77b04a8 100644 --- a/test/local/reputation_service_client_tests.js +++ b/test/local/reputation_service_client_tests.js @@ -13,6 +13,7 @@ var client = new IPReputationClient({ timeout: 150 }); var invalidIPError = new Error('Invalid IP.'); +var invalidEmailError = new Error('Invalid Email.'); // Tests in this file are specifically ordered @@ -68,6 +69,32 @@ test( } ); +test( + 'does not get reputation for a nonexistent email', + function (t) { + client.getTyped('email', 'not-there@example.com').then(function (response) { + t.equal(response.statusCode, 404); + t.end(); + }); + } +); + +test( + 'does not get reputation for a invalid type', + function (t) { + t.rejects(client.getTyped('not-real', 'foobar'), new Error('Invalid type: not-real')); + t.end(); + } +); + +test( + 'does not get reputation for a invalid email', + function (t) { + t.rejects(client.getTyped('email', 'not-an-email'), invalidEmailError); + t.end(); + } +); + test( 'does not get reputation for a invalid IP', function (t) { @@ -100,6 +127,26 @@ test( } ); +test( + 'update reputation for new email', + function (t) { + client.updateTyped('email', 'picard@example.com', 50).then(function (response) { + t.equal(response.statusCode, 200); + t.end(); + }); + } +); + +test( + 'update reputation for existing email', + function (t) { + client.updateTyped('email', 'picard@example.com', 57).then(function (response) { + t.equal(response.statusCode, 200); + t.end(); + }); + } +); + test( 'update reputation for new IP', function (t) { @@ -133,7 +180,21 @@ test( ); test( - 'gets reputation for a existing IP', + 'gets reputation for an existing IP (typed)', + function (t) { + client.getTyped('ip', '127.0.0.1').then(function (response) { + t.equal(response.statusCode, 200); + t.equal(response.body.reputation, 75); + t.equal(response.body.object, '127.0.0.1'); + t.equal(response.body.reviewed, false); + // also response.body.lastupdated, which is dynamic (time of previous test request) + t.end(); + }); + } +); + +test( + 'gets reputation for an existing IP', function (t) { client.get('127.0.0.1').then(function (response) { t.equal(response.statusCode, 200); @@ -146,6 +207,34 @@ test( } ); +test( + 'gets reputation for an existing email', + function (t) { + client.getTyped('email', 'picard@example.com').then(function (response) { + t.equal(response.statusCode, 200); + t.equal(response.body.reputation, 57); + t.equal(response.body.object, 'picard@example.com'); + t.equal(response.body.reviewed, false); + t.end(); + }); + } +); + +test( + 'removes reputation for existing email', + function (t) { + client.removeTyped('email', 'picard@example.com').then(function (response) { + t.equal(response.statusCode, 200); + t.equal(response.body, undefined); + return client.getTyped('email', 'picard@example.com'); + }).then(function (response) { + t.equal(response.statusCode, 404); + t.equal(response.body, undefined); // JSON.stringify() -> undefined + t.end(); + }); + } +); + test( 'removes reputation for existing IP', function (t) {