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

@@ -1,25 +0,0 @@
FROM node:18
# Install git and other useful tools
RUN apt-get update && apt-get install -y \
git \
curl \
vim \
&& rm -rf /var/lib/apt/lists/*
# Set working directory
WORKDIR /workspace
# Install global packages
RUN npm install -g typescript ts-node nodemon
# Create user with same UID as host (for file permissions)
ARG USERNAME=node
ARG USER_UID=1000
ARG USER_GID=$USER_UID
RUN groupmod --gid $USER_GID $USERNAME \
&& usermod --uid $USER_UID --gid $USER_GID $USERNAME \
&& chown -R $USER_UID:$USER_GID /home/$USERNAME
USER $USERNAME

View File

@@ -1,42 +0,0 @@
{
"name": "Fotodrucker Full Stack",
"build": {
"dockerfile": "Dockerfile"
},
"workspaceFolder": "/workspace",
"workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached",
"customizations": {
"vscode": {
"extensions": [
"ms-vscode.vscode-typescript-next",
"esbenp.prettier-vscode",
"ms-vscode.vscode-json",
"bradlc.vscode-tailwindcss",
"ms-vscode.vscode-eslint",
"ms-vscode-remote.remote-containers"
],
"settings": {
"typescript.preferences.includePackageJsonAutoImports": "auto",
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
},
"postCreateCommand": "cd backend && npm install && cd ../frontend && npm install",
"forwardPorts": [3000, 3001, 9229, 9230],
"portsAttributes": {
"3000": {
"label": "Frontend (React)",
"onAutoForward": "openBrowser"
},
"3001": {
"label": "Backend (Express)"
},
"9229": {
"label": "Backend Debug Port"
},
"9230": {
"label": "Frontend Debug Port"
}
}
}

View File

@@ -1,16 +0,0 @@
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
volumes:
- ..:/workspace:cached
ports:
- "3000:3000"
- "3001:3001"
- "9229:9229"
- "9230:9230"
command: sleep infinity
working_dir: /workspace

118
.env.example Normal file
View File

@@ -0,0 +1,118 @@
# =============================================================================
# Full Stack TypeScript Template - Environment Configuration
# =============================================================================
#
# SICHERHEITSHINWEISE:
# 1. Kopiere diese Datei zu '.env' und fülle die Werte aus
# 2. NIEMALS .env in Git committen! (bereits in .gitignore)
# 3. Verwende STARKE, ZUFÄLLIGE Passwörter und Secrets!
# 4. Unterschiedliche Werte für Development, Staging und Production!
#
# Generiere sichere Secrets mit:
# openssl rand -base64 32
# oder: node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
# =============================================================================
# -----------------------------------------------------------------------------
# Application Settings
# -----------------------------------------------------------------------------
NODE_ENV=production
APP_PORT=8080
# Docker Container Namen (optional)
APP_CONTAINER_NAME=fullstack-app
POSTGRES_CONTAINER_NAME=postgres-db
# Docker Image (für CI/CD)
APP_IMAGE=fullstack-app:latest
# -----------------------------------------------------------------------------
# Database Configuration (OPTIONAL)
# -----------------------------------------------------------------------------
# Dieses Template kann mit verschiedenen Datenbanken verwendet werden:
# - SQLite (Standard für einfache Projekte, keine Konfiguration nötig)
# - PostgreSQL (für Production, siehe unten)
# - MySQL, MongoDB, etc. (eigene Konfiguration)
#
# Für SQLite: Keine weiteren Einstellungen nötig!
# Die Datenbank wird automatisch im /data Volume erstellt.
# -----------------------------------------------------------------------------
# === PostgreSQL Configuration (Optional) ===
# Nur notwendig wenn PostgreSQL statt SQLite verwendet wird.
# Aktivierung:
# 1. Entkommentiere den postgres-Service in docker-compose.yml
# 2. Setze die Werte unten
# 3. Aktiviere DATABASE_URL
# POSTGRES_DB=appdb
# POSTGRES_USER=postgres
# ⚠️ KRITISCH: MUSS geändert werden! Niemals Default-Passwort verwenden!
# Beispiel für sicheres Passwort generieren:
# openssl rand -base64 32
# POSTGRES_PASSWORD=
# Optional: Für lokales Debugging (default: Port nicht exponiert)
# POSTGRES_PORT=5432
# Vollständige Database URL (nur für PostgreSQL)
# Format: postgresql://USER:PASSWORD@HOST:PORT/DATABASE
# DATABASE_URL=postgresql://postgres:YOUR_PASSWORD@postgres:5432/appdb
# -----------------------------------------------------------------------------
# Security & Authentication
# -----------------------------------------------------------------------------
# ⚠️ KRITISCH: JWT Secret für Token-Signierung
# MUSS ein starker, zufälliger String sein (min. 32 Zeichen)
# Beispiel generieren:
# openssl rand -base64 32
JWT_SECRET=
# Optional: JWT Token Gültigkeit
JWT_EXPIRY=7d
# Optional: Session Secret (falls Sessions verwendet werden)
# SESSION_SECRET=
# -----------------------------------------------------------------------------
# Application Secrets & API Keys
# -----------------------------------------------------------------------------
# Füge hier weitere Secrets hinzu, z.B.:
# STRIPE_API_KEY=
# SENDGRID_API_KEY=
# AWS_ACCESS_KEY_ID=
# AWS_SECRET_ACCESS_KEY=
# REDIS_URL=
# -----------------------------------------------------------------------------
# Development Settings (nur für lokale Entwicklung)
# -----------------------------------------------------------------------------
# Für Development-Modus setze NODE_ENV=development
# NODE_ENV=development
# APP_PORT=3000
# -----------------------------------------------------------------------------
# Beispiel für verschiedene Umgebungen:
# -----------------------------------------------------------------------------
# === DEVELOPMENT (mit SQLite) ===
# NODE_ENV=development
# APP_PORT=3000
# JWT_SECRET=dev_jwt_secret_12345 # NUR für lokales Development!
# # Keine DB-Config nötig für SQLite!
# === STAGING (mit PostgreSQL) ===
# NODE_ENV=staging
# APP_PORT=8080
# JWT_SECRET=[SICHERES_RANDOM_SECRET]
# DATABASE_URL=postgresql://postgres:[PASSWORD]@postgres:5432/appdb
# === PRODUCTION (mit PostgreSQL) ===
# NODE_ENV=production
# APP_PORT=8080
# JWT_SECRET=[SEHR_SICHERES_RANDOM_SECRET]
# DATABASE_URL=postgresql://postgres:[PASSWORD]@postgres:5432/appdb

View File

@@ -0,0 +1,301 @@
name: 🚀 Docker Image Build & Security Scan
on:
push:
branches:
- main
- beta
- alpha
- Nachweise
workflow_dispatch: # Manueller Trigger
schedule:
# Täglicher Security-Scan um 6:00 UTC
- cron: '0 6 * * *'
env:
# ⚠️ ANPASSEN: Nur die Registry-URL anpassen
REGISTRY: git.csnetworkx.dev
# Image-Name wird automatisch aus Repository generiert: REGISTRY/owner/repo
# Beispiel: git.csnetworkx.dev/user/project
TRIVY_VERSION: 0.58.1
jobs:
# 🔍 Security-Scan des Quellcodes und der Abhängigkeiten
security-scan:
name: 🔍 Sicherheitsüberprüfung
runs-on: ubuntu-latest
steps:
- name: 📥 Repository auschecken
uses: actions/checkout@v4
- name: 🔧 Trivy installieren
run: |
echo "📦 Installiere Trivy Security Scanner..."
# Moderne Installation ohne deprecated apt-key
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo gpg --dearmor -o /usr/share/keyrings/trivy-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/trivy-archive-keyring.gpg] https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/trivy.list
sudo apt-get update
sudo apt-get install -y trivy
trivy --version
- name: 🔍 Quellcode & Abhängigkeiten scannen
run: |
echo "🔍 Scanne Quellcode und npm-Abhängigkeiten..."
trivy fs --severity CRITICAL,HIGH --exit-code 1 --ignore-unfixed .
- name: 🔍 Dockerfile & Konfiguration scannen
run: |
echo "🔍 Scanne Dockerfile und IaC-Konfigurationen..."
# Erzwinge auch kleingeschriebene Dockerfiles (z.B. ./dockerfile)
trivy config --severity CRITICAL,HIGH --exit-code 1 \
--file-patterns "dockerfile:.*[Dd]ockerfile.*" .
# 🔄 Täglicher Scan und automatische Fixes (nur bei scheduled runs)
daily-security-scan:
name: 🔄 Täglicher Security-Scan & Auto-Fix
runs-on: ubuntu-latest
if: github.event_name == 'schedule'
steps:
- name: 📥 Repository auschecken (main Branch)
uses: actions/checkout@v4
with:
ref: main
- name: 🏷️ Image-Namen generieren
id: image_name
run: |
# Generiere Image-Name: REGISTRY/repository-lowercase
# Gitea: nutze GITHUB_REPOSITORY oder GITEA_REPOSITORY
REPO="${GITHUB_REPOSITORY:-$GITEA_REPOSITORY}"
IMAGE="${{ env.REGISTRY }}/${REPO}"
IMAGE_LOWER=$(echo "$IMAGE" | tr '[:upper:]' '[:lower:]')
echo "image=${IMAGE_LOWER}" >> "$GITHUB_OUTPUT"
echo "✅ Image-Name: ${IMAGE_LOWER}"
echo "📦 Repository: ${REPO}"
- name: 🟢 Node.js einrichten
uses: actions/setup-node@v4
with:
node-version: '20'
- name: 🔧 Trivy installieren
run: |
echo "📦 Installiere Trivy Security Scanner..."
# Moderne Installation ohne deprecated apt-key
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo gpg --dearmor -o /usr/share/keyrings/trivy-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/trivy-archive-keyring.gpg] https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/trivy.list
sudo apt-get update
sudo apt-get install -y trivy
trivy --version
- name: 🔐 In Docker Registry einloggen
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: 🔍 Docker Image scannen (latest)
id: image_scan
continue-on-error: true
run: |
echo "🐳 Scanne Docker Image: ${{ steps.image_name.outputs.image }}:latest"
trivy image --severity CRITICAL,HIGH --exit-code 1 --ignore-unfixed ${{ steps.image_name.outputs.image }}:latest || echo "scan_failed=true" >> "$GITHUB_OUTPUT"
- name: 🔍 Dateisystem scannen
id: fs_scan
continue-on-error: true
run: |
echo "📁 Scanne Dateisystem und Abhängigkeiten..."
trivy fs --severity CRITICAL,HIGH --exit-code 1 --ignore-unfixed . || echo "scan_failed=true" >> "$GITHUB_OUTPUT"
- name: 🔧 npm audit fix ausführen (Frontend)
if: steps.fs_scan.outcome == 'failure'
working-directory: ./frontend
run: |
echo "🔧 Führe npm audit fix aus (Frontend)..."
npm audit fix --force || true
npm update || true
- name: 🔧 npm audit fix ausführen (Backend)
if: steps.fs_scan.outcome == 'failure'
working-directory: ./backend
run: |
echo "🔧 Führe npm audit fix aus (Backend)..."
npm audit fix --force || true
npm update || true
- name: 🔎 Änderungen prüfen
id: changes
run: |
if git diff --quiet; then
echo "has_changes=false" >> "$GITHUB_OUTPUT"
echo "✅ Keine Änderungen durch npm audit fix"
else
echo "has_changes=true" >> "$GITHUB_OUTPUT"
echo "📝 Änderungen wurden vorgenommen"
fi
- name: 💾 Änderungen committen & pushen
if: steps.changes.outputs.has_changes == 'true'
run: |
echo "💾 Committe automatische Security-Fixes..."
git config user.name "Security Bot"
git config user.email "security-bot@csnetworkx.dev"
git add -A
git commit -m "🔒 Security: Automatische Dependency-Updates [skip ci]
- npm audit fix ausgeführt
- Schwachstellen in Abhängigkeiten behoben
🤖 Automatisch generiert durch den täglichen Security-Scan
Der Build wird beim nächsten manuellen Push ausgeführt."
git push
- name: 🔨 Docker Buildx einrichten
if: steps.image_scan.outcome == 'failure' || steps.changes.outputs.has_changes == 'true'
uses: docker/setup-buildx-action@v3
- name: 🏗️ Docker Image neu bauen & pushen
if: steps.image_scan.outcome == 'failure' || steps.changes.outputs.has_changes == 'true'
uses: docker/build-push-action@v5
with:
context: .
push: true
pull: true # Zieht immer das neueste Base-Image
build-args: |
VERSION_TAG=latest
VERSION_SHA=${{ github.sha }}
tags: ${{ steps.image_name.outputs.image }}:latest
- name: 📊 Security-Report erstellen
if: always()
run: |
echo "╔════════════════════════════════════════╗"
echo "║ 🔒 SECURITY SCAN REPORT 🔒 ║"
echo "╚════════════════════════════════════════╝"
echo ""
echo "📋 Scan-Ergebnisse:"
echo " • Docker Image: ${{ steps.image_scan.outcome }}"
echo " • Dateisystem: ${{ steps.fs_scan.outcome }}"
echo " • Änderungen: ${{ steps.changes.outputs.has_changes }}"
echo ""
if [[ "${{ steps.image_scan.outcome }}" == "failure" ]] || [[ "${{ steps.fs_scan.outcome }}" == "failure" ]]; then
echo "⚠️ SCHWACHSTELLEN GEFUNDEN!"
if [[ "${{ steps.changes.outputs.has_changes }}" == "true" ]]; then
echo "✅ Automatische Fixes wurden angewendet und committed."
else
echo "❌ Keine automatischen Fixes möglich - manuelle Überprüfung erforderlich!"
fi
else
echo "✅ Keine kritischen Schwachstellen gefunden."
fi
echo ""
# 🐳 Docker Image bauen und pushen
docker-build-and-push:
name: 🐳 Docker Image Build & Push
runs-on: ubuntu-latest
needs: security-scan
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && needs.security-scan.result == 'success'
steps:
- name: 📥 Repository auschecken
uses: actions/checkout@v4
- name: 🏷️ Image-Namen & Tags berechnen
id: vars
shell: bash
run: |
# Generiere Image-Name aus Repository (lowercase)
# Gitea: nutze GITHUB_REPOSITORY oder GITEA_REPOSITORY
REPO="${GITHUB_REPOSITORY:-$GITEA_REPOSITORY}"
IMAGE="${{ env.REGISTRY }}/${REPO}"
IMAGE_LOWER=$(echo "$IMAGE" | tr '[:upper:]' '[:lower:]')
echo "image_name=${IMAGE_LOWER}" >> "$GITHUB_OUTPUT"
echo "✅ Image-Name: ${IMAGE_LOWER}"
echo "📦 Repository: ${REPO}"
# Berechne Branch-Tag (Gitea kompatibel)
BRANCH="${GITHUB_REF_NAME:-$GITEA_REF_NAME}"
echo "🏷️ Berechne Image-Tags für Branch: ${BRANCH}"
case "${BRANCH}" in
main) echo "branch_tag=latest" >> "$GITHUB_OUTPUT" ;;
beta) echo "branch_tag=beta" >> "$GITHUB_OUTPUT" ;;
alpha) echo "branch_tag=alpha" >> "$GITHUB_OUTPUT" ;;
Nachweise) echo "branch_tag=Nachweise" >> "$GITHUB_OUTPUT" ;;
*) echo "branch_tag=${BRANCH}" >> "$GITHUB_OUTPUT" ;;
esac
# SHA (Gitea kompatibel)
SHA="${GITHUB_SHA:-$GITEA_SHA}"
echo "short_sha=${SHA::8}" >> "$GITHUB_OUTPUT"
echo "✅ Branch-Tag: $(cat $GITHUB_OUTPUT | grep branch_tag | cut -d'=' -f2)"
echo "✅ SHA-Tag: ${SHA::8}"
- name: 🖥️ QEMU einrichten (Multi-Arch Support)
uses: docker/setup-qemu-action@v3
- name: 🔨 Docker Buildx einrichten
uses: docker/setup-buildx-action@v3
- name: 🔌 Registry-Verbindung testen
run: |
echo "🔌 Teste Verbindung zur Registry: ${{ env.REGISTRY }}"
echo ""
echo "1⃣ HTTP-Verbindungstest..."
curl -v --connect-timeout 10 https://${{ env.REGISTRY }}/v2/ || echo "⚠️ Curl fehlgeschlagen"
echo ""
echo "2⃣ DNS-Auflösung..."
nslookup ${{ env.REGISTRY }} || echo "⚠️ DNS-Lookup fehlgeschlagen"
echo ""
echo "3⃣ Netzwerk-Ping..."
ping -c 3 ${{ env.REGISTRY }} || echo "⚠️ Ping fehlgeschlagen"
- name: 🔐 Manueller Login-Test
run: |
echo "🔐 Teste manuellen Docker-Login..."
echo ${{ secrets.DOCKER_PASSWORD }} | docker login --username ${{ secrets.DOCKER_USERNAME }} --password-stdin ${{ env.REGISTRY }}
echo "✅ Login erfolgreich!"
- name: 🔐 In Docker Registry einloggen
uses: docker/login-action@v3
env:
DOCKER_CLI_DEBUG: 1
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: 🏗️ Docker Image bauen & pushen
uses: docker/build-push-action@v5
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
build-args: |
VERSION_TAG=${{ steps.vars.outputs.branch_tag }}
VERSION_SHA=${{ steps.vars.outputs.short_sha }}
tags: |
${{ steps.vars.outputs.image_name }}:${{ steps.vars.outputs.branch_tag }}
${{ steps.vars.outputs.image_name }}:${{ steps.vars.outputs.short_sha }}
- name: ✅ Build-Zusammenfassung
if: success()
run: |
echo "╔════════════════════════════════════════╗"
echo "║ ✅ BUILD ERFOLGREICH ✅ ║"
echo "╚════════════════════════════════════════╝"
echo ""
echo "📦 Gebaute Images:"
echo " • ${{ steps.vars.outputs.image_name }}:${{ steps.vars.outputs.branch_tag }}"
echo " • ${{ steps.vars.outputs.image_name }}:${{ steps.vars.outputs.short_sha }}"
echo ""
echo "🎯 Plattformen: linux/amd64, linux/arm64"
echo "📤 Erfolgreich in Registry gepusht!"
echo ""
echo "🏷️ Image-Name: ${{ steps.vars.outputs.image_name }}"
echo "📍 Registry: ${{ env.REGISTRY }}"
echo "🔗 Repository: ${GITHUB_REPOSITORY:-$GITEA_REPOSITORY}"
echo ""

59
.vscode/cleanup-ports.sh vendored Normal file
View File

@@ -0,0 +1,59 @@
#!/bin/bash
# Debug Port Cleanup Script
# Stellt sicher, dass die Debug-Ports frei sind und beendet laufende Prozesse
set -euo pipefail
PORTS=("3000" "3001" "9229" "5173" "9222")
echo "🧹 Prüfe und bereinige Debug-Ports (${PORTS[*]})..."
have_lsof=false
have_fuser=false
have_ss=false
command -v lsof >/dev/null 2>&1 && have_lsof=true
command -v fuser >/dev/null 2>&1 && have_fuser=true
command -v ss >/dev/null 2>&1 && have_ss=true
if [ "$have_lsof" = false ] && [ "$have_fuser" = false ] && [ "$have_ss" = false ]; then
echo "⚠️ Weder lsof noch fuser/ss verfügbar. Ports können nicht geprüft/gekilled werden."
exit 0
fi
for PORT in "${PORTS[@]}"; do
PIDS=""
if [ "$have_lsof" = true ]; then
PIDS=$({ lsof -ti :"$PORT" 2>/dev/null || true; } | tr '\n' ' ')
elif [ "$have_fuser" = true ]; then
# fuser listet PIDs, -n tcp beschränkt auf TCP
PIDS=$({ fuser -n tcp "$PORT" 2>/dev/null || true; } | tr '\n' ' ')
elif [ "$have_ss" = true ]; then
# ss fallback, extrahiere pid=... aus der letzten Spalte
PIDS=$({ ss -ltnp "sport = :$PORT" 2>/dev/null || true; } | awk 'NR>1 {split($NF,pid,"pid="); split(pid[2],p,","); if(p[1]!=""){print p[1]}}' | tr '\n' ' ')
fi
if [ -n "$PIDS" ]; then
echo " ✓ Beende Prozesse auf Port $PORT (PID: $PIDS)"
if [ "$have_fuser" = true ]; then
fuser -k -n tcp "$PORT" 2>/dev/null || true
else
kill -9 $PIDS 2>/dev/null || true
fi
else
echo " ○ Port $PORT ist frei"
fi
done
echo "✅ Port-Bereinigung abgeschlossen."
echo ""
echo "Belegte Debug-Ports aktuell:"
if [ "$have_lsof" = true ]; then
lsof -i :3000 -i :3001 -i :9229 -i :5173 -i :9222 2>/dev/null || echo " Alle Debug-Ports sind frei ✓"
elif [ "$have_ss" = true ]; then
ss -ltnp "( sport = :3000 or sport = :3001 or sport = :9229 or sport = :5173 or sport = :9222 )" 2>/dev/null || echo " Alle Debug-Ports sind frei ✓"
else
echo " Port-Status nicht prüfbar (lsof/ss fehlen), Cleanup wurde dennoch ausgeführt."
fi

86
.vscode/launch.json vendored
View File

@@ -2,71 +2,49 @@
"version": "0.2.0",
"configurations": [
{
"name": "🚀 Debug Backend",
"type": "node",
"request": "launch",
"runtimeExecutable": "npx",
"runtimeArgs": [
"ts-node-dev",
"--respawn",
"--transpile-only",
"--no-notify"
],
"args": ["src/index.ts"],
"cwd": "${workspaceFolder}/backend",
"env": {
"NODE_ENV": "development"
},
"console": "integratedTerminal",
"skipFiles": ["<node_internals>/**"],
"sourceMaps": true,
"restart": true,
"outputCapture": "std",
"presentation": {
"group": "fullstack",
"panel": "new"
},
"postDebugTask": "🛑 Terminate All Development Processes"
},
{
"name": "🌐 Debug Frontend (Chrome)",
"name": "Debug Vite",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}/frontend/src",
"skipFiles": ["<node_internals>/**"]
"webRoot": "${workspaceFolder}/Client",
"skipFiles": ["<node_internals>/**"],
"preLaunchTask": "Start Vite (after backend ready)"
},
{
"name": "🔧 Debug Frontend (Edge)",
"type": "msedge",
"name": "Debug Backend",
"type": "node",
"request": "launch",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}/frontend/src",
"runtimeExecutable": "npx",
"args": ["tsx", "watch", "--inspect=9229", "./src/index.ts"],
"cwd": "${workspaceFolder}/Server",
"console": "integratedTerminal",
"skipFiles": ["<node_internals>/**"],
"preLaunchTask": "Clean Debug Ports",
"envFile": "${workspaceFolder}/Server/.env",
"env": {
"PORT": "3001"
}
},
{
"name": "Debug Jest Tests",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/Server/node_modules/jest/bin/jest.js",
"args": [
"--config=Server/tests/jest.config.cjs",
"--runInBand"
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"skipFiles": ["<node_internals>/**"]
}
],
"compounds": [
{
"name": "🚀🌐 Debug Full Stack (Chrome)",
"preLaunchTask": "🌐 Frontend",
"configurations": ["🚀 Debug Backend", "🌐 Debug Frontend (Chrome)"],
"stopAll": true,
"presentation": {
"hidden": false,
"group": "fullstack",
"order": 1
}
},
{
"name": "🚀🔧 Debug Full Stack (Edge)",
"preLaunchTask": "🌐 Frontend",
"configurations": ["🚀 Debug Backend", "🔧 Debug Frontend (Edge)"],
"stopAll": true,
"presentation": {
"hidden": false,
"group": "fullstack",
"order": 2
}
"name": "Compound Debug",
"configurations": ["Debug Vite", "Debug Backend"]
}
]
}

35
.vscode/settings.json vendored
View File

@@ -1,28 +1,9 @@
{
"typescript.preferences.includePackageJsonAutoImports": "auto",
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"files.exclude": {
"**/node_modules": true,
"**/dist": true,
"**/.git": true,
"**/.DS_Store": true
},
"search.exclude": {
"**/node_modules": true,
"**/dist": true
},
"typescript.updateImportsOnFileMove.enabled": "always",
"emmet.includeLanguages": {
"typescript": "typescriptreact"
},
"debug.allowBreakpointsEverywhere": true,
"debug.node.autoAttach": "on",
"terminal.integrated.enablePersistentSessions": false,
"terminal.integrated.confirmOnKill": "editor",
"task.autoDetect": "off",
"task.showDecorations": true
}
"github.copilot.chat.languageContext.inline.typescript.enabled": true,
"github.copilot.chat.languageContext.fix.typescript.enabled": true,
"github.copilot.chat.edits.temporalContext.enabled": true,
"github.copilot.chat.completionContext.typescript.mode": "on",
"github.copilot.chat.agent.thinkingTool": true,
"github.copilot.chat.followUps": "always",
"debug.onTaskErrors": "abort"
}

