@@ -8,16 +8,32 @@ import type { NextRequest } from 'next/server'
88import { z } from 'zod'
99import { renderOTPEmail } from '@/components/emails'
1010import { getRedisClient } from '@/lib/core/config/redis'
11+ import type { TokenBucketConfig } from '@/lib/core/rate-limiter'
12+ import { RateLimiter } from '@/lib/core/rate-limiter'
1113import { addCorsHeaders , isEmailAllowed } from '@/lib/core/security/deployment'
1214import { getStorageMethod } from '@/lib/core/storage'
13- import { generateRequestId } from '@/lib/core/utils/request'
15+ import { generateRequestId , getClientIp } from '@/lib/core/utils/request'
1416import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
1517import { sendEmail } from '@/lib/messaging/email/mailer'
1618import { setChatAuthCookie } from '@/app/api/chat/utils'
1719import { createErrorResponse , createSuccessResponse } from '@/app/api/workflows/utils'
1820
1921const logger = createLogger ( 'ChatOtpAPI' )
2022
23+ const rateLimiter = new RateLimiter ( )
24+
25+ const OTP_IP_RATE_LIMIT : TokenBucketConfig = {
26+ maxTokens : 10 ,
27+ refillRate : 10 ,
28+ refillIntervalMs : 15 * 60_000 ,
29+ }
30+
31+ const OTP_EMAIL_RATE_LIMIT : TokenBucketConfig = {
32+ maxTokens : 3 ,
33+ refillRate : 3 ,
34+ refillIntervalMs : 15 * 60_000 ,
35+ }
36+
2137function generateOTP ( ) : string {
2238 return randomInt ( 100000 , 1000000 ) . toString ( )
2339}
@@ -214,6 +230,19 @@ export const POST = withRouteHandler(
214230 const requestId = generateRequestId ( )
215231
216232 try {
233+ const ip = getClientIp ( request )
234+ const ipRateLimit = await rateLimiter . checkRateLimitDirect (
235+ `chat-otp:ip:${ identifier } :${ ip } ` ,
236+ OTP_IP_RATE_LIMIT
237+ )
238+ if ( ! ipRateLimit . allowed ) {
239+ logger . warn ( `[${ requestId } ] OTP IP rate limit exceeded for ${ identifier } from ${ ip } ` )
240+ const retryAfter = Math . ceil ( ( ipRateLimit . retryAfterMs ?? 60_000 ) / 1000 )
241+ const response = createErrorResponse ( 'Too many requests. Please try again later.' , 429 )
242+ response . headers . set ( 'Retry-After' , String ( retryAfter ) )
243+ return addCorsHeaders ( response , request )
244+ }
245+
217246 const body = await request . json ( )
218247 const { email } = otpRequestSchema . parse ( body )
219248
@@ -255,6 +284,23 @@ export const POST = withRouteHandler(
255284 )
256285 }
257286
287+ const emailRateLimit = await rateLimiter . checkRateLimitDirect (
288+ `chat-otp:email:${ deployment . id } :${ email . toLowerCase ( ) } ` ,
289+ OTP_EMAIL_RATE_LIMIT
290+ )
291+ if ( ! emailRateLimit . allowed ) {
292+ logger . warn (
293+ `[${ requestId } ] OTP email rate limit exceeded for ${ email } on chat ${ deployment . id } `
294+ )
295+ const retryAfter = Math . ceil ( ( emailRateLimit . retryAfterMs ?? 60_000 ) / 1000 )
296+ const response = createErrorResponse (
297+ 'Too many verification code requests. Please try again later.' ,
298+ 429
299+ )
300+ response . headers . set ( 'Retry-After' , String ( retryAfter ) )
301+ return addCorsHeaders ( response , request )
302+ }
303+
258304 const otp = generateOTP ( )
259305 await storeOTP ( email , deployment . id , otp )
260306
0 commit comments