From 10ebf46a98e722d43338ab5fab38fc61907a2577 Mon Sep 17 00:00:00 2001 From: fgbona Date: Mon, 30 Mar 2026 13:19:01 -0300 Subject: [PATCH] harden runtime config and automate first-run permissions Run the container as a non-root user in production to fail fast on insecure deployments. Add DEBUG env-based request/response logging for container diagnostics, and introduce a one-shot init-permissions service in docker-compose so fresh installs automatically fix data/uploads ownership for SQLite write access. --- Dockerfile | 3 +++ docker-compose.yml | 16 ++++++++++++++++ server/.env.example | 1 + server/src/index.ts | 30 ++++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+) diff --git a/Dockerfile b/Dockerfile index b45bd59..7a19f74 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,6 +30,9 @@ COPY --from=client-builder /app/client/public/fonts ./public/fonts RUN mkdir -p /app/data /app/uploads/files /app/uploads/covers /app/uploads/avatars /app/uploads/photos && \ mkdir -p /app/server && ln -s /app/uploads /app/server/uploads && ln -s /app/data /app/server/data +RUN chown -R node:node /app +USER node + # Umgebung setzen ENV NODE_ENV=production ENV PORT=3000 diff --git a/docker-compose.yml b/docker-compose.yml index 1acc607..141f7ac 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,23 @@ services: + init-permissions: + image: alpine:3.20 + container_name: trek-init-permissions + user: "0:0" + command: > + sh -c "mkdir -p /app/data /app/uploads && + chown -R 1000:1000 /app/data /app/uploads && + chmod -R u+rwX /app/data /app/uploads" + volumes: + - ./data:/app/data + - ./uploads:/app/uploads + restart: "no" + app: image: mauriceboe/trek:latest container_name: trek + depends_on: + init-permissions: + condition: service_completed_successfully ports: - "3000:3000" environment: diff --git a/server/.env.example b/server/.env.example index 188bf55..4e2e99e 100644 --- a/server/.env.example +++ b/server/.env.example @@ -1,3 +1,4 @@ PORT=3001 JWT_SECRET=your-super-secret-jwt-key-change-in-production NODE_ENV=development +DEBUG=false diff --git a/server/src/index.ts b/server/src/index.ts index c53977f..666feb0 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -6,6 +6,7 @@ import path from 'path'; import fs from 'fs'; const app = express(); +const DEBUG = String(process.env.DEBUG || 'false').toLowerCase() === 'true'; // Trust first proxy (nginx/Docker) for correct req.ip if (process.env.NODE_ENV === 'production' || process.env.TRUST_PROXY) { @@ -79,6 +80,34 @@ if (shouldForceHttps) { app.use(express.json({ limit: '100kb' })); app.use(express.urlencoded({ extended: true })); +if (DEBUG) { + app.use((req: Request, res: Response, next: NextFunction) => { + const startedAt = Date.now(); + const requestId = Math.random().toString(36).slice(2, 10); + const redact = (value: unknown): unknown => { + if (!value || typeof value !== 'object') return value; + if (Array.isArray(value)) return value.map(redact); + const hidden = new Set(['password', 'token', 'jwt', 'authorization', 'cookie', 'client_secret', 'mfa_token', 'code']); + const out: Record = {}; + for (const [k, v] of Object.entries(value as Record)) { + out[k] = hidden.has(k.toLowerCase()) ? '[REDACTED]' : redact(v); + } + return out; + }; + + const safeQuery = redact(req.query); + const safeBody = redact(req.body); + console.log(`[DEBUG][REQ ${requestId}] ${req.method} ${req.originalUrl} ip=${req.ip} query=${JSON.stringify(safeQuery)} body=${JSON.stringify(safeBody)}`); + + res.on('finish', () => { + const elapsedMs = Date.now() - startedAt; + console.log(`[DEBUG][RES ${requestId}] ${req.method} ${req.originalUrl} status=${res.statusCode} elapsed_ms=${elapsedMs}`); + }); + + next(); + }); +} + // Avatars are public (shown on login, sharing screens) app.use('/uploads/avatars', express.static(path.join(__dirname, '../uploads/avatars'))); @@ -181,6 +210,7 @@ const PORT = process.env.PORT || 3001; const server = app.listen(PORT, () => { console.log(`TREK API running on port ${PORT}`); console.log(`Environment: ${process.env.NODE_ENV || 'development'}`); + console.log(`Debug logs: ${DEBUG ? 'ENABLED' : 'disabled'}`); if (process.env.DEMO_MODE === 'true') console.log('Demo mode: ENABLED'); if (process.env.DEMO_MODE === 'true' && process.env.NODE_ENV === 'production') { console.warn('[SECURITY WARNING] DEMO_MODE is enabled in production! Demo credentials are publicly exposed.');