350
.vscode/tasks.json vendored
View File

@@ -2,320 +2,80 @@
"version": "2.0.0",
"tasks": [
{
"label": "Install Backend Dependencies",
"label": "Clean Debug Ports",
"type": "shell",
"command": "npm",
"args": ["install"],
"options": {
"cwd": "${workspaceFolder}/backend"
},
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared"
}
"command": "bash ${workspaceFolder}/.vscode/cleanup-ports.sh",
"problemMatcher": []
},
{
"label": "Install Frontend Dependencies",
"label": "Wait for Backend Ready",
"type": "shell",
"command": "npm",
"args": ["install"],
"options": {
"cwd": "${workspaceFolder}/frontend"
},
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared"
}
"command": "bash -lc 'for i in {1..120}; do if (echo > /dev/tcp/127.0.0.1/3001) >/dev/null 2>&1; then exit 0; fi; sleep 1; done; echo \"Backend not ready on :3001\"; exit 1'",
"problemMatcher": []
},
{
"label": "🖥️ Backend",
"label": "Start Backend (watch + inspect)",
"type": "shell",
"command": "npm",
"args": ["run", "dev"],
"options": {
"cwd": "${workspaceFolder}/backend"
},
"group": "build",
"command": "npx tsx watch --inspect=9229 ./src/index.ts",
"isBackground": true,
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "dedicated",
"group": "dev",
"showReuseMessage": false
},
"problemMatcher": {
"pattern": {
"regexp": "^.*$",
"file": 1,
"location": 2,
"message": 3
},
"background": {
"activeOnStart": true,
"beginsPattern": "^.*ts-node-dev.*$",
"endsPattern": "^🚀 Backend Server läuft auf Port \\d+$"
}
}
},
{
"label": "🌐 Frontend",
"type": "shell",
"command": "npm",
"args": ["start"],
"options": {
"cwd": "${workspaceFolder}/frontend",
"cwd": "${workspaceFolder}/Server",
"env": {
"BROWSER": "none"
"PORT": "3001"
}
},
"group": "build",
"dependsOn": "Clean Debug Ports",
"problemMatcher": [
{
"owner": "backend-ready",
"fileLocation": ["absolute"],
"pattern": {
"regexp": "^(.*)$",
"message": 1
},
"background": {
"activeOnStart": true,
"beginsPattern": "Starte Datenbankinitialisierung|Server läuft unter http://localhost:3001",
"endsPattern": "NotificationScheduler erfolgreich initialisiert"
}
}
],
"presentation": {
"reveal": "always",
"panel": "dedicated",
"clear": false
}
},
{
"label": "Start Vite (after backend ready)",
"type": "npm",
"script": "frontend",
"isBackground": true,
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "dedicated",
"group": "dev",
"showReuseMessage": false
"options": {
"cwd": "${workspaceFolder}/Client"
},
"problemMatcher": {
"owner": "custom",
"pattern": {
"regexp": "^.*$",
"file": 1,
"location": 2,
"message": 3
},
"background": {
"activeOnStart": true,
"beginsPattern": "^.*Starting the development server.*$",
"endsPattern": "^.*webpack compiled.*$"
"dependsOn": "Wait for Backend Ready",
"dependsOrder": "sequence",
"problemMatcher": [
{
"owner": "vite",
"fileLocation": ["relative", "${workspaceFolder}"],
"pattern": {
"regexp": "^(.*)$",
"file": 1,
"message": 1
},
"background": {
"activeOnStart": true,
"beginsPattern": "VITE|ready in",
"endsPattern": "localhost:3000"
}
}
}
},
{
"label": "🛑 Kill Frontend Process",
"type": "shell",
"command": "pkill",
"args": ["-f", "npm start"],
"options": {
"cwd": "${workspaceFolder}/frontend"
},
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared"
},
"problemMatcher": []
},
{
"label": "🛑 Kill Backend Process",
"type": "shell",
"command": "pkill",
"args": ["-f", "ts-node-dev"],
"options": {
"cwd": "${workspaceFolder}/backend"
},
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared"
},
"problemMatcher": []
},
{
"label": "📊 Show Development Processes Status",
"type": "shell",
"command": "bash",
"args": [
"-c",
"echo '📊 Aktuelle Development-Prozesse:'; echo ''; ps aux | grep -E 'npm.*start|react-scripts|ts-node-dev' | grep -v grep | awk '{print \"PID: \" $2 \" - \" $11 \" \" $12 \" \" $13}' || echo 'Keine Development-Prozesse gefunden'"
],
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared"
},
"problemMatcher": []
},
{
"label": "🛑 Cleanup Development Processes",
"type": "shell",
"command": "bash",
"args": [
"-c",
"pkill -f 'ts-node-dev' > /dev/null 2>&1 || true; pkill -f 'npm.*start' > /dev/null 2>&1 || true; pkill -f 'react-scripts' > /dev/null 2>&1 || true"
],
"group": "build",
"presentation": {
"echo": false,
"reveal": "never",
"focus": false,
"panel": "shared",
"clear": false,
"showReuseMessage": false,
"close": true
},
"problemMatcher": [],
"isBackground": false,
"runOptions": {
"reevaluateOnRerun": true
}
},
{
"label": "🧹 Post Debug Cleanup",
"type": "shell",
"command": "bash",
"args": ["-c", "/workspace/scripts/post-debug-cleanup.sh"],
"group": "build",
"presentation": {
"echo": false,
"reveal": "never",
"focus": false,
"panel": "new",
"clear": false,
"showReuseMessage": false,
"close": true
},
"problemMatcher": [],
"isBackground": false,
"runOptions": {
"reevaluateOnRerun": true,
"runOn": "default"
}
},
{
"label": "🔄 Force Kill All Dev Processes",
"type": "shell",
"command": "bash",
"args": [
"-c",
"RANDOM_ID=$RANDOM; echo \"Cleanup ID: $RANDOM_ID\" > /dev/null; pkill -9 -f 'ts-node-dev' > /dev/null 2>&1 || true; pkill -9 -f 'npm.*start' > /dev/null 2>&1 || true; pkill -9 -f 'react-scripts' > /dev/null 2>&1 || true"
],
"group": "build",
"presentation": {
"echo": false,
"reveal": "never",
"focus": false,
"panel": "shared",
"clear": false,
"showReuseMessage": false,
"close": true
},
"problemMatcher": []
},
{
"label": "🛑 Terminate All Development Processes",
"type": "shell",
"command": "/workspace/scripts/kill-dev-processes.sh",
"args": [],
"group": "build",
"presentation": {
"echo": false,
"reveal": "never",
"focus": false,
"panel": "shared",
"clear": false,
"showReuseMessage": false,
"close": true
},
"problemMatcher": [],
"runOptions": {
"runOn": "default"
}
},
{
"label": "🛑 Terminate All Development Processes (Verbose)",
"type": "shell",
"command": "/workspace/scripts/kill-dev-processes.sh",
"args": [],
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "dedicated",
"clear": true,
"showReuseMessage": false
},
"problemMatcher": []
},
{
"label": "🚀 Start Full Stack (Split Terminal)",
"dependsOrder": "parallel",
"dependsOn": ["🖥️ Backend", "🌐 Frontend"],
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "new"
}
},
{
"label": "🔄 Restart Full Stack",
"type": "shell",
"command": "bash",
"args": [
"-c",
"/workspace/scripts/kill-dev-processes.sh && sleep 2 && echo '🚀 Starte Full Stack...' && code --command 'workbench.action.tasks.runTask' 'shell: 🚀 Start Full Stack (Split Terminal)'"
],
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"clear": true
},
"problemMatcher": []
},
{
"label": "Build Backend",
"type": "shell",
"command": "npm",
"args": ["run", "build"],
"options": {
"cwd": "${workspaceFolder}/backend"
},
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared"
},
"problemMatcher": ["$tsc"]
},
{
"label": "Build Frontend",
"type": "shell",
"command": "npm",
"args": ["run", "build"],
"options": {
"cwd": "${workspaceFolder}/frontend"
},
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared"
"clear": false
}
}
]

