mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-06-06 18:31:00 +00:00
Remove getAllUsers endpoint, add checks to image proxy endpoint, setup local in memory cache for rate limitng in dev, added NODE_ENV variable
This commit is contained in:
parent
735ed2b7db
commit
7953bb641f
@ -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,
|
||||||
|
},
|
||||||
});
|
});
|
@ -2,13 +2,45 @@ import { NextResponse } from 'next/server';
|
|||||||
import { Ratelimit } from '@upstash/ratelimit';
|
import { Ratelimit } from '@upstash/ratelimit';
|
||||||
import { kv } from '@vercel/kv';
|
import { kv } from '@vercel/kv';
|
||||||
|
|
||||||
const ratelimit = new Ratelimit({
|
// In-memory store for development
|
||||||
redis: kv,
|
const inMemoryStore = new Map();
|
||||||
// 5 requests from the same IP in 10 seconds
|
|
||||||
limiter: Ratelimit.slidingWindow(5, '10 s'),
|
// Simple in-memory rate limiter for development
|
||||||
analytics: true,
|
const localRatelimit = {
|
||||||
timeout: 1000, // 1 second
|
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
|
// Define which routes you want to rate limit
|
||||||
export const config = {
|
export const config = {
|
||||||
@ -17,7 +49,7 @@ export const config = {
|
|||||||
|
|
||||||
export default async function middleware(request) {
|
export default async function middleware(request) {
|
||||||
const ip = request.ip ?? '127.0.0.1';
|
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}`
|
`ratelimit_middleware_${ip}`
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -25,10 +57,15 @@ export default async function middleware(request) {
|
|||||||
return new NextResponse('Too Many Requests', {
|
return new NextResponse('Too Many Requests', {
|
||||||
status: 429,
|
status: 429,
|
||||||
headers: {
|
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;
|
||||||
}
|
}
|
@ -1,11 +1,25 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import { URL } from 'url';
|
||||||
|
|
||||||
export default async function handler(req, res) {
|
export default async function handler(req, res) {
|
||||||
const { imageUrl } = req.query;
|
const { imageUrl } = req.query;
|
||||||
|
|
||||||
// Validate the imageUrl query parameter
|
// Validate the imageUrl query parameter
|
||||||
if (!imageUrl) {
|
if (!imageUrl || typeof imageUrl !== 'string') {
|
||||||
return res.status(400).json({ error: 'Missing imageUrl query parameter' });
|
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 {
|
try {
|
||||||
@ -13,9 +27,9 @@ export default async function handler(req, res) {
|
|||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: imageUrl,
|
url: imageUrl,
|
||||||
responseType: 'arraybuffer',
|
responseType: 'arraybuffer',
|
||||||
timeout: 8000, // Set a timeout to prevent long-running requests
|
timeout: 5000, // Reduced timeout to 5 seconds
|
||||||
// limit the size of the response to 100MB
|
maxContentLength: 10 * 1024 * 1024, // Reduced max content length to 10MB
|
||||||
maxContentLength: 100 * 1024 * 1024,
|
// ... rest of the axios config
|
||||||
});
|
});
|
||||||
|
|
||||||
// Validate content type
|
// Validate content type
|
||||||
@ -34,6 +48,6 @@ export default async function handler(req, res) {
|
|||||||
res.send(response.data);
|
res.send(response.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Image proxy error:', 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' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,20 +3,8 @@ import { getServerSession } from "next-auth/next"
|
|||||||
import { authOptions } from "@/pages/api/auth/[...nextauth].js"
|
import { authOptions } from "@/pages/api/auth/[...nextauth].js"
|
||||||
|
|
||||||
export default async function handler(req, res) {
|
export default async function handler(req, res) {
|
||||||
if (req.method === 'GET') {
|
// const session = await getServerSession(req, res, authOptions);
|
||||||
try {
|
if (req.method === 'POST') {
|
||||||
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') {
|
|
||||||
try {
|
try {
|
||||||
const user = await createUser(req.body);
|
const user = await createUser(req.body);
|
||||||
res.status(201).json(user);
|
res.status(201).json(user);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user