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:
2025-12-01 08:37:35 +01:00
parent b13e7d1228
commit 4a6b4a0ae8
20 changed files with 1296 additions and 764 deletions

View File

@@ -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"
}
}

View File

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

View File

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