🔒️ Rate limit registration endpoint (#1153)

This commit is contained in:
Luke Vella 2024-06-17 21:42:16 +01:00 committed by GitHub
parent 05d1e56805
commit 491af5c71b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 75 additions and 9 deletions

View file

@ -36,6 +36,8 @@
"@trpc/client": "^10.13.0",
"@trpc/next": "^10.13.0",
"@trpc/react-query": "^10.13.0",
"@upstash/ratelimit": "^1.2.1",
"@vercel/kv": "^2.0.0",
"@vercel/functions": "^1.0.2",
"accept-language-parser": "^1.5.0",
"autoprefixer": "^10.4.13",

View file

@ -2,6 +2,8 @@ import { createTRPCContext } from "@rallly/backend/trpc/context";
import { AppRouter, appRouter } from "@rallly/backend/trpc/routers";
import * as Sentry from "@sentry/nextjs";
import { createNextApiHandler } from "@trpc/server/adapters/next";
import { Ratelimit } from "@upstash/ratelimit";
import { kv } from "@vercel/kv";
import { posthog, posthogApiHandler } from "@/app/posthog";
import { absoluteUrl, shortUrl } from "@/utils/absolute-url";
@ -10,6 +12,12 @@ import { isSelfHosted } from "@/utils/constants";
import { emailClient } from "@/utils/emails";
import { composeApiHandlers } from "@/utils/next";
const ratelimit = new Ratelimit({
redis: kv,
// 5 requests from the same user in 10 seconds
limiter: Ratelimit.slidingWindow(5, "10 s"),
});
export const config = {
api: {
externalResolver: true,
@ -38,6 +46,12 @@ const trpcApiHandler = createNextApiHandler<AppRouter>({
isEmailBlocked,
absoluteUrl,
shortUrl,
ratelimit: async (key: string) => {
if (!process.env.KV_REST_API_URL) {
return { success: true };
}
return ratelimit.limit(key);
},
});
return res;

View file

@ -21,6 +21,7 @@ export interface TRPCContextParams {
*/
absoluteUrl: (path?: string) => string;
shortUrl: (path?: string) => string;
ratelimit: (key: string) => Promise<{ success: boolean }>;
}
export const createTRPCContext = async (

View file

@ -1,4 +1,5 @@
import { prisma } from "@rallly/database";
import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { createToken, decryptToken } from "../../session";
@ -23,6 +24,16 @@ 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",
});
}
}
if (ctx.isEmailBlocked?.(input.email)) {
return { ok: false, reason: "emailNotAllowed" };
}
@ -50,10 +61,9 @@ export const auth = router({
await ctx.emailClient.sendTemplate("RegisterEmail", {
to: input.email,
subject: `${input.name}, please verify your email address`,
subject: "Please verify your email address",
props: {
code,
name: input.name,
},
});

View file

@ -64,6 +64,14 @@ export const participants = router({
}),
)
.mutation(async ({ ctx, input: { pollId, votes, name, email } }) => {
const { success } = await ctx.ratelimit(ctx.user.id);
if (!success) {
throw new TRPCError({
code: "TOO_MANY_REQUESTS",
message: "You are doing that too much",
});
}
const { user } = ctx;
const poll = await prisma.poll.findUnique({

View file

@ -13,7 +13,7 @@ import { fontFamily, Section, Text } from "./styled-components";
export interface EmailLayoutProps {
preview: string;
recipientName: string;
recipientName?: string;
footNote?: React.ReactNode;
ctx: EmailContext;
}
@ -40,7 +40,7 @@ const linkStyles = {
export const EmailLayout = ({
preview,
recipientName = "Guest",
recipientName,
children,
footNote,
ctx,
@ -60,7 +60,7 @@ export const EmailLayout = ({
<Container style={containerStyles}>
<Img src={logoUrl} alt="Rallly" width={128} />
<Section style={sectionStyles}>
<Text>Hi {recipientName},</Text>
{recipientName ? <Text>Hi {recipientName},</Text> : null}
{children}
{footNote ? (
<Text

View file

@ -8,13 +8,11 @@ import {
} from "./_components/styled-components";
interface RegisterEmailProps {
name: string;
code: string;
ctx: EmailContext;
}
export const RegisterEmail = ({
name = "John",
code = "123456",
ctx = defaultEmailContext,
}: RegisterEmailProps) => {
@ -28,7 +26,6 @@ export const RegisterEmail = ({
please ignore this email.
</>
}
recipientName={name}
preview={`Your 6-digit code is: ${code}`}
>
<Text>

View file

@ -119,6 +119,7 @@
"STRIPE_SECRET_KEY",
"STRIPE_SIGNING_SECRET",
"STRIPE_YEARLY_PRICE",
"SUPPORT_EMAIL"
"SUPPORT_EMAIL",
"KV_REST_API_URL"
]
}

View file

@ -4715,6 +4715,27 @@
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==
"@upstash/core-analytics@^0.0.9":
version "0.0.9"
resolved "https://registry.yarnpkg.com/@upstash/core-analytics/-/core-analytics-0.0.9.tgz#59f29a920099084e049726c0aca67be1c1ecfc1f"
integrity sha512-9NXXxZ5y1/A/zqKLlVT7NsAWSggJfOjB0hG6Ffx29b4jbzHOiQVWB55h5+j2clT9Ib+mNPXn0iB5zN3aWLkICw==
dependencies:
"@upstash/redis" "^1.28.3"
"@upstash/ratelimit@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@upstash/ratelimit/-/ratelimit-1.2.1.tgz#835a33ce715e999d646431f70a71a69de7d439ee"
integrity sha512-o01lV1yFS5Fzj5KONZmNyVch/Qrlj785B2ob+kStUmxn8F6xXk7IHTQqVcHE+Ce3CmT/qQIwvMxDZftyJ5wYpQ==
dependencies:
"@upstash/core-analytics" "^0.0.9"
"@upstash/redis@^1.28.3", "@upstash/redis@^1.31.3":
version "1.31.5"
resolved "https://registry.yarnpkg.com/@upstash/redis/-/redis-1.31.5.tgz#8d5fe439a2a28638b3a354a23680ecf7f7eb4f54"
integrity sha512-2MatqeqftroSJ9Q+pqbyGAIwXX6KEPtUTUna2c/fq09h12ffwvltDTgfppeF+NzJo/SyZfHY8e1RoflduMbz1A==
dependencies:
crypto-js "^4.2.0"
"@vercel/analytics@^0.1.8":
version "0.1.11"
resolved "https://registry.npmjs.org/@vercel/analytics/-/analytics-0.1.11.tgz"
@ -4725,6 +4746,13 @@
resolved "https://registry.yarnpkg.com/@vercel/functions/-/functions-1.0.2.tgz#c26ed4e3b0ed701e28c4ebd71c76b1bfe14db02a"
integrity sha512-j3udyHOv/05Y8o3WQ/ANMWa1aYagsY5B3ouImiwgYsz5z4CBUHTY5dk74oQAXYr+bgoVDpdDlmxkpnxGzKEdLQ==
"@vercel/kv@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@vercel/kv/-/kv-2.0.0.tgz#a0baa12563946cb35cee23d638b68f0fbbf76172"
integrity sha512-zdVrhbzZBYo5d1Hfn4bKtqCeKf0FuzW8rSHauzQVMUgv1+1JOwof2mWcBuI+YMJy8s0G0oqAUfQ7HgUDzb8EbA==
dependencies:
"@upstash/redis" "^1.31.3"
"@vitest/expect@1.3.1":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-1.3.1.tgz#d4c14b89c43a25fd400a6b941f51ba27fe0cb918"
@ -5991,6 +6019,11 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
shebang-command "^2.0.0"
which "^2.0.1"
crypto-js@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631"
integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==
crypto@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz"