diff --git a/next.config.js b/next.config.js index 0c426da..40dec91 100644 --- a/next.config.js +++ b/next.config.js @@ -24,4 +24,18 @@ module.exports = removeImports({ } ]; }, + env: { + KV_URL: process.env.NODE_ENV !== 'production' + ? process.env.REDIS_URL + : process.env.KV_URL, + KV_REST_API_URL: process.env.NODE_ENV !== 'production' + ? process.env.REDIS_URL + : process.env.KV_REST_API_URL, + KV_REST_API_TOKEN: process.env.NODE_ENV !== 'production' + ? 'dummy_token' + : process.env.KV_REST_API_TOKEN, + KV_REST_API_READ_ONLY_TOKEN: process.env.NODE_ENV !== 'production' + ? 'dummy_token' + : process.env.KV_REST_API_READ_ONLY_TOKEN, + }, }); \ No newline at end of file diff --git a/src/middleware.js b/src/middleware.js index 564a6a3..cf29440 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -2,13 +2,45 @@ import { NextResponse } from 'next/server'; import { Ratelimit } from '@upstash/ratelimit'; import { kv } from '@vercel/kv'; -const ratelimit = new Ratelimit({ - redis: kv, - // 5 requests from the same IP in 10 seconds - limiter: Ratelimit.slidingWindow(5, '10 s'), - analytics: true, - timeout: 1000, // 1 second -}); +// In-memory store for development +const inMemoryStore = new Map(); + +// Simple in-memory rate limiter for development +const localRatelimit = { + limit: async (key) => { + const now = Date.now(); + const windowMs = 10 * 1000; // 10 seconds + const maxRequests = 5; + + const requestLog = inMemoryStore.get(key) || []; + const windowStart = now - windowMs; + + const recentRequests = requestLog.filter(timestamp => timestamp > windowStart); + const isRateLimited = recentRequests.length >= maxRequests; + + if (!isRateLimited) { + recentRequests.push(now); + inMemoryStore.set(key, recentRequests); + } + + return { + success: !isRateLimited, + limit: maxRequests, + remaining: Math.max(0, maxRequests - recentRequests.length), + reset: windowStart + windowMs, + }; + }, +}; + +// Use local rate limiter for development, Upstash for production +const ratelimit = process.env.NODE_ENV === 'production' + ? new Ratelimit({ + redis: kv, + limiter: Ratelimit.slidingWindow(10, '10 s'), + analytics: true, + timeout: 1000, + }) + : localRatelimit; // Define which routes you want to rate limit export const config = { @@ -17,7 +49,7 @@ export const config = { export default async function middleware(request) { const ip = request.ip ?? '127.0.0.1'; - const { success, pending, limit, reset, remaining } = await ratelimit.limit( + const { success, limit, remaining, reset } = await ratelimit.limit( `ratelimit_middleware_${ip}` ); @@ -25,10 +57,15 @@ export default async function middleware(request) { return new NextResponse('Too Many Requests', { status: 429, headers: { - 'Retry-After': reset.toString(), + 'Retry-After': Math.ceil((reset - Date.now()) / 1000).toString(), }, }); } - return NextResponse.next(); + const response = NextResponse.next(); + response.headers.set('X-RateLimit-Limit', limit.toString()); + response.headers.set('X-RateLimit-Remaining', remaining.toString()); + response.headers.set('X-RateLimit-Reset', reset.toString()); + + return response; } \ No newline at end of file diff --git a/src/pages/api/image-proxy.js b/src/pages/api/image-proxy.js index 5e46b46..e41538e 100644 --- a/src/pages/api/image-proxy.js +++ b/src/pages/api/image-proxy.js @@ -1,11 +1,25 @@ import axios from 'axios'; +import { URL } from 'url'; export default async function handler(req, res) { const { imageUrl } = req.query; // Validate the imageUrl query parameter - if (!imageUrl) { - return res.status(400).json({ error: 'Missing imageUrl query parameter' }); + if (!imageUrl || typeof imageUrl !== 'string') { + return res.status(400).json({ error: 'Invalid or missing imageUrl query parameter' }); + } + + // Validate the URL + let parsedUrl; + try { + parsedUrl = new URL(imageUrl); + } catch (error) { + return res.status(400).json({ error: 'Invalid URL' }); + } + + // Only allow http and https protocols + if (!['http:', 'https:'].includes(parsedUrl.protocol)) { + return res.status(403).json({ error: 'Invalid protocol' }); } try { @@ -13,9 +27,9 @@ export default async function handler(req, res) { method: 'GET', url: imageUrl, responseType: 'arraybuffer', - timeout: 8000, // Set a timeout to prevent long-running requests - // limit the size of the response to 100MB - maxContentLength: 100 * 1024 * 1024, + timeout: 5000, // Reduced timeout to 5 seconds + maxContentLength: 10 * 1024 * 1024, // Reduced max content length to 10MB + // ... rest of the axios config }); // Validate content type @@ -34,6 +48,6 @@ export default async function handler(req, res) { res.send(response.data); } catch (error) { console.error('Image proxy error:', error); - res.status(500).json({ error: 'Failed to fetch image' }); + res.status(error.response?.status || 500).json({ error: 'Failed to fetch image' }); } } diff --git a/src/pages/api/users/index.js b/src/pages/api/users/index.js index a18d444..ec1ad94 100644 --- a/src/pages/api/users/index.js +++ b/src/pages/api/users/index.js @@ -3,20 +3,8 @@ import { getServerSession } from "next-auth/next" import { authOptions } from "@/pages/api/auth/[...nextauth].js" export default async function handler(req, res) { - if (req.method === 'GET') { - try { - const session = await getServerSession(req, res, authOptions) - - if (!session) { - return res.status(401).json({ error: 'Unauthorized' }); - } - - const users = await getAllUsers(); - res.status(200).json(users); - } catch (error) { - res.status(500).json({ error: error.message }); - } - } else if (req.method === 'POST') { + // const session = await getServerSession(req, res, authOptions); + if (req.method === 'POST') { try { const user = await createUser(req.body); res.status(201).json(user);