From f7eda38e0ab8a49bc2346f2874a5d193c57a5aef Mon Sep 17 00:00:00 2001 From: Luke Vella Date: Mon, 17 Jun 2024 22:13:40 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=92=EF=B8=8F=20Rate=20limit=20by=20ip?= =?UTF-8?q?=20address=20(#1155)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/package.json | 2 ++ apps/web/src/pages/api/trpc/[trpc].ts | 12 ++++++++++-- packages/backend/trpc/context.ts | 2 +- packages/backend/trpc/routers/auth.ts | 14 ++++++-------- .../backend/trpc/routers/polls/participants.ts | 2 +- yarn.lock | 12 ++++++++++++ 6 files changed, 32 insertions(+), 12 deletions(-) diff --git a/apps/web/package.json b/apps/web/package.json index 3d26b6526..9a2cd6fe5 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -38,6 +38,7 @@ "@trpc/react-query": "^10.13.0", "@upstash/ratelimit": "^1.2.1", "@vercel/kv": "^2.0.0", + "request-ip": "^3.3.0", "@vercel/functions": "^1.0.2", "accept-language-parser": "^1.5.0", "autoprefixer": "^10.4.13", @@ -83,6 +84,7 @@ "@rallly/eslint-config": "*", "@types/accept-language-parser": "^1.5.3", "@types/color-hash": "^1.0.2", + "@types/request-ip": "^0.0.41", "@types/lodash": "^4.14.178", "@types/react-big-calendar": "^1.8.8", "@types/smoothscroll-polyfill": "^0.3.1", diff --git a/apps/web/src/pages/api/trpc/[trpc].ts b/apps/web/src/pages/api/trpc/[trpc].ts index 858f8bb78..8b89856a5 100644 --- a/apps/web/src/pages/api/trpc/[trpc].ts +++ b/apps/web/src/pages/api/trpc/[trpc].ts @@ -4,6 +4,7 @@ import * as Sentry from "@sentry/nextjs"; import { createNextApiHandler } from "@trpc/server/adapters/next"; import { Ratelimit } from "@upstash/ratelimit"; import { kv } from "@vercel/kv"; +import requestIp from "request-ip"; import { posthog, posthogApiHandler } from "@/app/posthog"; import { absoluteUrl, shortUrl } from "@/utils/absolute-url"; @@ -46,11 +47,18 @@ const trpcApiHandler = createNextApiHandler({ isEmailBlocked, absoluteUrl, shortUrl, - ratelimit: async (key: string) => { + ratelimit: async () => { if (!process.env.KV_REST_API_URL) { return { success: true }; } - return ratelimit.limit(key); + + const clientIp = requestIp.getClientIp(opts.req); + + if (!clientIp) { + return { success: false }; + } + + return ratelimit.limit(clientIp); }, }); diff --git a/packages/backend/trpc/context.ts b/packages/backend/trpc/context.ts index 1983d63b9..1106c123f 100644 --- a/packages/backend/trpc/context.ts +++ b/packages/backend/trpc/context.ts @@ -21,7 +21,7 @@ export interface TRPCContextParams { */ absoluteUrl: (path?: string) => string; shortUrl: (path?: string) => string; - ratelimit: (key: string) => Promise<{ success: boolean }>; + ratelimit: () => Promise<{ success: boolean }>; } export const createTRPCContext = async ( diff --git a/packages/backend/trpc/routers/auth.ts b/packages/backend/trpc/routers/auth.ts index 5b2a9e4f8..f987c0155 100644 --- a/packages/backend/trpc/routers/auth.ts +++ b/packages/backend/trpc/routers/auth.ts @@ -24,14 +24,12 @@ export const auth = router({ | { ok: true; token: string } | { ok: false; reason: "userAlreadyExists" | "emailNotAllowed" } > => { - if (process.env.KV_REST_API_URL) { - const { success } = await ctx.ratelimit(ctx.user.id); - if (!success) { - throw new TRPCError({ - code: "TOO_MANY_REQUESTS", - message: "Too many requests", - }); - } + const { success } = await ctx.ratelimit(); + if (!success) { + throw new TRPCError({ + code: "TOO_MANY_REQUESTS", + message: "Too many requests", + }); } if (ctx.isEmailBlocked?.(input.email)) { diff --git a/packages/backend/trpc/routers/polls/participants.ts b/packages/backend/trpc/routers/polls/participants.ts index ad2303884..559ce2287 100644 --- a/packages/backend/trpc/routers/polls/participants.ts +++ b/packages/backend/trpc/routers/polls/participants.ts @@ -64,7 +64,7 @@ export const participants = router({ }), ) .mutation(async ({ ctx, input: { pollId, votes, name, email } }) => { - const { success } = await ctx.ratelimit(ctx.user.id); + const { success } = await ctx.ratelimit(); if (!success) { throw new TRPCError({ diff --git a/yarn.lock b/yarn.lock index b31b6fa01..26f08893d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4538,6 +4538,13 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/request-ip@^0.0.41": + version "0.0.41" + resolved "https://registry.yarnpkg.com/@types/request-ip/-/request-ip-0.0.41.tgz#c22a3244df2573402989346062851b06b7a5ac4e" + integrity sha512-Qzz0PM2nSZej4lsLzzNfADIORZhhxO7PED0fXpg4FjXiHuJ/lMyUg+YFF5q8x9HPZH3Gl6N+NOM8QZjItNgGKg== + dependencies: + "@types/node" "*" + "@types/scheduler@*": version "0.16.2" resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz" @@ -10990,6 +10997,11 @@ replace-ext@^1.0.0: resolved "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz" integrity sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw== +request-ip@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/request-ip/-/request-ip-3.3.0.tgz#863451e8fec03847d44f223e30a5d63e369fa611" + integrity sha512-cA6Xh6e0fDBBBwH77SLJaJPBmD3nWVAcF9/XAcsrIHdjhFzFiB5aNQFytdjCGPezU3ROwrR11IddKAM08vohxA== + resize-observer-polyfill@^1.5.1: version "1.5.1" resolved "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz"