From 7a87c49703de002205246cbd647f5fdb327c4872 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Sat, 1 Nov 2025 17:54:12 +0100 Subject: [PATCH] added configuration over https / http --- .env.template | 44 +++++++------- backend/src/server.ts | 16 +++++ docker-compose.yml | 13 ++-- frontend/src/App.tsx | 2 + .../SecurityWarning/SecurityWarning.tsx | 59 +++++++++++++++++++ 5 files changed, 109 insertions(+), 25 deletions(-) create mode 100644 frontend/src/components/SecurityWarning/SecurityWarning.tsx diff --git a/.env.template b/.env.template index e9115bf..2ae4c0b 100644 --- a/.env.template +++ b/.env.template @@ -1,27 +1,29 @@ -# === SCHICHTPLANER DOCKER COMPOSE ENVIRONMENT VARIABLES === -# Diese Datei wird von docker-compose automatisch geladen +# .env.template +# ============================================ +# DOCKER COMPOSE ENVIRONMENT TEMPLATE +# Copy this file to .env and adjust values +# ============================================ -# Security -JWT_SECRET=${JWT_SECRET:-your-secret-key-please-change} -NODE_ENV=${NODE_ENV:-production} +# Application settings +NODE_ENV=production +JWT_SECRET=your-secret-key-please-change +HOSTNAME=localhost + +# Security & Network +TRUST_PROXY_ENABLED=false +TRUSTED_PROXY_IPS=127.0.0.1,::1 +FORCE_HTTPS=false # Database -DB_PATH=${DB_PATH:-/app/data/database.db} +DATABASE_PATH=/app/data/schichtplaner.db -# Server -PORT=${PORT:-3002} +# Optional features +ENABLE_PRO=false +DEBUG=false -# App Configuration -APP_TITLE="Shift Planning App" -ENABLE_PRO=${ENABLE_PRO:-false} +# Port configuration +APP_PORT=3002 -# Trust Proxy Configuration -TRUST_PROXY_ENABLED=false -TRUSTED_PROXY_IPS= # Your load balancer/reverse proxy IPs -TRUSTED_PROXY_HEADER=x-forwarded-for - -# Rate Limiting Configuration -RATE_LIMIT_WINDOW_MS=900000 -RATE_LIMIT_MAX_REQUESTS=100 -RATE_LIMIT_SKIP_PATHS=/api/health,/api/status -RATE_LIMIT_WHITELIST=127.0.0.1,::1 \ No newline at end of file +# ============================================ +# END OF TEMPLATE +# ============================================ \ No newline at end of file diff --git a/backend/src/server.ts b/backend/src/server.ts index 269b7b9..ec6d673 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -113,6 +113,21 @@ const configureTrustProxy = (): string | string[] | boolean | number => { app.set('trust proxy', configureTrustProxy()); +app.use((req, res, next) => { + const protocol = req.headers['x-forwarded-proto'] || req.protocol; + const isHttps = protocol === 'https'; + + // Add security warning for HTTP requests + if (!isHttps && process.env.NODE_ENV === 'production') { + res.setHeader('X-Security-Warning', 'This application is being accessed over HTTP. For secure communication, please use HTTPS.'); + + // Log HTTP access in production + console.warn(`⚠️ HTTP access detected: ${req.method} ${req.path} from ${req.ip}`); + } + + next(); +}); + // Security headers app.use(helmet({ contentSecurityPolicy: { @@ -126,6 +141,7 @@ app.use(helmet({ objectSrc: ["'none'"], mediaSrc: ["'self'"], frameSrc: ["'none'"], + upgradeInsecureRequests: process.env.FORCE_HTTPS === 'true' ? [] : null }, }, hsts: { diff --git a/docker-compose.yml b/docker-compose.yml index 4666d37..afcccde 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,17 +6,22 @@ services: image: ghcr.io/donpat1to/schichtenplaner:v1.0.0 environment: - NODE_ENV=production - - JWT_SECRET=${JWT_SECRET:-your-secret-key-please-change} - ports: - - "3002:3002" + - JWT_SECRET=${JWT_SECRET} + - TRUST_PROXY_ENABLED=true + - TRUSTED_PROXY_IPS=nginx-proxy,172.0.0.0/8,10.0.0.0/8,192.168.0.0/16 + - FORCE_HTTPS=${FORCE_HTTPS:-false} + networks: + - app-network volumes: - app_data:/app/data restart: unless-stopped healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:3002/api/health"] + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3002/api/health"] interval: 30s timeout: 10s retries: 3 + expose: + - "3002" volumes: app_data: \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index bd72e3b..e7a21e6 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -16,6 +16,7 @@ import Settings from './pages/Settings/Settings'; import Help from './pages/Help/Help'; import Setup from './pages/Setup/Setup'; import ErrorBoundary from './components/ErrorBoundary/ErrorBoundary'; +import SecurityWarning from './components/SecurityWarning/SecurityWarning'; // Free Footer Link Pages (always available) import FAQ from './components/Layout/FooterLinks/FAQ/FAQ'; @@ -165,6 +166,7 @@ function App() { + diff --git a/frontend/src/components/SecurityWarning/SecurityWarning.tsx b/frontend/src/components/SecurityWarning/SecurityWarning.tsx new file mode 100644 index 0000000..913f0a5 --- /dev/null +++ b/frontend/src/components/SecurityWarning/SecurityWarning.tsx @@ -0,0 +1,59 @@ +// src/components/SecurityWarning/SecurityWarning.tsx +import React, { useState, useEffect } from 'react'; + +const SecurityWarning: React.FC = () => { + const [isHttp, setIsHttp] = useState(false); + const [isDismissed, setIsDismissed] = useState(false); + + useEffect(() => { + // Check if current protocol is HTTP + const checkProtocol = () => { + setIsHttp(window.location.protocol === 'http:'); + }; + + checkProtocol(); + window.addEventListener('load', checkProtocol); + + return () => window.removeEventListener('load', checkProtocol); + }, []); + + if (!isHttp || isDismissed) { + return null; + } + + return ( +
+ ⚠️ SECURITY WARNING: This site is being accessed over HTTP. + For secure communication, please use HTTPS. + +
+ ); +}; + +export default SecurityWarning; \ No newline at end of file