Upstash rate limiting seems trigger multiple times.
Unanswered
Red-tailed wasp posted this in #help-forum
Red-tailed waspOP
Hi heros, I'm using server actions to handle form submission and with upstash rate limiting library.
Somehow I'm able to passthrough the limit after awhile although I'm configured with
Can anyone explain why is it happen or my code? (For some reason I'm not placing the rate limiting in the middleware.)
My
Somehow I'm able to passthrough the limit after awhile although I'm configured with
limiter: Ratelimit.slidingWindow(1, "50s"),. So quickly went to upstash dashboard under "Data Browser" and I saw there's 3 rate limits created.Can anyone explain why is it happen or my code? (For some reason I'm not placing the rate limiting in the middleware.)
My
action.ts:"use server";
import { z } from "zod";
import { emailRegex } from "../../../utils/isValidEmail";
import { headers } from "next/headers";
import { getClient } from "@faustwp/experimental-app-router";
import { SEND_PASSWORD_RESET_EMAIL } from "@/src/gql/reset-password";
import { Redis } from "@upstash/redis";
import { Ratelimit } from "@upstash/ratelimit";
// Initialize Redis client
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(1, "50s"),
});
export async function resetPasswordAction(prevState: any, formData: FormData) {
const usernameEmail = formData.get("usernameEmail") as string;
// Define schema for Zod
const ResetPasswordDetails = z.object({
usernameEmail: z
.string()
.min(1, { message: "Email is required!" })
.regex(emailRegex, { message: "Email is invalid!" }) // Apply the updated regex with RFC5322 compiled
.transform((value) => value.toLowerCase()),
});
try {
// Run validation with Zod
const validated = ResetPasswordDetails.parse({ usernameEmail }); // Assign into a variable for accessing
// Get the IP from `request.ip` or use `x-forwarded-for` as a fallback
const headersList = headers();
const ip = headersList.get("ip") || headersList.get("x-forwarded-for") || "127.0.0.1"; // Fallback to localhost in dev
// try {
const rateLimitResult = await ratelimit.limit(ip);
const { success, remaining } = rateLimitResult;
if (!success) {
// console.log("Rate limit exceeded for IP:", ip);
return { success: false, error: "Your request exceeded the limit. Please try again later!" };
}
// If the request is not blocked, proceed with the Apollo Client mutation
const client = await getClient();
const { data, errors } = await client.mutate({
mutation: SEND_PASSWORD_RESET_EMAIL,
variables: { username: validated.usernameEmail },
});
// Handle GraphQL errors
if (errors && errors.length > 0) {
return { success: false, error: errors[0].message };
}
// If success
return {
success: true,
message: "If this email address was used to create an account, instructions to reset your password will be sent to you.",
};
} catch (error) {
// console.error(error);
// Zod validation errors
if (error instanceof z.ZodError) {
// console.error(error);
return {
success: false,
zodError: error.flatten().fieldErrors, // Structured field errors
};
}
return { success: false, error: error.message || "An unknown error occurred." };
}
}3 Replies
American Chinchilla
Im curiois to know too, happenes to me before but i read somewhere on stackoverflow thats its the servers
Red-tailed waspOP
@American Chinchilla , I got some information from upstash support. Here's the response.
"This may be because of how ratelimit chunks the time.
When you initialize the ratelimiter like Ratelimit.slidingWindow(1, "50s"), it doesn't necessarily mean that you will be able to call the endpoint 50 seconds after the first invocation.
Ratelimit will create fixed time chunks with 50 second size. If you make the first invocation in the 20th second of some interval, you will be able to call the endpoint 30 seconds later again, since the first interval will be over by then.
This means, over time, you will only be able to call the endpoint 1 time every 50 seconds, but time between each request won't be necessarily at least 50 seconds."
American Chinchilla
Thx