1
- import { NextResponse , NextRequest } from 'next/server'
1
+ import { NextRequest , NextResponse } from "next/server"
2
+ import { Duration , Ratelimit } from "@upstash/ratelimit"
3
+
4
+ import { Redis } from "@upstash/redis"
2
5
import { cookies } from 'next/headers'
3
6
import { v4 as uuidv4 } from 'uuid'
4
7
5
8
const rateLimitMap = new Map < string , number [ ] > ( )
6
9
10
+ let redis : Redis
11
+ let sessionRatelimit : Ratelimit
12
+ let ipRatelimit : Ratelimit
13
+ let currentConfig : any = { }
14
+
15
+ function initialize ( { sessionLimit, sessionWindow, ipLimit, ipWindow, upstash } : Partial < RateLimitOptions > ) {
16
+ const { url, token, analytics } = upstash !
17
+
18
+ const config = {
19
+ sessionLimit,
20
+ sessionWindow,
21
+ ipLimit,
22
+ ipWindow,
23
+ upstash
24
+ }
25
+
26
+ if ( ! redis || JSON . stringify ( config ) !== JSON . stringify ( currentConfig ) ) {
27
+ currentConfig = config
28
+
29
+ redis = new Redis ( {
30
+ url : url ,
31
+ token : token ,
32
+ } )
33
+
34
+ sessionRatelimit = new Ratelimit ( {
35
+ redis,
36
+ limiter : Ratelimit . slidingWindow ( sessionLimit ! , `${ sessionWindow } s` as Duration ) ,
37
+ analytics
38
+ } )
39
+
40
+ ipRatelimit = new Ratelimit ( {
41
+ redis,
42
+ limiter : Ratelimit . slidingWindow ( ipLimit ! , `${ ipWindow } s` as Duration ) ,
43
+ analytics
44
+ } )
45
+ }
46
+ }
47
+
48
+ interface RateLimitOptions {
49
+ request : NextRequest
50
+ response : NextResponse
51
+ sessionLimit ?: number
52
+ ipLimit ?: number
53
+ sessionWindow ?: number
54
+ ipWindow ?: number
55
+ upstash ?: {
56
+ enabled ?: boolean
57
+ url ?: string
58
+ token ?: string
59
+ analytics ?: boolean
60
+ }
61
+ windowMs ?: number
62
+ }
7
63
8
64
/**
9
65
* This middleware is used to rate limit requests based
10
66
* on IP address and session ID. Returns NextResponse with
11
67
* 429 status code if the rate limit is exceeded.
12
- * @param {object } options - Rate limiting options.
13
- * @param {NextRequest } options.request - Incoming request object.
68
+ * @param {RateLimitOptions } options - Rate limiting options.
69
+ * @param {NextRequest } options.request - NextRequest object.
14
70
* @param {NextResponse } options.response - NextResponse object.
15
- * @param {number } [options.sessionLimit=30] - Number of requests allowed per session in the window.
16
- * @param {number } [options.ipLimit=300] - Number of requests allowed per IP in the window.
17
- * @param {number } [options.windowMs=15000] - Time window in milliseconds.
71
+ * @param {number } [options.sessionLimit=20] - Number of requests allowed per session in the window.
72
+ * @param {number } [options.ipLimit=100] - Number of requests allowed per IP in the window.
73
+ * @param {number } [options.sessionWindow=10] - Window in seconds for session rate limiting.
74
+ * @param {number } [options.ipWindow=10] - Window in seconds for IP rate limiting.
75
+ * @param {object } [options.upstash] - Upstash Redis configuration.
76
+ * @param {boolean } [options.upstash.enabled] - Enable Upstash Redis rate limiting.
77
+ * @param {string } [options.upstash.url] - Upstash Redis REST URL.
78
+ * @param {string } [options.upstash.token] - Upstash Redis REST token.
79
+ * @param {boolean } [options.upstash.analytics] - Enable analytics for rate limiting.
18
80
*/
19
81
export async function rateLimit ( {
20
82
request,
21
83
response,
22
- sessionLimit = 30 ,
23
- ipLimit = 300 ,
24
- windowMs = 15 * 1000
25
- } : { request : NextRequest ; response : NextResponse ; sessionLimit ?: number ; ipLimit ?: number ; windowMs ?: number } ) {
26
- const ip = request . headers . get ( "x-forwarded-for" ) || request . headers . get ( "x-real-ip" )
84
+ sessionLimit = 20 ,
85
+ ipLimit = 100 ,
86
+ sessionWindow = 10 ,
87
+ ipWindow = 10 ,
88
+ upstash = { } ,
89
+ windowMs,
90
+ } : RateLimitOptions ) {
91
+ const errorResponse = NextResponse . json ( { message : "Too Many Requests" } , { status : 429 } )
92
+
93
+ const ip = request . headers . get ( 'x-forwarded-for' ) ?. split ( ',' ) [ 0 ] || '127.0.0.1'
27
94
let sessionId = ( await cookies ( ) ) . get ( 'session_id' ) ?. value
28
95
29
96
if ( ! sessionId ) {
30
97
sessionId = uuidv4 ( )
31
98
response . cookies . set ( "session_id" , sessionId )
32
99
}
33
100
34
- // Check IP rate limit
35
- if ( ip && handleRateLimiting ( ip , ipLimit , windowMs ) ) {
36
- const response = { message : "Too Many Requests" }
37
- console . warn ( `Rate limit exceeded for IP: ${ ip } ` )
38
- return new NextResponse ( JSON . stringify ( response ) , {
39
- status : 429 ,
40
- headers : {
41
- "Content-Type" : "application/json"
101
+ upstash . url = upstash . url || process . env . UPSTASH_REDIS_REST_URL
102
+ upstash . token = upstash . token || process . env . UPSTASH_REDIS_REST_TOKEN
103
+
104
+ // Merge defaults with provided config
105
+ if ( upstash . enabled && upstash . url && upstash . token ) {
106
+ initialize ( {
107
+ sessionLimit,
108
+ sessionWindow,
109
+ ipLimit,
110
+ ipWindow,
111
+ upstash : {
112
+ url : upstash . url ,
113
+ token : upstash . token ,
42
114
}
43
115
} )
116
+
117
+ const { success, pending, limit, reset, remaining } =
118
+ await sessionRatelimit . limit ( sessionId )
119
+
120
+ if ( ! success ) {
121
+ console . warn ( `Rate limit exceeded for session ID: ${ sessionId } ` )
122
+ return errorResponse
123
+ }
124
+
125
+ const { success : ipSuccess } = await ipRatelimit . limit ( ip )
126
+
127
+ if ( ! ipSuccess ) {
128
+ console . warn ( `Rate limit exceeded for IP: ${ ip } ` )
129
+ return errorResponse
130
+ }
131
+
132
+ return response
133
+ }
134
+
135
+ // Check IP rate limit
136
+ if ( ip && handleRateLimiting ( ip , ipLimit , windowMs || ipWindow * 1000 ) ) {
137
+ console . warn ( `Rate limit exceeded for IP: ${ ip } ` )
138
+ return errorResponse
44
139
}
45
140
46
141
// Check session rate limit
47
- if ( handleRateLimiting ( sessionId , sessionLimit , windowMs ) ) {
48
- const response = { message : "Too Many Requests" }
142
+ if ( handleRateLimiting ( sessionId , sessionLimit , windowMs || sessionWindow * 1000 ) ) {
49
143
console . warn ( `Rate limit exceeded for session ID: ${ sessionId } ` )
50
- return new NextResponse ( JSON . stringify ( response ) , {
51
- status : 429 ,
52
- headers : {
53
- "Content-Type" : "application/json"
54
- }
55
- } )
144
+ return errorResponse
56
145
}
57
146
58
- return null
147
+ return response
59
148
}
60
149
61
150
export function handleRateLimiting ( key : string , limit : number , windowMs : number ) {
@@ -81,4 +170,4 @@ export function handleRateLimiting(key: string, limit: number, windowMs: number)
81
170
// Add the current timestamp to the list
82
171
validTimestamps . push ( currentTime )
83
172
return false
84
- }
173
+ }
0 commit comments