View File

@@ -1,164 +0,0 @@
# Woodpecker CI Pipeline für Full Stack TypeScript Template
# Automatisiert Docker Builds für main, beta und alpha branches
when:
event: [push, pull_request]
steps:
# =============================================================================
# Dependencies installieren und Tests ausführen
# =============================================================================
dependencies:
image: node:18-alpine
commands:
- echo "📦 Installing dependencies..."
- npm ci
- cd backend && npm ci && cd ..
- cd frontend && npm ci && cd ..
when:
event: [push, pull_request]
lint_and_test:
image: node:18-alpine
commands:
- echo "🔍 Running linting and tests..."
- cd backend && npm run test && cd ..
- cd frontend && npm run test -- --coverage --watchAll=false && cd ..
when:
event: [push, pull_request]
depends_on: [dependencies]
build_check:
image: node:18-alpine
commands:
- echo "🏗️ Testing production builds..."
- cd backend && npm run build && cd ..
- cd frontend && npm run build && cd ..
when:
event: [push, pull_request]
depends_on: [dependencies]
# =============================================================================
# Docker Build & Push für MAIN Branch (Production)
# =============================================================================
docker_build_push_main:
image: woodpeckerci/plugin-docker-buildx
settings:
repo: YOUR_REGISTRY/YOUR_ORG/fullstack-typescript-template
registry: YOUR_REGISTRY
platforms: linux/amd64,linux/arm64
tag: latest
dockerfile: Dockerfile
context: .
username:
from_secret: docker_username
password:
from_secret: docker_password
build_args:
- NODE_ENV=production
- BUILD_VERSION=${CI_COMMIT_SHA:0:8}
when:
branch: main
event: push
depends_on: [lint_and_test, build_check]
# =============================================================================
# Docker Build & Push für BETA Branch (Staging)
# =============================================================================
docker_build_push_beta:
image: woodpeckerci/plugin-docker-buildx
settings:
repo: YOUR_REGISTRY/YOUR_ORG/fullstack-typescript-template
registry: YOUR_REGISTRY
platforms: linux/amd64,linux/arm64
tag: beta
dockerfile: Dockerfile
context: .
username:
from_secret: docker_username
password:
from_secret: docker_password
build_args:
- NODE_ENV=staging
- BUILD_VERSION=${CI_COMMIT_SHA:0:8}
when:
branch: beta
event: push
depends_on: [lint_and_test, build_check]
# =============================================================================
# Docker Build & Push für ALPHA Branch (Development)
# =============================================================================
docker_build_push_alpha:
image: woodpeckerci/plugin-docker-buildx
settings:
repo: YOUR_REGISTRY/YOUR_ORG/fullstack-typescript-template
registry: YOUR_REGISTRY
platforms: linux/amd64,linux/arm64
tag: alpha
dockerfile: Dockerfile
context: .
username:
from_secret: docker_username
password:
from_secret: docker_password
build_args:
- NODE_ENV=development
- BUILD_VERSION=${CI_COMMIT_SHA:0:8}
when:
branch: alpha
event: push
depends_on: [lint_and_test, build_check]
# =============================================================================
# Notification bei erfolgreichem Build
# =============================================================================
notify_success:
image: alpine:latest
commands:
- echo "✅ Pipeline erfolgreich abgeschlossen!"
- echo "🐳 Docker Image gebaut für Branch: ${CI_COMMIT_BRANCH}"
- echo "📦 Tag: ${CI_COMMIT_BRANCH == 'main' ? 'latest' : CI_COMMIT_BRANCH}"
- echo "🔗 Commit: ${CI_COMMIT_SHA:0:8}"
when:
status: success
event: push
branch: [main, beta, alpha]
depends_on: [docker_build_push_main, docker_build_push_beta, docker_build_push_alpha]
# =============================================================================
# Security Scan (Optional - kann aktiviert werden)
# =============================================================================
# security_scan:
# image: aquasec/trivy:latest
# commands:
# - trivy image --exit-code 0 --severity HIGH,CRITICAL YOUR_REGISTRY/YOUR_ORG/fullstack-typescript-template:${CI_COMMIT_BRANCH == 'main' ? 'latest' : CI_COMMIT_BRANCH}
# when:
# branch: [main, beta, alpha]
# event: push
# depends_on: [docker_build_push_main, docker_build_push_beta, docker_build_push_alpha]
# =============================================================================
# Pipeline Services (falls externe Services für Tests benötigt werden)
# =============================================================================
# services:
# postgres:
# image: postgres:15-alpine
# environment:
# POSTGRES_DB: testdb
# POSTGRES_USER: test
# POSTGRES_PASSWORD: test
# ports:
# - 5432:5432
#
# redis:
# image: redis:7-alpine
# ports:
# - 6379:6379

