From 55e622e22ff741ad23cc52ece9b93cc9fc5bd15f Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sun, 2 Oct 2022 19:27:21 -0400 Subject: [PATCH] chore: More rate limited endpoints --- server/RateLimiter.ts | 5 +++ server/routes/api/team.ts | 85 +++++++++++++++++++++----------------- server/routes/api/views.ts | 65 ++++++++++++++++------------- 3 files changed, 87 insertions(+), 68 deletions(-) diff --git a/server/RateLimiter.ts b/server/RateLimiter.ts index 1c80d028a..9aa6a2e16 100644 --- a/server/RateLimiter.ts +++ b/server/RateLimiter.ts @@ -49,6 +49,11 @@ export const RateLimiterStrategy = { duration: 60, requests: 10, }, + /** Allows one thousand requests per hour, per IP address */ + OneThousandPerHour: { + duration: 3600, + requests: 1000, + }, /** Allows ten requests per hour, per IP address */ TenPerHour: { duration: 3600, diff --git a/server/routes/api/team.ts b/server/routes/api/team.ts index 68ddc8f20..b2be013ea 100644 --- a/server/routes/api/team.ts +++ b/server/routes/api/team.ts @@ -1,6 +1,8 @@ import Router from "koa-router"; +import { RateLimiterStrategy } from "@server/RateLimiter"; import teamUpdater from "@server/commands/teamUpdater"; import auth from "@server/middlewares/authentication"; +import { rateLimiter } from "@server/middlewares/rateLimiter"; import { Team, TeamDomain } from "@server/models"; import { authorize } from "@server/policies"; import { presentTeam, presentPolicies } from "@server/presenters"; @@ -8,35 +10,12 @@ import { assertUuid } from "@server/validation"; const router = new Router(); -router.post("team.update", auth(), async (ctx) => { - const { - name, - avatarUrl, - subdomain, - sharing, - guestSignin, - documentEmbeds, - memberCollectionCreate, - collaborativeEditing, - defaultCollectionId, - defaultUserRole, - inviteRequired, - allowedDomains, - preferences, - } = ctx.body; - - const { user } = ctx.state; - const team = await Team.findByPk(user.teamId, { - include: [{ model: TeamDomain }], - }); - authorize(user, "update", team); - - if (defaultCollectionId !== undefined && defaultCollectionId !== null) { - assertUuid(defaultCollectionId, "defaultCollectionId must be uuid"); - } - - const updatedTeam = await teamUpdater({ - params: { +router.post( + "team.update", + auth(), + rateLimiter(RateLimiterStrategy.TenPerHour), + async (ctx) => { + const { name, avatarUrl, subdomain, @@ -50,16 +29,44 @@ router.post("team.update", auth(), async (ctx) => { inviteRequired, allowedDomains, preferences, - }, - user, - team, - ip: ctx.request.ip, - }); + } = ctx.body; - ctx.body = { - data: presentTeam(updatedTeam), - policies: presentPolicies(user, [updatedTeam]), - }; -}); + const { user } = ctx.state; + const team = await Team.findByPk(user.teamId, { + include: [{ model: TeamDomain }], + }); + authorize(user, "update", team); + + if (defaultCollectionId !== undefined && defaultCollectionId !== null) { + assertUuid(defaultCollectionId, "defaultCollectionId must be uuid"); + } + + const updatedTeam = await teamUpdater({ + params: { + name, + avatarUrl, + subdomain, + sharing, + guestSignin, + documentEmbeds, + memberCollectionCreate, + collaborativeEditing, + defaultCollectionId, + defaultUserRole, + inviteRequired, + allowedDomains, + preferences, + }, + user, + team, + ip: ctx.request.ip, + }); + + ctx.body = { + data: presentTeam(updatedTeam), + policies: presentPolicies(user, [updatedTeam]), + }; + } +); export default router; diff --git a/server/routes/api/views.ts b/server/routes/api/views.ts index e323d34a2..c0c55afcf 100644 --- a/server/routes/api/views.ts +++ b/server/routes/api/views.ts @@ -1,5 +1,7 @@ import Router from "koa-router"; +import { RateLimiterStrategy } from "@server/RateLimiter"; import auth from "@server/middlewares/authentication"; +import { rateLimiter } from "@server/middlewares/rateLimiter"; import { View, Document, Event } from "@server/models"; import { authorize } from "@server/policies"; import { presentView } from "@server/presenters"; @@ -23,38 +25,43 @@ router.post("views.list", auth(), async (ctx) => { }; }); -router.post("views.create", auth(), async (ctx) => { - const { documentId } = ctx.body; - assertUuid(documentId, "documentId is required"); +router.post( + "views.create", + auth(), + rateLimiter(RateLimiterStrategy.OneThousandPerHour), + async (ctx) => { + const { documentId } = ctx.body; + assertUuid(documentId, "documentId is required"); - const { user } = ctx.state; - const document = await Document.findByPk(documentId, { - userId: user.id, - }); - authorize(user, "read", document); + const { user } = ctx.state; + const document = await Document.findByPk(documentId, { + userId: user.id, + }); + authorize(user, "read", document); - const view = await View.incrementOrCreate({ - documentId, - userId: user.id, - }); + const view = await View.incrementOrCreate({ + documentId, + userId: user.id, + }); - await Event.create({ - name: "views.create", - actorId: user.id, - documentId: document.id, - collectionId: document.collectionId, - teamId: user.teamId, - modelId: view.id, - data: { - title: document.title, - }, - ip: ctx.request.ip, - }); - view.user = user; + await Event.create({ + name: "views.create", + actorId: user.id, + documentId: document.id, + collectionId: document.collectionId, + teamId: user.teamId, + modelId: view.id, + data: { + title: document.title, + }, + ip: ctx.request.ip, + }); + view.user = user; - ctx.body = { - data: presentView(view), - }; -}); + ctx.body = { + data: presentView(view), + }; + } +); export default router;