From ea8d036c727b2ff93151c2ad9ed0677f7c6f3ef6 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Sat, 6 Jun 2020 14:23:13 +0200 Subject: [PATCH] Added rate limiting to all methods --- .../api/creature/CreatureProperties.js | 46 +++++++++++++++++++ app/imports/api/creature/Creatures.js | 12 +++++ .../creature/computation/recomputeCreature.js | 7 +++ .../recomputeDamageMultipliers.js | 7 +++ .../api/creature/experience/Experiences.js | 16 +++++++ app/imports/api/creature/removeCreature.js | 6 +++ app/imports/api/creature/restCreature.js | 6 +++ app/imports/api/icons/Icons.js | 6 +++ app/imports/api/library/Libraries.js | 16 +++++++ app/imports/api/library/LibraryNodes.js | 31 +++++++++++++ app/imports/api/parenting/organizeMethods.js | 11 +++++ app/imports/api/sharing/sharing.js | 11 +++++ app/imports/api/users/Invites.js | 16 +++++++ app/imports/api/users/Users.js | 36 +++++++++++++++ app/package-lock.json | 24 ++++++++++ app/package.json | 1 + 16 files changed, 252 insertions(+) diff --git a/app/imports/api/creature/CreatureProperties.js b/app/imports/api/creature/CreatureProperties.js index 20dec2c2..36c033b6 100644 --- a/app/imports/api/creature/CreatureProperties.js +++ b/app/imports/api/creature/CreatureProperties.js @@ -1,5 +1,6 @@ import { Meteor } from 'meteor/meteor'; import { Mongo } from 'meteor/mongo'; +import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; import { ValidatedMethod } from 'meteor/mdg:validated-method'; import SimpleSchema from 'simpl-schema'; import ColorSchema from '/imports/api/properties/subSchemas/ColorSchema.js'; @@ -78,6 +79,11 @@ function recomputeCreatures(property){ const insertProperty = new ValidatedMethod({ name: 'creatureProperties.insert', validate: null, + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({creatureProperty}) { delete creatureProperty._id; assertPropertyEditPermission(creatureProperty, this.userId); @@ -95,6 +101,11 @@ const duplicateProperty = new ValidatedMethod({ regEx: SimpleSchema.RegEx.Id, } }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({_id}) { let creatureProperty = CreatureProperties.findOne(_id); assertPropertyEditPermission(creatureProperty, this.userId); @@ -115,6 +126,11 @@ const insertPropertyFromLibraryNode = new ValidatedMethod({ type: RefSchema, }, }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({nodeId, parentRef}) { // get the new ancestry for the properties let {parentDoc, ancestors} = getAncestry({parentRef}); @@ -192,6 +208,11 @@ const updateProperty = new ValidatedMethod({ 'This property can\'t be updated directly'); } }, + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({_id, path, value}) { let property = CreatureProperties.findOne(_id); assertPropertyEditPermission(property, this.userId); @@ -220,6 +241,11 @@ const damageProperty = new ValidatedMethod({ }, value: Number, }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({_id, operation, value}) { let currentProperty = CreatureProperties.findOne(_id); // Check permissions @@ -274,6 +300,11 @@ const adjustQuantity = new ValidatedMethod({ }, value: Number, }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({_id, operation, value}) { let currentProperty = CreatureProperties.findOne(_id); // Check permissions @@ -309,6 +340,11 @@ const adjustQuantity = new ValidatedMethod({ const pushToProperty = new ValidatedMethod({ name: 'creatureProperties.push', validate: null, + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({_id, path, value}){ let property = CreatureProperties.findOne(_id); assertPropertyEditPermission(property, this.userId); @@ -324,6 +360,11 @@ const pushToProperty = new ValidatedMethod({ const pullFromProperty = new ValidatedMethod({ name: 'creatureProperties.pull', validate: null, + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({_id, path, itemId}){ let property = CreatureProperties.findOne(_id); assertPropertyEditPermission(property, this.userId); @@ -342,6 +383,11 @@ const softRemoveProperty = new ValidatedMethod({ validate: new SimpleSchema({ _id: SimpleSchema.RegEx.Id }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({_id}){ let property = CreatureProperties.findOne(_id); assertPropertyEditPermission(property, this.userId); diff --git a/app/imports/api/creature/Creatures.js b/app/imports/api/creature/Creatures.js index 2e602b8a..197941b5 100644 --- a/app/imports/api/creature/Creatures.js +++ b/app/imports/api/creature/Creatures.js @@ -1,4 +1,5 @@ import { ValidatedMethod } from 'meteor/mdg:validated-method'; +import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; import SimpleSchema from 'simpl-schema'; import deathSaveSchema from '/imports/api/properties/subSchemas/DeathSavesSchema.js' import ColorSchema from '/imports/api/properties/subSchemas/ColorSchema.js'; @@ -124,6 +125,12 @@ const insertCreature = new ValidatedMethod({ validate: null, + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, + run() { if (!this.userId) { throw new Meteor.Error('Creatures.methods.insert.denied', @@ -164,6 +171,11 @@ const updateCreature = new ValidatedMethod({ 'This field can\'t be updated using this method'); } }, + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({_id, path, value}) { let creature = Creatures.findOne(_id); assertEditPermission(creature, this.userId); diff --git a/app/imports/api/creature/computation/recomputeCreature.js b/app/imports/api/creature/computation/recomputeCreature.js index 33346d27..56dc3d15 100644 --- a/app/imports/api/creature/computation/recomputeCreature.js +++ b/app/imports/api/creature/computation/recomputeCreature.js @@ -1,4 +1,5 @@ import { ValidatedMethod } from 'meteor/mdg:validated-method'; +import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; import SimpleSchema from 'simpl-schema'; import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js'; import ComputationMemo from '/imports/api/creature/computation/ComputationMemo.js'; @@ -17,6 +18,12 @@ export const recomputeCreature = new ValidatedMethod({ charId: { type: String } }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, + run({charId}) { let creature = Creatures.findOne(charId); // Permission diff --git a/app/imports/api/creature/damageMultiplierDenormalise/recomputeDamageMultipliers.js b/app/imports/api/creature/damageMultiplierDenormalise/recomputeDamageMultipliers.js index 3f60dc82..515fb9a4 100644 --- a/app/imports/api/creature/damageMultiplierDenormalise/recomputeDamageMultipliers.js +++ b/app/imports/api/creature/damageMultiplierDenormalise/recomputeDamageMultipliers.js @@ -1,4 +1,5 @@ import { ValidatedMethod } from 'meteor/mdg:validated-method'; +import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; import SimpleSchema from 'simpl-schema'; import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js'; import Creatures from '/imports/api/creature/Creatures.js'; @@ -12,6 +13,12 @@ export const recomputeDamageMultipliers = new ValidatedMethod({ creatureId: { type: String } }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, + run({creatureId}) { // Permission assertEditPermission(creatureId, this.userId); diff --git a/app/imports/api/creature/experience/Experiences.js b/app/imports/api/creature/experience/Experiences.js index af51d48a..88e6abc5 100644 --- a/app/imports/api/creature/experience/Experiences.js +++ b/app/imports/api/creature/experience/Experiences.js @@ -1,5 +1,6 @@ import SimpleSchema from 'simpl-schema'; import { ValidatedMethod } from 'meteor/mdg:validated-method'; +import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; import { getUserTier } from '/imports/api/users/patreon/tiers.js'; import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js'; import Creatures from '/imports/api/creature/Creatures.js'; @@ -78,6 +79,11 @@ const insertExperience = new ValidatedMethod({ regEx: SimpleSchema.RegEx.Id, }, }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({experience, creatureIds}) { let userId = this.userId; if (!userId) { @@ -106,6 +112,11 @@ const removeExperience = new ValidatedMethod({ regEx: SimpleSchema.RegEx.Id, }, }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({experienceId}) { let userId = this.userId; if (!userId) { @@ -146,6 +157,11 @@ const recomputeExperiences = new ValidatedMethod({ regEx: SimpleSchema.RegEx.Id, }, }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({creatureId}) { let userId = this.userId; if (!userId) { diff --git a/app/imports/api/creature/removeCreature.js b/app/imports/api/creature/removeCreature.js index 4f263e98..e97318f1 100644 --- a/app/imports/api/creature/removeCreature.js +++ b/app/imports/api/creature/removeCreature.js @@ -1,5 +1,6 @@ import SimpleSchema from 'simpl-schema'; import { ValidatedMethod } from 'meteor/mdg:validated-method'; +import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; import Creatures from '/imports/api/creature/Creatures.js'; import CreatureProperties from '/imports/api/creature/CreatureProperties.js' import { assertOwnership } from '/imports/api/creature/creaturePermissions.js'; @@ -18,6 +19,11 @@ const removeCreature = new ValidatedMethod({ regEx: SimpleSchema.RegEx.Id, }, }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({charId}) { assertOwnership(charId, this.userId) Creatures.remove(charId); diff --git a/app/imports/api/creature/restCreature.js b/app/imports/api/creature/restCreature.js index 66b2a88e..1f8a5fa9 100644 --- a/app/imports/api/creature/restCreature.js +++ b/app/imports/api/creature/restCreature.js @@ -1,5 +1,6 @@ import SimpleSchema from 'simpl-schema'; import { ValidatedMethod } from 'meteor/mdg:validated-method'; +import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; import Creatures from '/imports/api/creature/Creatures.js'; import CreatureProperties from '/imports/api/creature/CreatureProperties.js'; import getActiveProperties, { getActivePropertyFilter } from '/imports/api/creature/getActiveProperties.js'; @@ -18,6 +19,11 @@ const restCreature = new ValidatedMethod({ allowedValues: ['shortRest', 'longRest'], }, }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({creatureId, restType}) { let creature = Creatures.findOne(creatureId, { fields: { diff --git a/app/imports/api/icons/Icons.js b/app/imports/api/icons/Icons.js index 9f4224c6..4b6e769a 100644 --- a/app/imports/api/icons/Icons.js +++ b/app/imports/api/icons/Icons.js @@ -1,5 +1,6 @@ import SimpleSchema from 'simpl-schema'; import { ValidatedMethod } from 'meteor/mdg:validated-method'; +import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; import { assertAdmin } from '/imports/api/sharing/sharingPermissions.js'; let Icons = new Mongo.Collection('icons'); @@ -68,6 +69,11 @@ const findIcons = new ValidatedMethod({ optional: true, }, }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({search}){ if (!search) return []; if (!Meteor.isServer) return; diff --git a/app/imports/api/library/Libraries.js b/app/imports/api/library/Libraries.js index 85ea0b97..886d7f11 100644 --- a/app/imports/api/library/Libraries.js +++ b/app/imports/api/library/Libraries.js @@ -1,4 +1,5 @@ import { ValidatedMethod } from 'meteor/mdg:validated-method'; +import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; import SimpleSchema from 'simpl-schema'; import SharingSchema from '/imports/api/sharing/SharingSchema.js'; import simpleSchemaMixin from '/imports/api/creature/mixins/simpleSchemaMixin.js'; @@ -65,6 +66,11 @@ const updateLibraryName = new ValidatedMethod({ type: String, }, }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({_id, name}){ let library = Libraries.findOne(_id); assertEditPermission(library, this.userId); @@ -83,6 +89,11 @@ const setLibraryDefault = new ValidatedMethod({ type: Boolean, }, }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({_id, isDefault}) { if (!Meteor.users.isAdmin()){ throw new Meteor.Error('Permission denied', 'User must be admin to set libraries as default'); @@ -99,6 +110,11 @@ const removeLibrary = new ValidatedMethod({ regEx: SimpleSchema.RegEx.id }, }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({_id}){ let library = Libraries.findOne(_id); assertOwnership(library, this.userId); diff --git a/app/imports/api/library/LibraryNodes.js b/app/imports/api/library/LibraryNodes.js index edee24c2..d7ae17eb 100644 --- a/app/imports/api/library/LibraryNodes.js +++ b/app/imports/api/library/LibraryNodes.js @@ -1,6 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { Mongo } from 'meteor/mongo'; import { ValidatedMethod } from 'meteor/mdg:validated-method'; +import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; import SimpleSchema from 'simpl-schema'; import ColorSchema from '/imports/api/properties/subSchemas/ColorSchema.js'; import ChildSchema from '/imports/api/parenting/ChildSchema.js'; @@ -58,6 +59,11 @@ function assertNodeEditPermission(node, userId){ const insertNode = new ValidatedMethod({ name: 'libraryNodes.insert', validate: null, + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run(libraryNode) { delete libraryNode._id; assertNodeEditPermission(libraryNode, this.userId); @@ -73,6 +79,11 @@ const duplicateNode = new ValidatedMethod({ regEx: SimpleSchema.RegEx.Id, } }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({_id}) { let libraryNode = LibraryNodes.findOne(_id); assertNodeEditPermission(libraryNode, this.userId); @@ -94,6 +105,11 @@ const updateLibraryNode = new ValidatedMethod({ return false; } }, + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({_id, path, value}) { let node = LibraryNodes.findOne(_id); assertNodeEditPermission(node, this.userId); @@ -114,6 +130,11 @@ const updateLibraryNode = new ValidatedMethod({ const pushToLibraryNode = new ValidatedMethod({ name: 'libraryNodes.push', validate: null, + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({_id, path, value}){ let node = LibraryNodes.findOne(_id); assertNodeEditPermission(node, this.userId); @@ -128,6 +149,11 @@ const pushToLibraryNode = new ValidatedMethod({ const pullFromLibraryNode = new ValidatedMethod({ name: 'libraryNodes.pull', validate: null, + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({_id, path, itemId}){ let node = LibraryNodes.findOne(_id); assertNodeEditPermission(node, this.userId); @@ -145,6 +171,11 @@ const softRemoveLibraryNode = new ValidatedMethod({ validate: new SimpleSchema({ _id: SimpleSchema.RegEx.Id }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({_id}){ let node = LibraryNodes.findOne(_id); assertNodeEditPermission(node, this.userId); diff --git a/app/imports/api/parenting/organizeMethods.js b/app/imports/api/parenting/organizeMethods.js index 451f78df..9a0ac805 100644 --- a/app/imports/api/parenting/organizeMethods.js +++ b/app/imports/api/parenting/organizeMethods.js @@ -1,6 +1,7 @@ import SimpleSchema from 'simpl-schema'; import { union } from 'lodash'; import { ValidatedMethod } from 'meteor/mdg:validated-method'; +import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; import { updateParent } from '/imports/api/parenting/parenting.js'; import { reorderDocs, safeUpdateDocOrder } from '/imports/api/parenting/order.js'; import { RefSchema } from '/imports/api/parenting/ChildSchema.js'; @@ -19,6 +20,11 @@ const organizeDoc = new ValidatedMethod({ // Should end in 0.5 to place it reliably between two existing documents }, }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({docRef, parentRef, order}) { let doc = fetchDocByRef(docRef); let collection = getCollectionByName(docRef.collection); @@ -62,6 +68,11 @@ const reorderDoc = new ValidatedMethod({ // Should end in 0.5 to place it reliably between two existing documents }, }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({docRef, order}) { let doc = fetchDocByRef(docRef); assertDocEditPermission(doc, this.userId); diff --git a/app/imports/api/sharing/sharing.js b/app/imports/api/sharing/sharing.js index e597457f..616dd8e7 100644 --- a/app/imports/api/sharing/sharing.js +++ b/app/imports/api/sharing/sharing.js @@ -4,6 +4,7 @@ import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js'; import getCollectionByName from '/imports/api/parenting/getCollectionByName.js'; import { RefSchema } from '/imports/api/parenting/ChildSchema.js'; import { ValidatedMethod } from 'meteor/mdg:validated-method'; +import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; const setPublic = new ValidatedMethod({ name: 'sharing.setPublic', @@ -11,6 +12,11 @@ const setPublic = new ValidatedMethod({ docRef: RefSchema, isPublic: { type: Boolean }, }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({docRef, isPublic}){ let doc = fetchDocByRef(docRef); assertOwnership(doc, this.userId); @@ -33,6 +39,11 @@ const updateUserSharePermissions = new ValidatedMethod({ allowedValues: ['reader', 'writer', 'none'], }, }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({docRef, userId, role}){ let doc = fetchDocByRef(docRef); if (role === 'none'){ diff --git a/app/imports/api/users/Invites.js b/app/imports/api/users/Invites.js index 800aaa48..87a6301c 100644 --- a/app/imports/api/users/Invites.js +++ b/app/imports/api/users/Invites.js @@ -1,5 +1,6 @@ import SimpleSchema from 'simpl-schema'; import { ValidatedMethod } from 'meteor/mdg:validated-method'; +import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; import { getUserTier } from '/imports/api/users/patreon/tiers.js'; let Invites= new Mongo.Collection('invites'); @@ -92,6 +93,11 @@ const getInviteToken = new ValidatedMethod({ regEx: SimpleSchema.RegEx.Id, }, }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({inviteId}) { let invite = Invites.findOne(inviteId); if (this.userId !== invite.inviter) { @@ -115,6 +121,11 @@ const acceptInviteToken = new ValidatedMethod({ type: String, }, }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({inviteToken}) { if (!this.userId) { throw new Meteor.Error('Invites.methods.acceptToken.denied', @@ -153,6 +164,11 @@ const revokeInvite = new ValidatedMethod({ regEx: SimpleSchema.RegEx.Id, }, }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({inviteId}) { if (!this.userId) { throw new Meteor.Error('Invites.methods.revokeInvite.denied', diff --git a/app/imports/api/users/Users.js b/app/imports/api/users/Users.js index bd479b08..54789917 100644 --- a/app/imports/api/users/Users.js +++ b/app/imports/api/users/Users.js @@ -1,5 +1,6 @@ import SimpleSchema from 'simpl-schema'; import { ValidatedMethod } from 'meteor/mdg:validated-method'; +import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; const userSchema = new SimpleSchema({ username: { @@ -89,6 +90,11 @@ Meteor.users.attachSchema(userSchema); Meteor.users.generateApiKey = new ValidatedMethod({ name: 'users.generateApiKey', validate: null, + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run(){ if(Meteor.isClient) return; var user = Meteor.users.findOne(this.userId); @@ -104,6 +110,11 @@ Meteor.users.setDarkMode = new ValidatedMethod({ validate: new SimpleSchema({ darkMode: { type: Boolean }, }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({darkMode}){ if (!this.userId) return; Meteor.users.update(this.userId, {$set: {darkMode}}); @@ -121,6 +132,11 @@ Meteor.users.sendVerificationEmail = new ValidatedMethod({ type: String, }, }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({userId, address}){ userId = this.userId || userId; let user = Meteor.users.findOne(userId); @@ -145,6 +161,11 @@ Meteor.users.isAdmin = function(userId){ Meteor.users.canPickUsername = new ValidatedMethod({ name: 'users.canPickUsername', validate: userSchema.pick('username').validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({username}){ if (Meteor.isClient) return; let user = Accounts.findUserByUsername(username); @@ -159,6 +180,11 @@ Meteor.users.canPickUsername = new ValidatedMethod({ Meteor.users.setUsername = new ValidatedMethod({ name: 'users.setUsername', validate: userSchema.pick('username').validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({username}){ if (!this.userId) throw 'Can only set your username if logged in'; if (Meteor.isClient) return; @@ -177,6 +203,11 @@ Meteor.users.subscribeToLibrary = new ValidatedMethod({ type: Boolean, }, }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({libraryId, subscribe}){ if (!this.userId) throw 'Can only subscribe if logged in'; if (subscribe){ @@ -198,6 +229,11 @@ Meteor.users.findUserByUsernameOrEmail = new ValidatedMethod({ type: String, }, }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, run({usernameOrEmail}){ if (Meteor.isClient) return; let user = Accounts.findUserByUsername(usernameOrEmail) || diff --git a/app/package-lock.json b/app/package-lock.json index 7ab6d756..e3b62748 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -142,6 +142,22 @@ "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + } + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -375,6 +391,14 @@ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==" }, + "ddp-rate-limiter-mixin": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/ddp-rate-limiter-mixin/-/ddp-rate-limiter-mixin-1.1.10.tgz", + "integrity": "sha1-WIx5zw8RUrUKtbGHBkLKAcZy5uY=", + "requires": { + "babel-runtime": "6.x.x" + } + }, "debug": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", diff --git a/app/package.json b/app/package.json index faf9ae74..0c3f037b 100644 --- a/app/package.json +++ b/app/package.json @@ -24,6 +24,7 @@ "core-js": "^2.6.11", "css-box-shadow": "^1.0.0-3", "date-fns": "^1.30.1", + "ddp-rate-limiter-mixin": "^1.1.10", "dompurify": "^2.0.10", "lodash": "^4.17.15", "marked": "^0.8.2",