security: implement CRITICAL and MEDIUM security fixes with environment profiles
This commit addresses all CRITICAL and MEDIUM security vulnerabilities
identified in the security audit with environment-aware configuration.
## Docker Compose Profiles
- Added docker-compose.dev.yml for development (relaxed security)
- Added docker-compose.prod.yml for production (strict security)
- Environment-specific configurations for rate limiting, CSRF, logging
## CRITICAL Fixes (P0)
1. Fixed insecure random number generation
- Replaced Math.random() with crypto.randomBytes() for verification codes
- Now cryptographically secure
2. Implemented rate limiting
- express-rate-limit for all endpoints
- Strict limits on auth endpoints (5 attempts in dev=off, prod=5)
- Email endpoint limits (20 in dev, 3 in prod)
- API-wide rate limiting
3. Added request body size limits
- Development: 50MB (for testing)
- Production: 10KB (security)
4. Fixed user enumeration vulnerability
- Generic error message for registration
- No disclosure of which field exists
5. Added security headers
- helmet.js with CSP, HSTS, XSS protection
- No-sniff, hide powered-by headers
## MEDIUM Fixes (P1)
6. Strengthened password policy
- Environment-aware validation (8+ chars)
- Production: requires uppercase, lowercase, number
- Development: relaxed for testing
7. Enhanced input validation
- Validation for all auth endpoints
- WSDC ID validation (numeric, max 10 digits)
- Name validation (safe characters only)
- Email normalization
8. Added input sanitization
- DOMPurify for XSS prevention
- Sanitize all user inputs in emails
- Timing-safe string comparison for tokens
9. Improved error handling
- Generic errors in production
- Detailed errors only in development
- Proper error logging
10. Enhanced CORS configuration
- Whitelist-based origin validation
- Environment-specific allowed origins
- Credentials support
## New Files
- backend/src/config/security.js - Environment-aware security config
- backend/src/middleware/rateLimiter.js - Rate limiting middleware
- backend/src/utils/sanitize.js - Input sanitization utilities
- backend/.env.example - Development environment template
- backend/.env.production.example - Production environment template
- docker-compose.dev.yml - Development overrides
- docker-compose.prod.yml - Production configuration
- docs/DEPLOYMENT.md - Complete deployment guide
- docs/SECURITY_AUDIT.md - Full security audit report
- .gitignore - Updated to exclude .env files
## Dependencies Added
- helmet (^8.1.0) - Security headers
- express-rate-limit (^8.2.1) - Rate limiting
- dompurify (^3.3.0) - XSS prevention
- jsdom (^27.2.0) - DOM manipulation for sanitization
## Testing
- ✅ Password validation works (weak passwords rejected)
- ✅ User enumeration fixed (generic error messages)
- ✅ WSDC lookup functional
- ✅ Registration flow working
- ✅ Rate limiting active (environment-aware)
- ✅ Security headers present
## Usage
Development:
docker compose -f docker-compose.yml -f docker-compose.dev.yml up
Production:
docker compose -f docker-compose.yml -f docker-compose.prod.yml up
See docs/DEPLOYMENT.md for detailed instructions.
This commit is contained in:
@@ -1,15 +1,57 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const helmet = require('helmet');
|
||||
const securityConfig = require('./config/security');
|
||||
const { apiLimiter } = require('./middleware/rateLimiter');
|
||||
|
||||
const app = express();
|
||||
|
||||
// Middleware
|
||||
app.use(cors({
|
||||
origin: process.env.CORS_ORIGIN || 'http://localhost:8080',
|
||||
credentials: true
|
||||
// Security Headers (helmet)
|
||||
app.use(helmet({
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
styleSrc: ["'self'", "'unsafe-inline'", "https://ui-avatars.com"],
|
||||
scriptSrc: ["'self'"],
|
||||
imgSrc: ["'self'", "data:", "https:", "https://ui-avatars.com"],
|
||||
connectSrc: ["'self'"],
|
||||
fontSrc: ["'self'"],
|
||||
objectSrc: ["'none'"],
|
||||
mediaSrc: ["'self'"],
|
||||
frameSrc: ["'none'"],
|
||||
},
|
||||
},
|
||||
hsts: {
|
||||
maxAge: 31536000,
|
||||
includeSubDomains: true,
|
||||
preload: true,
|
||||
},
|
||||
noSniff: true,
|
||||
xssFilter: true,
|
||||
hidePoweredBy: true,
|
||||
}));
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
// CORS
|
||||
app.use(cors({
|
||||
origin: (origin, callback) => {
|
||||
const allowedOrigins = securityConfig.cors.origin;
|
||||
|
||||
// Allow requests with no origin (mobile apps, curl, etc.)
|
||||
if (!origin) return callback(null, true);
|
||||
|
||||
if (allowedOrigins.includes(origin)) {
|
||||
callback(null, true);
|
||||
} else {
|
||||
callback(new Error('Not allowed by CORS'));
|
||||
}
|
||||
},
|
||||
credentials: securityConfig.cors.credentials,
|
||||
maxAge: 86400, // 24 hours
|
||||
}));
|
||||
|
||||
// Body parsing with size limits
|
||||
app.use(express.json({ limit: securityConfig.bodyLimit }));
|
||||
app.use(express.urlencoded({ extended: true, limit: securityConfig.bodyLimit }));
|
||||
|
||||
// Request logging middleware
|
||||
app.use((req, res, next) => {
|
||||
@@ -27,6 +69,9 @@ app.get('/api/health', (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
// Apply rate limiting to all API routes
|
||||
app.use('/api/', apiLimiter);
|
||||
|
||||
// API routes
|
||||
app.use('/api/auth', require('./routes/auth'));
|
||||
app.use('/api/users', require('./routes/users'));
|
||||
@@ -45,11 +90,24 @@ app.use((req, res) => {
|
||||
|
||||
// Error handler
|
||||
app.use((err, req, res, next) => {
|
||||
// Log full error for debugging
|
||||
console.error('Error:', err);
|
||||
res.status(err.status || 500).json({
|
||||
error: err.message || 'Internal Server Error',
|
||||
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
|
||||
});
|
||||
|
||||
// Determine if we should show detailed errors
|
||||
const isDevelopment = process.env.NODE_ENV === 'development';
|
||||
|
||||
// Generic error response
|
||||
const errorResponse = {
|
||||
success: false,
|
||||
error: isDevelopment ? err.message : 'Internal Server Error',
|
||||
};
|
||||
|
||||
// Add stack trace only in development
|
||||
if (isDevelopment && err.stack) {
|
||||
errorResponse.stack = err.stack;
|
||||
}
|
||||
|
||||
res.status(err.status || 500).json(errorResponse);
|
||||
});
|
||||
|
||||
module.exports = app;
|
||||
|
||||
Reference in New Issue
Block a user