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:
Radosław Gierwiało
2025-11-13 16:39:27 +01:00
parent 46224fca79
commit bf8a9260bd
17 changed files with 2620 additions and 82 deletions

View File

@@ -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;