diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/customers/[customerId]/page-client.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/customers/[customerId]/page-client.tsx index c32bef2d761..253aa46ecbb 100644 --- a/apps/web/app/app.dub.co/(dashboard)/[slug]/customers/[customerId]/page-client.tsx +++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/customers/[customerId]/page-client.tsx @@ -25,11 +25,7 @@ export function CustomerPageClient() { const { customerId } = useParams<{ customerId: string }>(); const { id: workspaceId, slug } = useWorkspace(); - const { - data: customer, - isLoading, - error, - } = useCustomer({ + const { data: customer, isLoading } = useCustomer({ customerId, query: { includeExpandedFields: true }, }); @@ -41,7 +37,7 @@ export function CustomerPageClient() { fetcher, ); - if (!customer && !isLoading && !error) notFound(); + if (!customer && !isLoading) notFound(); return (
diff --git a/apps/web/lib/actions/create-user-account.ts b/apps/web/lib/actions/create-user-account.ts index 8493f82c4b5..38c1080aa97 100644 --- a/apps/web/lib/actions/create-user-account.ts +++ b/apps/web/lib/actions/create-user-account.ts @@ -2,9 +2,9 @@ import { ratelimit } from "@/lib/upstash"; import { prisma } from "@dub/prisma"; +import { waitUntil } from "@vercel/functions"; import { flattenValidationErrors } from "next-safe-action"; import { createId } from "../api/create-id"; -import { getIP } from "../api/utils"; import { hashPassword } from "../auth/password"; import z from "../zod"; import { signUpSchema } from "../zod/schemas/auth"; @@ -15,6 +15,9 @@ const schema = signUpSchema.extend({ code: z.string().min(6, "OTP must be 6 characters long."), }); +const MAX_OTP_ATTEMPTS = 5; // Block after 5 failed attempts +const OTP_LOCKOUT_DURATION = "24 h"; // Block for 24 hours + // Sign up a new user using email and password export const createUserAccountAction = actionClient .schema(schema, { @@ -25,26 +28,45 @@ export const createUserAccountAction = actionClient .action(async ({ parsedInput }) => { const { email, password, code } = parsedInput; - const { success } = await ratelimit(2, "1 m").limit(`signup:${getIP()}`); + const signupAttemptKey = `signup:attempts:${email}`; + + const { remaining: attemptsRemaining } = await ratelimit( + MAX_OTP_ATTEMPTS, + OTP_LOCKOUT_DURATION, + ).getRemaining(signupAttemptKey); - if (!success) { - throw new Error("Too many requests. Please try again later."); + if (attemptsRemaining <= 0) { + throw new Error("Too many failed attempts. You have to try again later."); } const verificationToken = await prisma.emailVerificationToken.findUnique({ where: { identifier: email, token: code, - expires: { - gte: new Date(), - }, }, }); if (!verificationToken) { + await ratelimit(MAX_OTP_ATTEMPTS, OTP_LOCKOUT_DURATION).limit( + signupAttemptKey, + ); + throw new Error("Invalid verification code entered."); } + if (verificationToken.expires && verificationToken.expires < new Date()) { + waitUntil( + prisma.emailVerificationToken.delete({ + where: { + identifier: email, + token: code, + }, + }), + ); + + throw new Error("The OTP has expired. Please request a new one."); + } + await prisma.emailVerificationToken.delete({ where: { identifier: email, diff --git a/apps/web/lib/zod/schemas/leads.ts b/apps/web/lib/zod/schemas/leads.ts index 0f38f699b37..bd0bfa86ed9 100644 --- a/apps/web/lib/zod/schemas/leads.ts +++ b/apps/web/lib/zod/schemas/leads.ts @@ -65,7 +65,12 @@ export const trackLeadRequestSchema = z.object({ .record(z.unknown()) .nullish() .default(null) - .describe("Additional metadata to be stored with the lead event"), + .refine((val) => !val || JSON.stringify(val).length <= 10000, { + message: "Metadata must be less than 10,000 characters when stringified", + }) + .describe( + "Additional metadata to be stored with the lead event. Max 10,000 characters.", + ), mode: z .enum(["async", "wait"]) .default("async") diff --git a/apps/web/lib/zod/schemas/sales.ts b/apps/web/lib/zod/schemas/sales.ts index 49e34962d3c..74af54369ce 100644 --- a/apps/web/lib/zod/schemas/sales.ts +++ b/apps/web/lib/zod/schemas/sales.ts @@ -56,7 +56,12 @@ export const trackSaleRequestSchema = z.object({ .record(z.unknown()) .nullish() .default(null) - .describe("Additional metadata to be stored with the sale event."), + .refine((val) => !val || JSON.stringify(val).length <= 10000, { + message: "Metadata must be less than 10,000 characters when stringified", + }) + .describe( + "Additional metadata to be stored with the sale event. Max 10,000 characters.", + ), leadEventName: z .string() .nullish()