From 0a319a9330d7f47eed05cdd3e418979e63488d6f Mon Sep 17 00:00:00 2001 From: jonathanzhu Date: Fri, 1 Aug 2025 01:50:44 -0400 Subject: [PATCH 1/5] feat: add rate limit by user --- app/auth/confirm/actions.ts | 14 +++++++++++--- app/auth/confirm/page.tsx | 12 ++++++++++-- app/rateLimiter.ts | 35 +++++++++++++++++++++++++++++++++++ app/signin/Form.tsx | 2 +- app/signin/actions.ts | 1 + app/signup/Form.tsx | 2 +- app/signup/actions.ts | 2 +- package.json | 3 +++ supabase/config.toml | 6 +++--- 9 files changed, 66 insertions(+), 11 deletions(-) create mode 100644 app/rateLimiter.ts diff --git a/app/auth/confirm/actions.ts b/app/auth/confirm/actions.ts index 9e5e8fc..d773a59 100644 --- a/app/auth/confirm/actions.ts +++ b/app/auth/confirm/actions.ts @@ -3,8 +3,9 @@ import { createClient, createAdminClient } from "@/utils/supabase/server"; import { redirect } from "next/navigation"; import dayjs from "dayjs"; +import { queryRateLimit } from "@/app/rateLimiter"; -const ms_to_minute = 60 * 1000; +const minutes_to_ms = 60 * 1000; export const verifyToken = async (email: string, token: string) => { // const token = formData.get("token")?.toString(); @@ -29,6 +30,11 @@ export const verifyToken = async (email: string, token: string) => { export const resendMagicLink = async (email: string) => { const supabase = await createClient(); + const { error: RateLimitError } = await queryRateLimit(email); + if (RateLimitError) { + return { error: RateLimitError }; + } + const { error } = await supabase.auth.signInWithOtp({ email, options: { @@ -38,8 +44,10 @@ export const resendMagicLink = async (email: string) => { }); if (error) { - console.error(error.code + " " + error.message); + return { error }; } + + return {}; }; // TODO: decide on how much time to allow users to be able to enter code for @@ -67,7 +75,7 @@ export const canLoadPage = async (email: string) => { m_time.add(20, "minute"); // change this to minutes later // console.log(dayjs()); console.log("difference", dayjs().diff(m_time)); - if (dayjs().diff(m_time) < 20 * ms_to_minute) { + if (dayjs().diff(m_time) < 20 * minutes_to_ms) { return true; } diff --git a/app/auth/confirm/page.tsx b/app/auth/confirm/page.tsx index 92eeecf..1c0cf99 100644 --- a/app/auth/confirm/page.tsx +++ b/app/auth/confirm/page.tsx @@ -25,6 +25,9 @@ export default function Confirm() { const [value, setValue] = useState(""); const [disabled, setDisabled] = useState(false); const [error, setError] = useState(undefined); + const [rateLimitError, setRateLimitError] = useState( + undefined + ); const { focusRef, setFocus } = useInputFocus(); useEffect(() => { @@ -103,8 +106,13 @@ export default function Confirm() {