View File

@@ -52,13 +52,21 @@ RUN apk add --no-cache \
curl \
tzdata \
tini \
su-exec \
shadow \
&& rm -rf /var/cache/apk/*
# Arbeitsverzeichnisse erstellen
RUN mkdir -p /var/www/html \
# Non-root User erstellen (UID/GID 1000 für Kompatibilität)
RUN addgroup -g 1000 appuser \
&& adduser -D -u 1000 -G appuser appuser \
&& mkdir -p /var/www/html \
&& mkdir -p /app/backend/dist \
&& mkdir -p /etc/supervisor.d \
&& mkdir -p /data
&& mkdir -p /data \
&& mkdir -p /var/log/nginx \
&& mkdir -p /var/lib/nginx \
&& mkdir -p /var/tmp/nginx \
&& mkdir -p /run/nginx
# Frontend Build von Builder Stage kopieren
COPY --from=frontend-builder /app/frontend/build /var/www/html
@@ -67,10 +75,10 @@ COPY --from=frontend-builder /app/frontend/build /var/www/html
COPY --from=backend-builder /app/backend/dist /app/backend/dist
COPY --from=backend-builder /app/backend/node_modules /app/backend/node_modules
# Nginx Konfiguration erstellen
# Nginx Konfiguration erstellen (non-root optimiert)
RUN printf 'worker_processes auto;\n\
error_log /var/log/nginx/error.log warn;\n\
pid /var/run/nginx.pid;\n\
pid /run/nginx/nginx.pid;\n\
\n\
events {\n\
worker_connections 1024;\n\
@@ -81,6 +89,13 @@ events {\n\
http {\n\
include /etc/nginx/mime.types;\n\
default_type application/octet-stream;\n\
\n\
# Temp-Pfade für non-root\n\
client_body_temp_path /var/tmp/nginx/client_body;\n\
proxy_temp_path /var/tmp/nginx/proxy;\n\
fastcgi_temp_path /var/tmp/nginx/fastcgi;\n\
uwsgi_temp_path /var/tmp/nginx/uwsgi;\n\
scgi_temp_path /var/tmp/nginx/scgi;\n\
\n\
# Logging\n\
log_format main '\''$remote_addr - $remote_user [$time_local] "$request" '\''\n\
@@ -96,19 +111,24 @@ http {\n\
gzip on;\n\
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;\n\
\n\
# Security Headers\n\
# Moderne Security Headers\n\
add_header X-Frame-Options "SAMEORIGIN" always;\n\
add_header X-Content-Type-Options "nosniff" always;\n\
add_header X-XSS-Protection "1; mode=block" always;\n\
add_header Referrer-Policy "strict-origin-when-cross-origin" always;\n\
add_header Content-Security-Policy "default-src '\''self'\''; script-src '\''self'\'' '\''unsafe-inline'\''; style-src '\''self'\'' '\''unsafe-inline'\''; img-src '\''self'\'' data: https:; font-src '\''self'\'' data:; connect-src '\''self'\'' https:; frame-ancestors '\''self'\''; base-uri '\''self'\''; form-action '\''self'\'';" always;\n\
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;\n\
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;\n\
\n\
# Request-Limits gegen DoS\n\
client_max_body_size 50M;\n\
client_body_timeout 60s;\n\
client_header_timeout 60s;\n\
client_body_buffer_size 128k;\n\
large_client_header_buffers 4 16k;\n\
\n\
server {\n\
listen 80;\n\
listen 8080;\n\
server_name _;\n\
\n\
client_max_body_size 50M;\n\
client_body_timeout 60s;\n\
client_header_timeout 60s;\n\
\n\
# Health Check Endpoint\n\
location /health {\n\
@@ -147,16 +167,22 @@ http {\n\
proxy_connect_timeout 60s;\n\
proxy_send_timeout 60s;\n\
proxy_read_timeout 60s;\n\
\n\
# Buffer-Limits\n\
proxy_buffer_size 4k;\n\
proxy_buffers 8 4k;\n\
proxy_busy_buffers_size 8k;\n\
}\n\
}\n\
}' > /etc/nginx/nginx.conf
# Supervisor Konfiguration erstellen
# Supervisor Konfiguration erstellen (non-root)
RUN printf '[supervisord]\n\
nodaemon=true\n\
loglevel=info\n\
logfile=/dev/stdout\n\
logfile_maxbytes=0\n\
user=appuser\n\
\n\
[program:nginx]\n\
command=/usr/sbin/nginx -g "daemon off;"\n\
@@ -167,6 +193,7 @@ stdout_logfile_maxbytes=0\n\
stderr_logfile=/dev/stderr\n\
stderr_logfile_maxbytes=0\n\
priority=10\n\
user=appuser\n\
\n\
[program:backend]\n\
command=node /app/backend/dist/index.js\n\
@@ -179,12 +206,12 @@ stdout_logfile_maxbytes=0\n\
stderr_logfile=/dev/stderr\n\
stderr_logfile_maxbytes=0\n\
priority=20\n\
user=node\n' > /etc/supervisor.d/supervisord.ini
user=appuser\n' > /etc/supervisor.d/supervisord.ini
# Health Check Script erstellen
RUN printf '#!/bin/sh\n\
# Health check for both nginx and backend\n\
nginx_status=$(curl -f -s http://localhost/health > /dev/null && echo "ok" || echo "fail")\n\
nginx_status=$(curl -f -s http://localhost:8080/health > /dev/null && echo "ok" || echo "fail")\n\
backend_status=$(curl -f -s http://localhost:3001/api/health > /dev/null && echo "ok" || echo "fail")\n\
\n\
if [ "$nginx_status" = "ok" ] && [ "$backend_status" = "ok" ]; then\n\
@@ -194,9 +221,17 @@ else\n\
exit 1\n\
fi' > /usr/local/bin/healthcheck.sh && chmod +x /usr/local/bin/healthcheck.sh
# Berechtigungen setzen
RUN chown -R node:node /data /app/backend
RUN chown nginx:nginx /var/www/html
# Berechtigungen setzen für non-root User
RUN chown -R appuser:appuser \
/data \
/app/backend \
/var/www/html \
/var/log/nginx \
/var/lib/nginx \
/var/tmp/nginx \
/run/nginx \
/etc/nginx \
/usr/local/bin/healthcheck.sh
# Health Check konfigurieren
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
@@ -205,6 +240,12 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
# Arbeitsverzeichnis setzen
WORKDIR /app
# Exponiere Port 8080 (non-root) statt 80
EXPOSE 8080
# Wechsle zu non-root User
USER appuser
# Startup mit Tini für proper signal handling
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor.d/supervisord.ini"]
@@ -216,7 +257,7 @@ LABEL traefik.enable=true
LABEL traefik.http.routers.app.rule="Host(\`your-domain.com\`)"
LABEL traefik.http.routers.app.entrypoints=websecure
LABEL traefik.http.routers.app.tls.certresolver=letsencrypt
LABEL traefik.http.services.app.loadbalancer.server.port=80
LABEL traefik.http.services.app.loadbalancer.server.port=8080
# =============================================================================
# Metadata Labels

195
README.md
View File

@@ -1,15 +1,29 @@
# 🚀 Full Stack TypeScript Template
Ein modernes Full Stack Template mit React Frontend und Express Backend, komplett containerisiert mit Docker.
Ein modernes, **sicherheitsoptimiertes** Full Stack Template mit React Frontend und Express Backend, komplett containerisiert mit Docker.
## 📋 Übersicht
- **Frontend**: React 18 + TypeScript + Create React App
- **Backend**: Express.js + TypeScript + Node.js
- **Database**: PostgreSQL 15
- **Backend**: Express.js + TypeScript + Node.js + Helmet + CORS
- **Database**: SQLite (Standard) oder PostgreSQL 15 (optional, mit scram-sha-256 Auth)
- **Container**: Docker + Docker Compose
- **Development**: Hot Reload für Frontend & Backend
- **CI/CD**: Woodpecker CI Ready
- **CI/CD**: Gitea Actions mit automatischen Security-Scans
- **Security**: Non-root Container, Input-Validierung, Security Headers
- **Flexibel**: PostgreSQL optional - auch für SQLite-only Projekte geeignet
## 🔐 Sicherheitsfeatures
**Non-root Docker Container** - Alle Container laufen als nicht-privilegierte User
**Security Headers** - Helmet.js & moderne CSP-Policies
**Input-Validierung** - express-validator für alle API-Endpoints
**CORS-Whitelist** - Konfigurierbare Origin-Beschränkung
**Resource Limits** - CPU/Memory-Limits gegen DoS
**Isolierte Datenbank** - PostgreSQL (falls verwendet) nur intern erreichbar
**Automatische Scans** - Trivy Security Scans in CI/CD
**Secrets Management** - Keine Hardcoded Credentials
**Flexible Architektur** - SQLite für einfache Projekte, PostgreSQL für Production
## 🛠️ Voraussetzungen
@@ -123,10 +137,14 @@ npm run build:backend # Nur Backend
### Production Build
```bash
# Mit Docker (empfohlen)
npm run docker:prod
# Mit Docker (empfohlen - baut automatisch)
docker-compose up --build -d
# Manuell
# Oder mit npm
npm run docker:build # Baut das Image
npm run docker:up # Startet die Services
# Manuell (ohne Docker)
npm run build
NODE_ENV=production npm start
```
@@ -149,29 +167,93 @@ npm run docker:clean # Docker Cleanup
## 🌍 Environment Variables
Erstelle eine `.env` Datei im Root-Verzeichnis:
### ⚠️ WICHTIG: Sicherheitskonfiguration
```bash
# Database
POSTGRES_DB=appdb
POSTGRES_USER=postgres
POSTGRES_PASSWORD=secure-password-change-this
DATABASE_URL=postgresql://postgres:secure-password-change-this@postgres:5432/appdb
1. **Kopiere die Beispiel-Datei**:
```bash
cp .env.example .env
```
# App
NODE_ENV=development
JWT_SECRET=your-jwt-secret-change-this
2. **Generiere sichere Secrets**:
```bash
# JWT Secret generieren
openssl rand -base64 32
# Ports
APP_PORT=3000
POSTGRES_PORT=5432
```
# PostgreSQL Passwort generieren
openssl rand -base64 32
```
3. **Fülle die `.env` Datei mit deinen Werten**:
```bash
# Database (⚠️ MUSS geändert werden!)
POSTGRES_DB=appdb
POSTGRES_USER=postgres
POSTGRES_PASSWORD=[DEIN_SICHERES_PASSWORT_HIER]
# Security (⚠️ MUSS geändert werden!)
JWT_SECRET=[DEIN_SICHERES_SECRET_HIER]
# App
NODE_ENV=production
APP_PORT=8080
```
### 📋 Vollständige Dokumentation
Siehe [.env.example](.env.example) für alle verfügbaren Optionen und detaillierte Erklärungen.
### 🚨 Sicherheitshinweise
- ❌ **NIEMALS** `.env` in Git committen (bereits in `.gitignore`)
- ✅ Verwende **unterschiedliche Secrets** für Development, Staging und Production
- ✅ Nutze **starke, zufällige Passwörter** (min. 32 Zeichen)
- ✅ Rotiere Secrets regelmäßig in Production-Umgebungen
## 🗄️ Datenbank-Konfiguration
Dieses Template ist **flexibel** und unterstützt verschiedene Datenbanken:
### Option 1: SQLite (Standard - keine Konfiguration nötig)
Perfekt für:
- ✅ Einfache Projekte und Prototypen
- ✅ Low-Traffic Anwendungen
- ✅ Development und Testing
- ✅ Keine separate DB-Server benötigt
**Keine weiteren Schritte nötig!** Die Datenbank wird automatisch im `/data` Volume erstellt.
### Option 2: PostgreSQL (Optional - für Production)
Empfohlen für:
- ✅ Production-Umgebungen
- ✅ High-Traffic Anwendungen
- ✅ Komplexe Datenmodelle
- ✅ Mehrere parallele Connections
**Aktivierung:**
1. Öffne [docker-compose.yml](docker-compose.yml) und entkommentiere den `postgres`-Service (Zeile 82-135)
2. Aktiviere `depends_on` im `app`-Service (Zeile 52-54)
3. Entkommentiere `postgres_data` Volume (Zeile 149-150)
4. Setze in `.env`: `DATABASE_URL=postgresql://postgres:YOUR_PASSWORD@postgres:5432/appdb`
5. Setze ein sicheres `POSTGRES_PASSWORD`
### Option 3: Andere Datenbanken
Du kannst auch MySQL, MongoDB, etc. verwenden:
- Füge den entsprechenden Service in `docker-compose.yml` hinzu
- Passe `DATABASE_URL` in `.env` an
- Installiere den entsprechenden Client (`mysql2`, `mongoose`, etc.)
## 📡 Ports
### Development (lokal ohne Docker)
- **Frontend**: http://localhost:3000
- **Backend**: http://localhost:3001
- **Database**: localhost:5432
### Production (Docker Container)
- **Application**: http://localhost:8080 (Nginx + Backend)
- **Database (PostgreSQL)**: Nur intern erreichbar (kein Port-Expose für Sicherheit)
- **SQLite**: Wird im `/data` Volume gespeichert (kein Port nötig)
> 💡 **Tipp**: Für lokales PostgreSQL-Debugging den Port in `docker-compose.yml` freigeben (Zeile 127-128)
## 🏗️ Projekt-Struktur
@@ -249,6 +331,75 @@ docker system prune -a
./scripts/kill-dev-processes.sh
```
## 🔒 Sicherheits-Best-Practices
### Vor dem Deployment
1. **Environment-Variablen überprüfen**
- [ ] Alle Secrets in `.env` sind gesetzt und stark
- [ ] `NODE_ENV=production` ist gesetzt
- [ ] JWT_SECRET ist ein starker, zufälliger String
- [ ] POSTGRES_PASSWORD ist sicher
2. **CORS-Konfiguration anpassen**
```typescript
// backend/src/index.ts - Zeile 36
const allowedOrigins = [
'https://your-domain.com', // ⚠️ Anpassen!
'https://www.your-domain.com'
];
```
3. **Gitea Actions konfigurieren**
```yaml
# .gittea/workflows/build.yaml - Zeile 17
REGISTRY: git.csnetworkx.dev # Deine Gitea-Instanz URL
# Image-Name wird automatisch generiert: REGISTRY/owner/repo
```
**Secrets in Gitea setzen** (Repository Settings → Secrets):
- `DOCKER_USERNAME` → Dein Gitea Username
- `DOCKER_PASSWORD` → Gitea Access Token mit Package-Rechten
### Container Security
- ✅ Alle Container laufen als **non-root** (UID 1000 / 70)
- ✅ `no-new-privileges` Security Option aktiv
- ✅ Minimale Linux Capabilities (CAP_DROP: ALL)
- ✅ Resource Limits gegen DoS-Angriffe
- ✅ PostgreSQL verwendet scram-sha-256 Authentifizierung
### API Security
- ✅ **Helmet.js** für Security Headers
- ✅ **CORS** mit Origin-Whitelist
- ✅ **Input-Validierung** mit express-validator
- ✅ **Request Size Limits** (10MB max)
- ✅ **Error Handling** ohne Stacktrace-Leaks in Production
### CI/CD Security
Die Gitea Actions Pipeline führt automatisch aus:
- 🔍 **Trivy Security Scans** (Quellcode, Dependencies, Docker Images)
- 🔍 **Dockerfile Lint** (IaC-Konfiguration)
- 🔄 **Automatische npm audit fix** bei Schwachstellen
- 📊 **Security Reports** nach jedem Scan
### Empfohlene Zusatz-Maßnahmen
1. **Secrets Management**: Verwende Vault oder Docker Secrets in Production
2. **TLS/HTTPS**: Nutze Traefik mit Let's Encrypt (Labels bereits im Dockerfile)
3. **Monitoring**: Implementiere Health-Checks und Alerting
4. **Backups**: Automatisiere PostgreSQL-Backups
5. **Updates**: Halte Dependencies aktuell (`npm audit`, `npm outdated`)
6. **Penetration Testing**: Führe regelmäßige Security-Audits durch
### Weitere Ressourcen
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [Docker Security Best Practices](https://docs.docker.com/develop/security-best-practices/)
- [Node.js Security Checklist](https://blog.risingstack.com/node-js-security-checklist/)
## 📄 License
[MIT](LICENSE)

208
SECURITY.md Normal file
View File

@@ -0,0 +1,208 @@
# 🔒 Security Policy
## Sicherheitsarchitektur
Dieses Template wurde mit einem **Security-First-Ansatz** entwickelt und ist optimiert für den Einsatz mit Gitea Actions und anderen CI/CD-Systemen.
## 🛡️ Implementierte Sicherheitsmaßnahmen
### Container Security
#### Non-Root Container (✅ Kritisch)
Alle Container laufen als nicht-privilegierte Benutzer:
- **Application Container**: UID 1000 (`appuser`)
- **PostgreSQL Container**: UID 70 (Standard postgres-User)
- **Frontend Dev Container**: UID 1000 (`node`)
**Warum wichtig?**: Container die als root laufen, können bei einem Breakout den Host kompromittieren.
#### Security Options
```yaml
security_opt:
- no-new-privileges:true # Verhindert Privilege Escalation
cap_drop:
- ALL # Entfernt alle Linux Capabilities
cap_add:
- NET_BIND_SERVICE # Nur benötigte Capabilities
```
#### Resource Limits
```yaml
resources:
limits:
cpus: '2.0'
memory: 2G
```
Verhindert DoS-Angriffe durch Resource-Exhaustion.
### Network Security
#### Isolierte Datenbank
- PostgreSQL ist **nicht** nach außen exponiert
- Nur über internes Docker-Netzwerk erreichbar
- Für lokales Debugging kann der Port bei Bedarf freigegeben werden
#### CORS-Whitelist
```typescript
const allowedOrigins = [
'https://your-domain.com',
'https://www.your-domain.com'
];
```
Nur explizit erlaubte Origins dürfen API-Requests durchführen.
### Application Security
#### Security Headers (Helmet.js)
- `Content-Security-Policy`: Verhindert XSS-Angriffe
- `Strict-Transport-Security`: Erzwingt HTTPS
- `X-Content-Type-Options`: Verhindert MIME-Sniffing
- `X-Frame-Options`: Verhindert Clickjacking
- `Permissions-Policy`: Kontrolliert Browser-Features
#### Input-Validierung
Alle API-Endpoints validieren ihre Inputs:
```typescript
app.post('/api/print', [
body('photoId').isInt({ min: 1 }),
body('copies').isInt({ min: 1, max: 100 }),
validateRequest
], handler);
```
#### Request Size Limits
```typescript
app.use(express.json({ limit: '10mb' }));
```
Verhindert DoS durch große Payloads.
### Database Security
#### Authentifizierung
- Verwendet `scram-sha-256` statt MD5
- Keine Default-Passwörter (müssen gesetzt werden)
- Sichere Passwortgenerierung empfohlen
#### Secrets Management
- Keine Secrets im Code
- `.env` in `.gitignore`
- `.env.example` als Vorlage
## 🔍 CI/CD Security Scans
### Automatische Scans in Gitea Actions
#### 1. Quellcode-Scan
```bash
trivy fs --severity CRITICAL,HIGH --exit-code 1 .
```
Scannt auf Schwachstellen in Dependencies.
#### 2. Docker Image-Scan
```bash
trivy image --severity CRITICAL,HIGH $IMAGE_NAME
```
Scannt fertige Docker Images.
#### 3. IaC-Scan
```bash
trivy config --severity CRITICAL,HIGH .
```
Prüft Dockerfiles und docker-compose.yml.
#### 4. Automatische Fixes
Bei gefundenen Schwachstellen:
- Führt `npm audit fix` aus
- Erstellt automatischen Commit
- Baut neue Docker Images
### Tägliche Security-Scans
- Laufen täglich um 6:00 UTC (Cron-Job)
- Prüfen auf neue Schwachstellen
- Erstellen automatische Updates
## ⚠️ Bekannte Einschränkungen
### Kein Rate Limiting
Rate Limiting ist **absichtlich nicht** implementiert, da es in Development-Umgebungen Probleme verursachen kann.
**Für Production**: Implementiere Rate Limiting über:
- Traefik/Nginx Rate Limiting
- express-rate-limit Middleware
- CloudFlare oder ähnliche Services
### Keine End-to-End Verschlüsselung
Das Template setzt voraus, dass TLS/HTTPS auf Reverse-Proxy-Ebene (z.B. Traefik) implementiert wird.
## 🚨 Vulnerability Reporting
### Sicherheitslücken melden
Wenn du eine Sicherheitslücke in diesem Template findest:
1. **NICHT** als öffentliches GitHub Issue melden
2. Sende eine E-Mail an: [security@your-domain.com]
3. Beschreibe das Problem detailliert
4. Füge PoC hinzu (falls vorhanden)
### Response Timeline
- **Initial Response**: Innerhalb von 48 Stunden
- **Fix & Patch**: Abhängig von Severity (Critical: 7 Tage, High: 30 Tage)
- **Public Disclosure**: Nach Patch-Veröffentlichung
## ✅ Security Checklist vor Deployment
### Pflicht (vor jedem Deployment)
- [ ] `.env` Datei erstellt und ausgefüllt
- [ ] Starke Secrets generiert (min. 32 Zeichen)
- [ ] `NODE_ENV=production` gesetzt
- [ ] CORS-Origins auf Production-Domains angepasst
- [ ] PostgreSQL-Passwort geändert
- [ ] JWT-Secret geändert
### Empfohlen
- [ ] TLS/HTTPS konfiguriert (Traefik mit Let's Encrypt)
- [ ] Monitoring & Alerting eingerichtet
- [ ] Backup-Strategie implementiert
- [ ] Secrets in Vault oder Secret Manager
- [ ] Logging konfiguriert (keine Secrets in Logs!)
- [ ] Firewall-Regeln geprüft
### Optional (Production)
- [ ] Rate Limiting aktiviert
- [ ] WAF (Web Application Firewall) vorgeschaltet
- [ ] DDoS-Protection aktiv
- [ ] Penetration Testing durchgeführt
- [ ] Security Headers im Reverse Proxy verdoppelt
## 📚 Weitere Ressourcen
### Standards & Best Practices
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [OWASP API Security Top 10](https://owasp.org/www-project-api-security/)
- [CIS Docker Benchmark](https://www.cisecurity.org/benchmark/docker)
- [Node.js Security Best Practices](https://nodejs.org/en/docs/guides/security/)
### Tools
- [Trivy Scanner](https://github.com/aquasecurity/trivy)
- [npm audit](https://docs.npmjs.com/cli/v8/commands/npm-audit)
- [Snyk](https://snyk.io/)
- [OWASP ZAP](https://www.zaproxy.org/)
## 📝 Changelog
### Version 1.0.0 (2024)
- ✅ Non-root Container für alle Services
- ✅ Security Headers mit Helmet.js
- ✅ Input-Validierung mit express-validator
- ✅ CORS-Whitelist
- ✅ Resource Limits
- ✅ Isolierte PostgreSQL-Datenbank
- ✅ Automatische Security-Scans in CI/CD
- ✅ scram-sha-256 Authentifizierung für PostgreSQL
## 📄 License
Dieses Security-Dokument ist Teil des Full Stack TypeScript Templates und unterliegt der MIT-Lizenz.

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

View File

@@ -1,47 +1,150 @@
# Docker Compose für Full Stack Application
# SICHERHEITS-OPTIMIERT für Gitea Actions & Production
version: "3.8"
services:
app:
image: ${APP_IMAGE:-fullstack-app:latest}
# Automatischer Build aus dem aktuellen Verzeichnis
build:
context: .
dockerfile: Dockerfile
args:
NODE_ENV: ${NODE_ENV:-production}
BUILD_VERSION: ${BUILD_VERSION:-latest}
# Optional: Image-Name für Registry-Push (für CI/CD)
# image: ${REGISTRY:-localhost}/${IMAGE_NAME:-fullstack-app}:${TAG:-latest}
container_name: ${APP_CONTAINER_NAME:-fullstack-app}
restart: unless-stopped
# Non-root User (UID/GID 1000)
user: "1000:1000"
environment:
- NODE_ENV=${NODE_ENV:-production}
- DATABASE_URL=${DATABASE_URL:-postgresql://postgres:postgres@postgres:5432/appdb}
- JWT_SECRET=${JWT_SECRET:-your-jwt-secret-change-this}
networks:
- app-network
depends_on:
postgres:
condition: service_healthy
ports:
- "${APP_PORT:-3000}:3000"
# Optional: Nur wenn PostgreSQL verwendet wird
- DATABASE_URL=${DATABASE_URL:-}
- JWT_SECRET=${JWT_SECRET}
# Security Options
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE # Erlaubt Binding an Ports < 1024 (falls nötig)
# Resource Limits (verhindern DoS)
deploy:
resources:
limits:
cpus: '2.0'
memory: 2G
reservations:
cpus: '0.5'
memory: 512M
postgres:
image: postgres:15-alpine
container_name: ${POSTGRES_CONTAINER_NAME:-postgres-db}
restart: unless-stopped
environment:
POSTGRES_DB: ${POSTGRES_DB:-appdb}
POSTGRES_USER: ${POSTGRES_USER:-postgres}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- app-network
# Optional: Nur aktivieren wenn PostgreSQL verwendet wird
# depends_on:
# postgres:
# condition: service_healthy
ports:
- "${POSTGRES_PORT:-5432}:5432"
- "${APP_PORT:-8080}:8080" # Port 8080 für non-root
# Volumes für persistente Daten
volumes:
- app-data:/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres}"]
interval: 10s
timeout: 5s
retries: 5
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# =============================================================================
# PostgreSQL Service (OPTIONAL)
# =============================================================================
# Nur aktivieren wenn PostgreSQL benötigt wird.
# Für SQLite oder andere Datenbanken kann dieser Service entfernt werden.
#
# Um PostgreSQL zu aktivieren:
# 1. Entferne die Kommentare (#) vor dem gesamten postgres-Service
# 2. Aktiviere "depends_on" im app-Service (siehe oben)
# 3. Setze DATABASE_URL in .env
# =============================================================================
# postgres:
# image: postgres:15-alpine
# container_name: ${POSTGRES_CONTAINER_NAME:-postgres-db}
# restart: unless-stopped
#
# # Non-root User (postgres verwendet UID 70 standardmäßig)
# user: "70:70"
#
# environment:
# POSTGRES_DB: ${POSTGRES_DB:-appdb}
# POSTGRES_USER: ${POSTGRES_USER:-postgres}
# POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} # MUSS in .env gesetzt werden!
# POSTGRES_INITDB_ARGS: "--auth-host=scram-sha-256" # Sichere Auth
# POSTGRES_HOST_AUTH_METHOD: scram-sha-256
#
# # Security Options
# security_opt:
# - no-new-privileges:true
# cap_drop:
# - ALL
# cap_add:
# - CHOWN
# - DAC_OVERRIDE
# - FOWNER
# - SETGID
# - SETUID
#
# # Resource Limits
# deploy:
# resources:
# limits:
# cpus: '1.0'
# memory: 1G
# reservations:
# cpus: '0.25'
# memory: 256M
#
# volumes:
# - postgres_data:/var/lib/postgresql/data
#
# networks:
# - app-network
#
# # KEIN Port-Expose nach außen - nur intern erreichbar!
# # Für lokales Debugging kann der Port freigegeben werden:
# # ports:
# # - "${POSTGRES_PORT:-5432}:5432"
#
# healthcheck:
# test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres}"]
# interval: 10s
# timeout: 5s
# retries: 5
# start_period: 10s
networks:
app-network:
driver: bridge
# Optional: Netzwerk isolieren
internal: false # Auf 'true' setzen wenn kein Internet-Zugriff nötig
volumes:
postgres_data:
# Persistente Daten für die App (z.B. SQLite-Datenbank, Uploads, etc.)
app-data:
driver: local
# Optional: Nur wenn PostgreSQL verwendet wird
# postgres_data:
# driver: local

View File

@@ -1,14 +1,21 @@
# Frontend Development Dockerfile
# SICHERHEITS-OPTIMIERT: Non-root User
FROM node:18-alpine
# Non-root User erstellen (node-User existiert bereits)
RUN mkdir -p /app && chown -R node:node /app
WORKDIR /app
# Wechsle zu non-root User VOR Package-Installation
USER node
# Package files kopieren
COPY package*.json ./
COPY --chown=node:node package*.json ./
RUN npm ci
# Source code kopieren
COPY . .
COPY --chown=node:node . .
# Port exposieren
EXPOSE 3000

View File

@@ -14,7 +14,7 @@
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,

View File

@@ -1,57 +0,0 @@
#!/bin/bash
# Einfacher Lock-Mechanismus ohne störende Ausgaben
LOCK_FILE="/tmp/kill-dev-processes.lock"
# Prüfe auf bereits laufende Instanz
if [ -f "$LOCK_FILE" ]; then
# Prüfe ob Prozess tatsächlich noch läuft
if [ -f "$LOCK_FILE" ] && kill -0 $(cat "$LOCK_FILE") 2>/dev/null; then
# Stilles Beenden wenn bereits aktiv
exit 0
else
# Aufräumen alter Lock-Dateien
rm -f "$LOCK_FILE" 2>/dev/null
fi
fi
# Erstelle Lock-File mit aktueller PID
echo $$ > "$LOCK_FILE"
trap "rm -f $LOCK_FILE 2>/dev/null" EXIT
echo "🛑 Beende alle Development-Prozesse..."
echo ""
# Finde alle Development-Prozesse
FRONTEND_PIDS=$(ps aux | grep -E 'npm start|react-scripts' | grep -v grep | awk '{print $2}')
BACKEND_PIDS=$(ps aux | grep 'ts-node-dev' | grep -v grep | awk '{print $2}')
echo "Frontend-Prozesse (PIDs): $FRONTEND_PIDS"
echo "Backend-Prozesse (PIDs): $BACKEND_PIDS"
echo ""
# Beende Frontend-Prozesse
if [ ! -z "$FRONTEND_PIDS" ]; then
echo "Beende Frontend-Prozesse..."
echo $FRONTEND_PIDS | xargs kill -TERM 2>/dev/null
sleep 2
echo $FRONTEND_PIDS | xargs kill -KILL 2>/dev/null
echo "✅ Frontend-Prozesse beendet"
else
echo "❌ Keine Frontend-Prozesse gefunden"
fi
# Beende Backend-Prozesse
if [ ! -z "$BACKEND_PIDS" ]; then
echo "Beende Backend-Prozesse..."
echo $BACKEND_PIDS | xargs kill -TERM 2>/dev/null
sleep 1
echo $BACKEND_PIDS | xargs kill -KILL 2>/dev/null
echo "✅ Backend-Prozesse beendet"
else
echo "❌ Keine Backend-Prozesse gefunden"
fi
echo ""
echo "Verbleibende Development-Prozesse:"
ps aux | grep -E 'npm start|react-scripts|ts-node-dev' | grep -v grep || echo "✅ Keine Development-Prozesse mehr vorhanden"