ship-safe 1.0.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Rate Limiting with Upstash Redis
3
+ * =================================
4
+ *
5
+ * Copy this file to your Next.js project.
6
+ *
7
+ * WHY RATE LIMITING MATTERS:
8
+ * - Prevents brute force attacks on auth endpoints
9
+ * - Protects against API abuse and scraping
10
+ * - Controls costs for AI/LLM endpoints
11
+ * - Required for production apps
12
+ *
13
+ * SETUP:
14
+ * 1. Create account at upstash.com
15
+ * 2. Create a Redis database
16
+ * 3. Copy UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN to .env
17
+ * 4. npm install @upstash/ratelimit @upstash/redis
18
+ *
19
+ * USAGE:
20
+ * import { ratelimit, checkRateLimit } from '@/lib/ratelimit';
21
+ *
22
+ * // In API route
23
+ * const { success, remaining } = await checkRateLimit(request);
24
+ * if (!success) {
25
+ * return new Response('Too many requests', { status: 429 });
26
+ * }
27
+ */
28
+
29
+ import { Ratelimit } from '@upstash/ratelimit';
30
+ import { Redis } from '@upstash/redis';
31
+
32
+ // =============================================================================
33
+ // REDIS CLIENT
34
+ // =============================================================================
35
+
36
+ const redis = new Redis({
37
+ url: process.env.UPSTASH_REDIS_REST_URL!,
38
+ token: process.env.UPSTASH_REDIS_REST_TOKEN!,
39
+ });
40
+
41
+ // =============================================================================
42
+ // RATE LIMITERS FOR DIFFERENT USE CASES
43
+ // =============================================================================
44
+
45
+ /**
46
+ * General API rate limiter
47
+ * 60 requests per minute per IP
48
+ */
49
+ export const ratelimit = new Ratelimit({
50
+ redis,
51
+ limiter: Ratelimit.slidingWindow(60, '1 m'),
52
+ analytics: true,
53
+ prefix: 'ratelimit:api',
54
+ });
55
+
56
+ /**
57
+ * Auth endpoints rate limiter (stricter)
58
+ * 5 requests per minute per IP
59
+ * Prevents brute force attacks
60
+ */
61
+ export const authRatelimit = new Ratelimit({
62
+ redis,
63
+ limiter: Ratelimit.slidingWindow(5, '1 m'),
64
+ analytics: true,
65
+ prefix: 'ratelimit:auth',
66
+ });
67
+
68
+ /**
69
+ * AI/LLM endpoints rate limiter
70
+ * 10 requests per minute per user
71
+ * Prevents cost explosion
72
+ */
73
+ export const aiRatelimit = new Ratelimit({
74
+ redis,
75
+ limiter: Ratelimit.slidingWindow(10, '1 m'),
76
+ analytics: true,
77
+ prefix: 'ratelimit:ai',
78
+ });
79
+
80
+ /**
81
+ * Expensive operations rate limiter
82
+ * 100 requests per day per user
83
+ * For operations like exports, reports
84
+ */
85
+ export const expensiveRatelimit = new Ratelimit({
86
+ redis,
87
+ limiter: Ratelimit.slidingWindow(100, '1 d'),
88
+ analytics: true,
89
+ prefix: 'ratelimit:expensive',
90
+ });
91
+
92
+ // =============================================================================
93
+ // HELPER FUNCTIONS
94
+ // =============================================================================
95
+
96
+ /**
97
+ * Get identifier for rate limiting
98
+ * Uses user ID if authenticated, otherwise IP address
99
+ */
100
+ export function getIdentifier(request: Request, userId?: string): string {
101
+ if (userId) {
102
+ return `user:${userId}`;
103
+ }
104
+
105
+ // Get IP from headers (works with Vercel, Cloudflare, etc.)
106
+ const forwarded = request.headers.get('x-forwarded-for');
107
+ const ip = forwarded ? forwarded.split(',')[0].trim() : 'unknown';
108
+
109
+ return `ip:${ip}`;
110
+ }
111
+
112
+ /**
113
+ * Check rate limit and return result
114
+ * Returns success, remaining requests, and reset time
115
+ */
116
+ export async function checkRateLimit(
117
+ request: Request,
118
+ userId?: string,
119
+ limiter: Ratelimit = ratelimit
120
+ ): Promise<{
121
+ success: boolean;
122
+ remaining: number;
123
+ reset: number;
124
+ limit: number;
125
+ }> {
126
+ const identifier = getIdentifier(request, userId);
127
+
128
+ const { success, remaining, reset, limit } = await limiter.limit(identifier);
129
+
130
+ return { success, remaining, reset, limit };
131
+ }
132
+
133
+ /**
134
+ * Rate limit middleware for Next.js API routes
135
+ * Returns Response if rate limited, undefined if OK
136
+ */
137
+ export async function rateLimitMiddleware(
138
+ request: Request,
139
+ userId?: string,
140
+ limiter: Ratelimit = ratelimit
141
+ ): Promise<Response | undefined> {
142
+ const { success, remaining, reset, limit } = await checkRateLimit(
143
+ request,
144
+ userId,
145
+ limiter
146
+ );
147
+
148
+ if (!success) {
149
+ return new Response(
150
+ JSON.stringify({
151
+ error: 'Too many requests',
152
+ message: 'Please try again later',
153
+ retryAfter: Math.ceil((reset - Date.now()) / 1000),
154
+ }),
155
+ {
156
+ status: 429,
157
+ headers: {
158
+ 'Content-Type': 'application/json',
159
+ 'X-RateLimit-Limit': limit.toString(),
160
+ 'X-RateLimit-Remaining': remaining.toString(),
161
+ 'X-RateLimit-Reset': reset.toString(),
162
+ 'Retry-After': Math.ceil((reset - Date.now()) / 1000).toString(),
163
+ },
164
+ }
165
+ );
166
+ }
167
+
168
+ return undefined;
169
+ }
170
+
171
+ // =============================================================================
172
+ // USAGE EXAMPLES
173
+ // =============================================================================
174
+
175
+ /**
176
+ * Example: API route with rate limiting
177
+ *
178
+ * // app/api/data/route.ts
179
+ * import { rateLimitMiddleware } from '@/lib/ratelimit';
180
+ *
181
+ * export async function GET(request: Request) {
182
+ * // Check rate limit
183
+ * const rateLimitResponse = await rateLimitMiddleware(request);
184
+ * if (rateLimitResponse) return rateLimitResponse;
185
+ *
186
+ * // Your API logic here
187
+ * return Response.json({ data: 'Hello!' });
188
+ * }
189
+ */
190
+
191
+ /**
192
+ * Example: Auth route with stricter limits
193
+ *
194
+ * // app/api/auth/login/route.ts
195
+ * import { rateLimitMiddleware, authRatelimit } from '@/lib/ratelimit';
196
+ *
197
+ * export async function POST(request: Request) {
198
+ * // Stricter rate limit for auth
199
+ * const rateLimitResponse = await rateLimitMiddleware(request, undefined, authRatelimit);
200
+ * if (rateLimitResponse) return rateLimitResponse;
201
+ *
202
+ * // Login logic here
203
+ * }
204
+ */
205
+
206
+ /**
207
+ * Example: AI endpoint with user-based limits
208
+ *
209
+ * // app/api/ai/generate/route.ts
210
+ * import { rateLimitMiddleware, aiRatelimit } from '@/lib/ratelimit';
211
+ * import { auth } from '@/lib/auth';
212
+ *
213
+ * export async function POST(request: Request) {
214
+ * const session = await auth();
215
+ * if (!session?.user) {
216
+ * return new Response('Unauthorized', { status: 401 });
217
+ * }
218
+ *
219
+ * // Rate limit by user ID for AI endpoints
220
+ * const rateLimitResponse = await rateLimitMiddleware(
221
+ * request,
222
+ * session.user.id,
223
+ * aiRatelimit
224
+ * );
225
+ * if (rateLimitResponse) return rateLimitResponse;
226
+ *
227
+ * // AI logic here
228
+ * }
229
+ */