feat: Enhance security and validation in backend
- Added helmet for security headers and configured content security policy - Implemented CORS with a whitelist for allowed origins - Introduced express-validator for input validation in API endpoints - Set request size limits to prevent DoS attacks - Added global error handling and 404 response - Updated TypeScript configuration to use node16 module resolution - Improved Docker Compose configuration for security and resource limits - Created a comprehensive .env.example for environment configuration - Implemented automated security scans in CI/CD with Trivy - Added cleanup script for debugging ports - Established a detailed security policy document
This commit is contained in:
@@ -24,6 +24,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.18.3"
|
||||
"express": "^4.18.3",
|
||||
"helmet": "^7.1.0",
|
||||
"express-validator": "^7.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,91 @@
|
||||
import express from "express";
|
||||
import cors from "cors";
|
||||
import helmet from "helmet";
|
||||
import { body, validationResult } from "express-validator";
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3001;
|
||||
|
||||
// Middleware
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
// =============================================================================
|
||||
// Security Middleware
|
||||
// =============================================================================
|
||||
|
||||
// Helmet: Setzt wichtige Security Headers
|
||||
app.use(helmet({
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
scriptSrc: ["'self'", "'unsafe-inline'"], // Für React Development
|
||||
styleSrc: ["'self'", "'unsafe-inline'"],
|
||||
imgSrc: ["'self'", "data:", "https:"],
|
||||
connectSrc: ["'self'"],
|
||||
fontSrc: ["'self'", "data:"],
|
||||
objectSrc: ["'none'"],
|
||||
mediaSrc: ["'self'"],
|
||||
frameSrc: ["'none'"],
|
||||
},
|
||||
},
|
||||
hsts: {
|
||||
maxAge: 31536000,
|
||||
includeSubDomains: true,
|
||||
preload: true
|
||||
}
|
||||
}));
|
||||
|
||||
// CORS: Konfiguriert mit Whitelist (anpassen für Production!)
|
||||
const allowedOrigins = process.env.NODE_ENV === 'production'
|
||||
? [
|
||||
'https://your-domain.com',
|
||||
'https://www.your-domain.com'
|
||||
]
|
||||
: [
|
||||
'http://localhost:3000',
|
||||
'http://localhost:8080',
|
||||
'http://127.0.0.1:3000',
|
||||
'http://127.0.0.1:8080'
|
||||
];
|
||||
|
||||
app.use(cors({
|
||||
origin: (origin, callback) => {
|
||||
// Erlaube Requests ohne Origin (z.B. Mobile Apps, Postman)
|
||||
if (!origin) return callback(null, true);
|
||||
|
||||
if (allowedOrigins.indexOf(origin) !== -1) {
|
||||
callback(null, true);
|
||||
} else {
|
||||
console.warn(`🚫 CORS blocked request from origin: ${origin}`);
|
||||
callback(new Error('Not allowed by CORS'));
|
||||
}
|
||||
},
|
||||
credentials: true,
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization'],
|
||||
maxAge: 86400 // 24 Stunden
|
||||
}));
|
||||
|
||||
// Body Parser mit Size Limits (gegen DoS)
|
||||
app.use(express.json({ limit: '10mb' }));
|
||||
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
||||
|
||||
// =============================================================================
|
||||
// Helper: Validierungsergebnis prüfen
|
||||
// =============================================================================
|
||||
const validateRequest = (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// Routes
|
||||
// =============================================================================
|
||||
|
||||
// Health Check Endpoint (ohne Validierung)
|
||||
app.get("/api/health", (req, res) => {
|
||||
// 🐛 Breakpoint-Test: Setze hier einen Breakpoint zum Testen
|
||||
console.log("Health check aufgerufen!");
|
||||
@@ -17,9 +94,11 @@ app.get("/api/health", (req, res) => {
|
||||
status: "OK",
|
||||
message: "Backend is running",
|
||||
timestamp: new Date().toISOString(),
|
||||
environment: process.env.NODE_ENV || 'development'
|
||||
});
|
||||
});
|
||||
|
||||
// Beispiel: GET Endpoint ohne Body
|
||||
app.get("/api/photos", (req, res) => {
|
||||
// Beispiel-Endpunkt für Fotos
|
||||
res.json({
|
||||
@@ -30,16 +109,64 @@ app.get("/api/photos", (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
app.post("/api/print", (req, res) => {
|
||||
// Beispiel-Endpunkt für Druckaufträge
|
||||
const { photoId, copies } = req.body;
|
||||
// Beispiel: POST Endpoint mit Input-Validierung
|
||||
app.post(
|
||||
"/api/print",
|
||||
[
|
||||
// Validierungsregeln
|
||||
body('photoId')
|
||||
.isInt({ min: 1 })
|
||||
.withMessage('photoId muss eine positive Ganzzahl sein'),
|
||||
body('copies')
|
||||
.isInt({ min: 1, max: 100 })
|
||||
.withMessage('copies muss zwischen 1 und 100 liegen'),
|
||||
validateRequest
|
||||
],
|
||||
(req, res) => {
|
||||
// Input ist validiert - sicher zu verwenden
|
||||
const { photoId, copies } = req.body;
|
||||
|
||||
console.log(`Druckauftrag erhalten: Photo ID ${photoId}, Kopien: ${copies}`);
|
||||
console.log(`✅ Druckauftrag erhalten: Photo ID ${photoId}, Kopien: ${copies}`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `Druckauftrag für Photo ${photoId} mit ${copies} Kopien wurde erstellt`,
|
||||
jobId: Math.random().toString(36).substr(2, 9),
|
||||
res.json({
|
||||
success: true,
|
||||
message: `Druckauftrag für Photo ${photoId} mit ${copies} Kopien wurde erstellt`,
|
||||
jobId: Math.random().toString(36).substr(2, 9),
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// =============================================================================
|
||||
// Error Handler
|
||||
// =============================================================================
|
||||
|
||||
// 404 Handler
|
||||
app.use((req, res) => {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
message: 'Endpoint nicht gefunden',
|
||||
path: req.path
|
||||
});
|
||||
});
|
||||
|
||||
// Global Error Handler
|
||||
app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
console.error('❌ Error:', err);
|
||||
|
||||
// CORS Error
|
||||
if (err.message === 'Not allowed by CORS') {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: 'CORS policy: Origin not allowed'
|
||||
});
|
||||
}
|
||||
|
||||
// Generic Error
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: process.env.NODE_ENV === 'production'
|
||||
? 'Interner Serverfehler'
|
||||
: err.message
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"module": "node16",
|
||||
"lib": ["ES2020"],
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
@@ -20,7 +20,7 @@
|
||||
"noImplicitThis": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"moduleResolution": "node",
|
||||
"moduleResolution": "node16",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true
|
||||
|
||||
Reference in New Issue
Block a user