diff --git a/RATE_LIMITING_CORS.md b/RATE_LIMITING_CORS.md new file mode 100644 index 0000000..d01b303 --- /dev/null +++ b/RATE_LIMITING_CORS.md @@ -0,0 +1,142 @@ +# Rate Limiting and CORS Configuration + +## Overview +This implementation provides comprehensive rate limiting and CORS configuration for the API Gateway to prevent brute-force attacks and ensure proper cross-origin resource sharing. + +## Features Implemented + +### 1. Rate Limiting +- **General API**: 50 requests/minute (production), 100 requests/minute (development) +- **Authentication**: 5 attempts per 15 minutes +- **Strict Operations**: 10 requests per minute +- **Progressive Slowdown**: Delays after 20 requests in 15 minutes + +### 2. CORS Configuration +- **Preflight Support**: Handles OPTIONS requests correctly +- **Origin Validation**: Allows specific frontend domains +- **Credentials**: Supports cookies and authorization headers +- **Development Mode**: Allows localhost origins in development + +### 3. Security Headers +- **Helmet**: Comprehensive security headers +- **CSP**: Content Security Policy +- **HSTS**: HTTP Strict Transport Security +- **XSS Protection**: Cross-site scripting protection + +## API Endpoints + +### Rate Limiting Test +```bash +# Test rate limiting (limited to 10 requests/minute) +GET /api/test-security/rate-limit +``` + +### CORS Test +```bash +# Test CORS configuration +GET /api/test-security/cors + +# Test CORS preflight +OPTIONS /api/test-security/cors +``` + +### Security Headers Test +```bash +# Test security headers +GET /api/test-security/security-headers +``` + +## Configuration + +### Environment Variables +```bash +NODE_ENV=production # Enables stricter rate limits +JWT_SECRET=your-secret-key +``` + +### Rate Limits +- **General**: 50/min (prod), 100/min (dev) +- **Auth**: 5 attempts per 15 minutes +- **Strict**: 10 requests per minute +- **Speed Limit**: 20 requests, then 500ms delay + +### CORS Origins +- `http://localhost:3000` (React) +- `http://localhost:5173` (Vite) +- `http://localhost:8080` (Vue) +- Production domains (to be added) + +## Testing + +### 1. Rate Limiting Test +```bash +# Make multiple requests to test rate limiting +for i in {1..15}; do + curl -w "%{http_code}\n" http://localhost:5000/api/test-security/rate-limit +done +``` + +### 2. CORS Test +```bash +# Test from browser console +fetch('http://localhost:5000/api/test-security/cors', { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, +}) +.then(response => response.json()) +.then(data => console.log(data)); +``` + +### 3. Preflight Test +```bash +# Test CORS preflight +curl -X OPTIONS \ + -H "Origin: http://localhost:3000" \ + -H "Access-Control-Request-Method: POST" \ + -H "Access-Control-Request-Headers: Content-Type" \ + http://localhost:5000/api/test-security/cors +``` + +## Error Responses + +### Rate Limit Exceeded +```json +{ + "success": false, + "message": "Too many requests from this IP, please try again later.", + "retryAfter": "1 minute" +} +``` + +### CORS Error +```json +{ + "success": false, + "message": "CORS policy violation: Origin not allowed", + "origin": "http://disallowed-origin.com" +} +``` + +## Security Features + +1. **Brute Force Protection**: Stricter limits on auth endpoints +2. **Progressive Delays**: Slows down repeated requests +3. **IP-based Limiting**: Tracks requests per IP address +4. **Origin Validation**: Only allows trusted domains +5. **Security Headers**: Comprehensive security headers via Helmet + +## Production Considerations + +1. **Redis Store**: Consider using Redis for distributed rate limiting +2. **Whitelist**: Add trusted IPs for higher limits +3. **Monitoring**: Implement rate limit monitoring +4. **Custom Messages**: Customize error messages per endpoint +5. **Bypass Options**: Add bypass mechanisms for admin users + +## Files Created +- `src/middleware/rateLimiter.middleware.js` - Rate limiting configuration +- `src/middleware/cors.middleware.js` - CORS configuration +- `src/config/app.config.js` - Environment configuration +- `src/routes/test-security.routes.js` - Test endpoints diff --git a/src/app.js b/src/app.js index c860f08..4cc149c 100644 --- a/src/app.js +++ b/src/app.js @@ -1,21 +1,31 @@ import express from 'express'; -import cors from 'cors'; import passport from './config/passport.config.js'; import productRoutes from './routes/product.routes.js'; import cartRoutes from './routes/cart.routes.js'; import collectionRoutes from './routes/collection.routes.js'; import wishlistRoutes from './routes/wishlist.routes.js'; import testRoutes from './routes/test.routes.js'; +import testSecurityRoutes from './routes/test-security.routes.js'; import authRoutes from './routes/auth.routes.js'; import errorHandler from './middleware/error-handler.middleware.js'; -import notFound from './middleware/notFound.middleware.js' -import cookieParser from 'cookie-parser'; - +import notFound from './middleware/notFound.middleware.js'; +import { corsMiddleware, securityHeaders, corsErrorHandler } from './middleware/cors.middleware.js'; +import { generalRateLimit, authRateLimit, speedLimiter, strictRateLimit } from './middleware/rateLimiter.middleware.js'; const app = express(); -app.use(cors()); -app.use(express.json()); -app.use(cookieParser()); +// Security middleware (must be first) +app.use(securityHeaders); + +// CORS middleware +app.use(corsMiddleware); + +// Body parsing middleware +app.use(express.json({ limit: '10mb' })); +app.use(express.urlencoded({ extended: true, limit: '10mb' })); + +// Rate limiting middleware +app.use(generalRateLimit); +app.use(speedLimiter); // Initialize Passport app.use(passport.initialize()); @@ -24,16 +34,23 @@ app.get('/',(req,res)=>{ res.send("Welcome to Homepage"); }) -// Routes +// Routes with specific rate limiting app.use('/api/products', productRoutes); app.use('/api/cart', cartRoutes); app.use('/api/collections', collectionRoutes); app.use('/api/wishlist', wishlistRoutes); app.use('/api/test', testRoutes); -app.use('/auth', authRoutes); +app.use('/api/test-security', testSecurityRoutes); + +// Auth routes with stricter rate limiting +app.use('/auth', authRateLimit, authRoutes); + +// CORS error handler +app.use(corsErrorHandler); // Middleware for not found 404 app.use(notFound); + // Global error handler (should be last) app.use(errorHandler); diff --git a/src/config/app.config.js b/src/config/app.config.js new file mode 100644 index 0000000..b321f45 --- /dev/null +++ b/src/config/app.config.js @@ -0,0 +1,91 @@ +/** + * Environment configuration for rate limiting and CORS + */ + +export const config = { + // Rate limiting configuration + rateLimit: { + // General API rate limits + general: { + windowMs: 60 * 1000, // 1 minute + max: process.env.NODE_ENV === 'production' ? 50 : 100, // 50 in prod, 100 in dev + }, + // Authentication rate limits (stricter) + auth: { + windowMs: 15 * 60 * 1000, // 15 minutes + max: 5, // 5 attempts per 15 minutes + }, + // Strict rate limits for sensitive operations + strict: { + windowMs: 60 * 1000, // 1 minute + max: 10, // 10 requests per minute + }, + // Speed limiter configuration + speedLimit: { + windowMs: 15 * 60 * 1000, // 15 minutes + delayAfter: 20, // Allow 20 requests per 15 minutes at full speed + delayMs: 500, // Add 500ms delay per request after delayAfter + maxDelayMs: 20000, // Maximum delay of 20 seconds + }, + }, + + // CORS configuration + cors: { + // Allowed origins + allowedOrigins: [ + 'http://localhost:3000', // React dev server + 'http://localhost:3001', // Alternative React port + 'http://localhost:5173', // Vite dev server + 'http://localhost:8080', // Vue dev server + 'http://127.0.0.1:3000', // Localhost alternative + 'http://127.0.0.1:5173', // Vite localhost alternative + // Add production domains here + // 'https://yourdomain.com', + // 'https://www.yourdomain.com', + ], + // Additional CORS settings + credentials: true, + methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'], + allowedHeaders: [ + 'Origin', + 'X-Requested-With', + 'Content-Type', + 'Accept', + 'Authorization', + 'Cache-Control', + 'Pragma', + ], + exposedHeaders: [ + 'X-Total-Count', + 'X-Page-Count', + 'X-Current-Page', + ], + }, + + // Security configuration + security: { + // Content Security Policy + csp: { + directives: { + defaultSrc: ["'self'"], + styleSrc: ["'self'", "'unsafe-inline'"], + scriptSrc: ["'self'"], + imgSrc: ["'self'", "data:", "https:"], + }, + }, + // HSTS configuration + hsts: { + maxAge: 31536000, // 1 year + includeSubDomains: true, + preload: true, + }, + }, + + // Request size limits + requestLimits: { + json: '10mb', + urlencoded: '10mb', + }, +}; + +export default config; diff --git a/src/middleware/cors.middleware.js b/src/middleware/cors.middleware.js new file mode 100644 index 0000000..29cee06 --- /dev/null +++ b/src/middleware/cors.middleware.js @@ -0,0 +1,99 @@ +import cors from 'cors'; +import helmet from 'helmet'; + +/** + * CORS configuration for frontend integration + * Handles preflight requests and allows specific origins + */ + +// Allowed origins for CORS +const allowedOrigins = [ + 'http://localhost:3000', // React dev server + 'http://localhost:3001', // Alternative React port + 'http://localhost:5173', // Vite dev server + 'http://localhost:8080', // Vue dev server + 'http://127.0.0.1:3000', // Localhost alternative + 'http://127.0.0.1:5173', // Vite localhost alternative + // Add production domains here + // 'https://yourdomain.com', + // 'https://www.yourdomain.com', +]; + +// CORS options +const corsOptions = { + origin: (origin, callback) => { + // Allow requests with no origin (mobile apps, Postman, etc.) + if (!origin) return callback(null, true); + + // Check if origin is in allowed list + if (allowedOrigins.includes(origin)) { + callback(null, true); + } else { + // In development, allow any localhost origin + if (process.env.NODE_ENV !== 'production' && origin.includes('localhost')) { + callback(null, true); + } else { + callback(new Error('Not allowed by CORS')); + } + } + }, + credentials: true, // Allow cookies and authorization headers + methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'], + allowedHeaders: [ + 'Origin', + 'X-Requested-With', + 'Content-Type', + 'Accept', + 'Authorization', + 'Cache-Control', + 'Pragma', + ], + exposedHeaders: [ + 'X-Total-Count', + 'X-Page-Count', + 'X-Current-Page', + ], + optionsSuccessStatus: 200, // Some legacy browsers choke on 204 + preflightContinue: false, // Pass the CORS preflight response to the next handler +}; + +// Security headers configuration +export const securityHeaders = helmet({ + contentSecurityPolicy: { + directives: { + defaultSrc: ["'self'"], + styleSrc: ["'self'", "'unsafe-inline'"], + scriptSrc: ["'self'"], + imgSrc: ["'self'", "data:", "https:"], + }, + }, + crossOriginEmbedderPolicy: false, // Disable for API compatibility + hsts: { + maxAge: 31536000, + includeSubDomains: true, + preload: true, + }, +}); + +// CORS middleware +export const corsMiddleware = cors(corsOptions); + +// CORS error handler +export const corsErrorHandler = (err, req, res, next) => { + if (err.message === 'Not allowed by CORS') { + res.status(403).json({ + success: false, + message: 'CORS policy violation: Origin not allowed', + origin: req.headers.origin, + }); + } else { + next(err); + } +}; + +export default { + corsMiddleware, + securityHeaders, + corsErrorHandler, + corsOptions, +}; diff --git a/src/middleware/rateLimiter.middleware.js b/src/middleware/rateLimiter.middleware.js new file mode 100644 index 0000000..0aa6cfc --- /dev/null +++ b/src/middleware/rateLimiter.middleware.js @@ -0,0 +1,77 @@ +import rateLimit from 'express-rate-limit'; +import slowDown from 'express-slow-down'; + +/** + * Rate limiting configuration + * - 50 requests per minute per IP + * - Burst up to 100 requests for development + * - Progressive delays for repeated requests + */ + +// General API rate limiter +export const generalRateLimit = rateLimit({ + windowMs: 60 * 1000, // 1 minute + max: process.env.NODE_ENV === 'production' ? 50 : 100, // 50 in prod, 100 in dev + message: { + success: false, + message: 'Too many requests from this IP, please try again later.', + retryAfter: '1 minute', + }, + standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers + legacyHeaders: false, // Disable the `X-RateLimit-*` headers + handler: (req, res) => { + res.status(429).json({ + success: false, + message: 'Too many requests from this IP, please try again later.', + retryAfter: '1 minute', + }); + }, +}); + +// Stricter rate limiter for auth endpoints +export const authRateLimit = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 5, // 5 attempts per 15 minutes + message: { + success: false, + message: 'Too many authentication attempts, please try again later.', + retryAfter: '15 minutes', + }, + standardHeaders: true, + legacyHeaders: false, + handler: (req, res) => { + res.status(429).json({ + success: false, + message: 'Too many authentication attempts, please try again later.', + retryAfter: '15 minutes', + }); + }, +}); + +// Progressive slowdown for repeated requests +export const speedLimiter = slowDown({ + windowMs: 15 * 60 * 1000, // 15 minutes + delayAfter: 20, // Allow 20 requests per 15 minutes at full speed + delayMs: 500, // Add 500ms delay per request after delayAfter + maxDelayMs: 20000, // Maximum delay of 20 seconds +}); + +// Very strict rate limiter for sensitive operations +export const strictRateLimit = rateLimit({ + windowMs: 60 * 1000, // 1 minute + max: 10, // 10 requests per minute + message: { + success: false, + message: 'Rate limit exceeded for this operation.', + retryAfter: '1 minute', + }, + standardHeaders: true, + legacyHeaders: false, +}); + +export default { + generalRateLimit, + authRateLimit, + speedLimiter, + strictRateLimit, +}; diff --git a/src/routes/test-security.routes.js b/src/routes/test-security.routes.js new file mode 100644 index 0000000..31a5526 --- /dev/null +++ b/src/routes/test-security.routes.js @@ -0,0 +1,64 @@ +import express from 'express'; +import { strictRateLimit } from '../middleware/rateLimiter.middleware.js'; + +const router = express.Router(); + +/** + * GET /api/test/rate-limit + * Test endpoint to verify rate limiting is working + * Uses strict rate limiting (10 requests per minute) + */ +router.get('/rate-limit', strictRateLimit, (req, res) => { + res.status(200).json({ + success: true, + message: 'Rate limit test endpoint - this should be limited to 10 requests per minute', + timestamp: new Date().toISOString(), + ip: req.ip, + userAgent: req.get('User-Agent'), + }); +}); + +/** + * GET /api/test/cors + * Test endpoint to verify CORS is working + */ +router.get('/cors', (req, res) => { + res.status(200).json({ + success: true, + message: 'CORS test endpoint', + origin: req.headers.origin, + method: req.method, + headers: { + 'Access-Control-Allow-Origin': res.get('Access-Control-Allow-Origin'), + 'Access-Control-Allow-Methods': res.get('Access-Control-Allow-Methods'), + 'Access-Control-Allow-Headers': res.get('Access-Control-Allow-Headers'), + }, + }); +}); + +/** + * OPTIONS /api/test/cors + * Test CORS preflight request + */ +router.options('/cors', (req, res) => { + res.status(200).end(); +}); + +/** + * GET /api/test/security-headers + * Test endpoint to verify security headers + */ +router.get('/security-headers', (req, res) => { + res.status(200).json({ + success: true, + message: 'Security headers test endpoint', + headers: { + 'X-Content-Type-Options': res.get('X-Content-Type-Options'), + 'X-Frame-Options': res.get('X-Frame-Options'), + 'X-XSS-Protection': res.get('X-XSS-Protection'), + 'Strict-Transport-Security': res.get('Strict-Transport-Security'), + }, + }); +}); + +export default router;