🔒️ Rate limit OTP attempts (#1713)

This commit is contained in:
Luke Vella 2025-05-14 14:37:27 +01:00 committed by GitHub
parent 2300ce65e5
commit 6d571b37c5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 48 additions and 10 deletions

View file

@ -338,5 +338,7 @@
"helpUsImproveDesc": "Take a few minutes to share your feedback and help us shape the future of Rallly.",
"giveFeedback": "Give feedback",
"homeActionsTitle": "Actions",
"dismissFeedback": "Don't show again"
"dismissFeedback": "Don't show again",
"tooManyAttempts": "Too many attempts, please try again later.",
"unknownError": "Something went wrong"
}

View file

@ -41,16 +41,36 @@ export function OTPForm({ email }: { email: string }) {
}/api/auth/callback/email?email=${encodeURIComponent(email.toLowerCase())}&token=${data.otp}`;
const res = await fetch(url);
const resUrl = new URL(res.url);
const hasError = !!resUrl.searchParams.get("error");
if (hasError) {
form.setError("otp", {
message: t("wrongVerificationCode"),
});
if (!res.ok) {
switch (res.status) {
case 429:
form.setError("otp", {
message: t("tooManyAttempts", {
defaultValue: "Too many attempts, please try again later.",
}),
});
break;
default:
form.setError("otp", {
message: t("unknownError", {
defaultValue: "Something went wrong",
}),
});
break;
}
} else {
window.location.href = searchParams?.get("redirectTo") ?? "/";
const resUrl = new URL(res.url);
const hasError = !!resUrl.searchParams.get("error");
if (hasError) {
form.setError("otp", {
message: t("wrongVerificationCode", {
defaultValue: "The code you entered is incorrect",
}),
});
} else {
window.location.href = searchParams?.get("redirectTo") ?? "/";
}
}
});

View file

@ -1,5 +1,20 @@
import { rateLimit } from "@/features/rate-limit";
import { handlers } from "@/next-auth";
import { withPosthog } from "@/utils/posthog";
import type { NextRequest } from "next/server";
export const GET = withPosthog(async (req: NextRequest) => {
if (req.nextUrl.pathname.includes("callback/email")) {
const { success } = await rateLimit("login_otp_attempt", 20, "15m");
if (!success) {
return new Response("Too many requests", {
status: 429,
});
}
}
return handlers.GET(req);
});
export const GET = withPosthog(handlers.GET);
export const POST = withPosthog(handlers.POST);

View file

@ -9,6 +9,7 @@ export const EmailProvider = NodemailerProvider({
server: "none", // This value is required even though we don't need it
from: process.env.NOREPLY_EMAIL,
id: "email",
maxAge: 15 * 60,
generateVerificationToken() {
return generateOtp();
},