diff --git a/rpg-docs/Model/Meta/Blacklist.js b/rpg-docs/Model/Meta/Blacklist.js new file mode 100644 index 00000000..221c64ec --- /dev/null +++ b/rpg-docs/Model/Meta/Blacklist.js @@ -0,0 +1,9 @@ +Blacklist = new Mongo.Collection("blacklist"); + +Schemas.Blacklist = new SimpleSchema({ + userId: { + type: String, + }, +}); + +Blacklist.attachSchema(Schemas.Blacklist); diff --git a/rpg-docs/Routes/API.js b/rpg-docs/Routes/API.js index 451ac2ee..6ad6d71b 100644 --- a/rpg-docs/Routes/API.js +++ b/rpg-docs/Routes/API.js @@ -42,12 +42,21 @@ var ifKeyValid = function(apiKey, response, callback){ }; var isKeyValid = function(apiKey){ - return !!Meteor.users.findOne({apiKey}); + var user = Meteor.users.findOne({apiKey}); + if (!user) return false; + var blackListed = Blacklist.findOne({userId: user._id}); + return !blackListed; }; var rateLimiter = new RateLimiter(); rateLimiter.addRule({apiKey: String}, 2, 10000); var isRateLimited = function(apiKey){ - return !rateLimiter.check({apiKey}).allowed; + const limited = !rateLimiter.check({apiKey}).allowed + if (limited) { + console.log(`Rate limit hit by API key ${apiKey}`); + return true; + } else { + return false; + } }; diff --git a/rpg-docs/Routes/Routes.js b/rpg-docs/Routes/Routes.js index 65b4cbb0..5d67332a 100644 --- a/rpg-docs/Routes/Routes.js +++ b/rpg-docs/Routes/Routes.js @@ -13,6 +13,11 @@ Router.plugin("ensureSignedIn", { Router.plugin("dataNotFound", {notFoundTemplate: "notFound"}); +var handleSubError = function(e){ + Session.set("error", {reason: e.reason, href: location.href}); + Router.go("/error"); +}; + Router.map(function() { this.route("/", { name: "home", @@ -36,7 +41,9 @@ Router.map(function() { path: "/character/:_id/", waitOn: function(){ return [ - subsManager.subscribe("singleCharacter", this.params._id), + subsManager.subscribe( + "singleCharacter", this.params._id, {onError: handleSubError} + ), ]; }, action: function(){ @@ -52,7 +59,9 @@ Router.map(function() { path: "/character/:_id/:urlName", waitOn: function(){ return [ - subsManager.subscribe("singleCharacter", this.params._id), + subsManager.subscribe( + "singleCharacter", this.params._id, {onError: handleSubError} + ), ]; }, data: function() { @@ -82,7 +91,9 @@ Router.map(function() { path: "/character/:_id/:urlName/print", waitOn: function(){ return [ - subsManager.subscribe("singleCharacter", this.params._id), + subsManager.subscribe( + "singleCharacter", this.params._id, {onError: handleSubError} + ), ]; }, data: function() { @@ -153,4 +164,11 @@ Router.map(function() { document.title = appName; }, }); + + this.route("/error", { + name: "error", + onAfterAction: function() { + document.title = `${appName} - Error`; + }, + }); }); diff --git a/rpg-docs/client/views/meta/error/error.html b/rpg-docs/client/views/meta/error/error.html new file mode 100644 index 00000000..945893f9 --- /dev/null +++ b/rpg-docs/client/views/meta/error/error.html @@ -0,0 +1,20 @@ + diff --git a/rpg-docs/client/views/meta/error/error.js b/rpg-docs/client/views/meta/error/error.js new file mode 100644 index 00000000..3870d686 --- /dev/null +++ b/rpg-docs/client/views/meta/error/error.js @@ -0,0 +1,17 @@ +Template.error.onRendered(function(){ + const error = Session.get("error") || {}; + if (error.href) window.history.replaceState("", "", error.href); +}); + +Template.error.helpers({ + errorMessage: function(){ + const error = Session.get("error") || {}; + return error.reason; + }, +}); + +Template.error.events({ + "click .try-again": function(event, instance){ + window.location.reload(); + }, +}); diff --git a/rpg-docs/server/lib/logRateError.js b/rpg-docs/server/lib/logRateError.js new file mode 100644 index 00000000..b261c3eb --- /dev/null +++ b/rpg-docs/server/lib/logRateError.js @@ -0,0 +1,8 @@ +logRateError = function(reply, ruleInput){ + // reply = {allowed, timeToReset, numInvocationsLeft} + // ruleInput = {userId, clientAddress, type, name, connectionId} + console.log( + `Limit hit for ${ruleInput.type} "${ruleInput.name}" ` + + `by user ${ruleInput.userId} from ${ruleInput.clientAddress}` + ); +} diff --git a/rpg-docs/server/publications/singleCharacter.js b/rpg-docs/server/publications/singleCharacter.js index 32e6f6a6..777a2ecb 100644 --- a/rpg-docs/server/publications/singleCharacter.js +++ b/rpg-docs/server/publications/singleCharacter.js @@ -38,9 +38,13 @@ Meteor.publish("singleCharacter", function(characterId){ DDPRateLimiter.addRule({ name: "singleCharacter", type: "subscription", - userId(){ return true; }, + userId: null, connectionId(){ return true; }, -}, 8, 5000); +}, 8, 10000, function(reply, ruleInput){ + if(!reply.allowed){ + logRateError(reply, ruleInput); + } +}); Meteor.publish("singleCharacterName", function(characterId){ userId = this.userId;