mirror of
https://github.com/lukevella/rallly.git
synced 2025-06-02 18:51:52 +02:00
🔒️ Rate limit registration endpoint (#1153)
This commit is contained in:
parent
05d1e56805
commit
491af5c71b
9 changed files with 75 additions and 9 deletions
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -119,6 +119,7 @@
|
|||
"STRIPE_SECRET_KEY",
|
||||
"STRIPE_SIGNING_SECRET",
|
||||
"STRIPE_YEARLY_PRICE",
|
||||
"SUPPORT_EMAIL"
|
||||
"SUPPORT_EMAIL",
|
||||
"KV_REST_API_URL"
|
||||
]
|
||||
}
|
||||
|
|
33
yarn.lock
33
yarn.lock
|
@ -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"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue