From f3bea57837b27bd4b4e79d636a5619a867e34078 Mon Sep 17 00:00:00 2001 From: ashggan Date: Mon, 19 Jan 2026 18:50:42 +0200 Subject: [PATCH 1/2] feat(docker): Add Docker local development environment - Add docker-compose.yml with PostgreSQL, Redis, Azurite, and server services - Add Dockerfiles for client and server containers - Add database initialization script with schema - Fix POSTGRES_SSL_MODE to read from environment variable (enables local dev without SSL) - Include README with setup instructions Co-Authored-By: Claude Opus 4.5 --- docker/.dockerignore | 48 ++++++++++++++ docker/.env.local | 46 ++++++++++++++ docker/Dockerfile.client | 20 ++++++ docker/Dockerfile.server | 33 ++++++++++ docker/README.md | 130 ++++++++++++++++++++++++++++++++++++++ docker/docker-compose.yml | 116 ++++++++++++++++++++++++++++++++++ docker/init-db.sql | 75 ++++++++++++++++++++++ server/config.ts | 2 +- 8 files changed, 469 insertions(+), 1 deletion(-) create mode 100644 docker/.dockerignore create mode 100644 docker/.env.local create mode 100644 docker/Dockerfile.client create mode 100644 docker/Dockerfile.server create mode 100644 docker/README.md create mode 100644 docker/docker-compose.yml create mode 100644 docker/init-db.sql diff --git a/docker/.dockerignore b/docker/.dockerignore new file mode 100644 index 0000000..fd3ef82 --- /dev/null +++ b/docker/.dockerignore @@ -0,0 +1,48 @@ +# Dependencies +node_modules/ +**/node_modules/ + +# Build outputs +dist/ +**/dist/ +build/ +**/build/ + +# Git +.git/ +.gitignore + +# IDE +.vscode/ +.idea/ + +# OS files +.DS_Store +Thumbs.db + +# Logs +*.log +npm-debug.log* + +# Test files +coverage/ +**/*.test.ts +**/*.test.js +**/*.spec.ts +**/*.spec.js + +# Documentation +docs/ +*.md + +# Docker files (avoid recursive copies) +docker/ +Dockerfile* + +# Environment files (use docker-compose env instead) +.env +.env.* +!.env.example + +# QA tests +qaAutomationTests/ diff --git a/docker/.env.local b/docker/.env.local new file mode 100644 index 0000000..d34f84e --- /dev/null +++ b/docker/.env.local @@ -0,0 +1,46 @@ +# Digital Safe Server Environment Variables for Local Development +# Copy this to server/.env when running the server locally with Docker infrastructure + +# ============================================ +# Core Settings +# ============================================ +ENVIRONMENT=DEVELOPMENT +PORT=2999 + +# ============================================ +# Authentication +# ============================================ +AUTH_TOKEN=dev_auth_token_12345_change_in_production + +# ============================================ +# PostgreSQL (Docker container) +# ============================================ +POSTGRES_HOST=localhost +POSTGRES_USER=digitalsafe +POSTGRES_DB_NAME=digitalsafe +POSTGRES_PASSWORD=digitalsafe_dev_password + +# ============================================ +# Redis (Docker container) +# ============================================ +REDIS_URL=redis://localhost:6379 +REDIS_PASSWORD=redis_dev_password +REDIS_USE_TLS=false + +# ============================================ +# Azure Storage (Azurite emulator) +# ============================================ +AZURE_CONNECTION_STRING=DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://localhost:10000/devstoreaccount1; +AZURE_ACCOUNT_NAME=devstoreaccount1 +AZURE_ACCOUNT_KEY=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw== + +# ============================================ +# Application Settings +# ============================================ +DIGITAL_SAFE_DOMAIN=localhost + +# ============================================ +# Optional Services +# ============================================ +KUTT_URL=https://old.tinylink.me/ +SENTRY_EXPRESS_DSN= diff --git a/docker/Dockerfile.client b/docker/Dockerfile.client new file mode 100644 index 0000000..69381b4 --- /dev/null +++ b/docker/Dockerfile.client @@ -0,0 +1,20 @@ +# Digital Safe Client Dockerfile (Development) +FROM node:20-alpine + +WORKDIR /app + +# Copy package files for client +COPY client/package*.json ./client/ + +# Install client dependencies +WORKDIR /app/client +RUN npm ci + +# Copy client source code +COPY client/ ./ + +# Expose Vite dev server port +EXPOSE 5173 + +# Start Vite dev server with host binding for Docker +CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"] diff --git a/docker/Dockerfile.server b/docker/Dockerfile.server new file mode 100644 index 0000000..16fed7c --- /dev/null +++ b/docker/Dockerfile.server @@ -0,0 +1,33 @@ +# Digital Safe Server Dockerfile +FROM node:20-alpine + +# Install build dependencies for native modules (argon2) +RUN apk add --no-cache python3 make g++ libc6-compat + +WORKDIR /app + +# Copy package files for server +COPY server/package*.json ./server/ + +# Install server dependencies +WORKDIR /app/server +RUN npm ci + +# Copy server source code +COPY server/ ./ + +# Copy shared files if needed +COPY custom-types/ /app/custom-types/ +COPY custom-webgl-ext.d.ts /app/ + +# Build TypeScript +RUN npm run build + +# Set working directory to dist for running +WORKDIR /app/server/dist + +# Expose port +EXPOSE 2999 + +# Start the server +CMD ["node", "./server.js"] diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000..d6f8518 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,130 @@ +# Digital Safe - Docker Development Setup + +This directory contains Docker configuration for local development. + +## Prerequisites + +- Docker Desktop installed and running +- Docker Compose (included with Docker Desktop) + +## Quick Start + +### Option 1: Infrastructure Only (Recommended for Development) + +Start only the database and storage services, then run the app locally: + +```bash +# From the docker directory +cd docker + +# Start PostgreSQL, Redis, and Azurite +docker-compose up -d postgres redis azurite + +# Wait for services to be healthy +docker-compose ps + +# Now run the app locally from the project root +cd .. +npm run dev:win # Windows +# or +npm run dev # Mac/Linux +``` + +### Option 2: Full Stack in Docker + +Run everything in Docker containers: + +```bash +# From the docker directory +cd docker + +# Start all services including server +docker-compose up -d + +# Or include the client as well +docker-compose --profile with-client up -d +``` + +## Services + +| Service | Port | Description | +|-----------|-------|---------------------------------| +| postgres | 5432 | PostgreSQL database | +| redis | 6379 | Redis cache | +| azurite | 10000 | Azure Blob Storage emulator | +| server | 2999 | Express API server | +| client | 5173 | Vite dev server (optional) | + +## Environment Variables + +Copy `.env` and modify as needed. The default values work for local development. + +### Connecting to Services Locally + +When running the app locally (not in Docker), use these connection settings: + +```env +# PostgreSQL +POSTGRES_HOST=localhost +POSTGRES_USER=digitalsafe +POSTGRES_PASSWORD=digitalsafe_dev_password +POSTGRES_DB_NAME=digitalsafe + +# Redis +REDIS_URL=redis://localhost:6379 +REDIS_PASSWORD=redis_dev_password +REDIS_USE_TLS=false + +# Azure Storage (Azurite) +AZURE_CONNECTION_STRING=DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://localhost:10000/devstoreaccount1; +``` + +## Common Commands + +```bash +# Start services +docker-compose up -d + +# Stop services +docker-compose down + +# View logs +docker-compose logs -f + +# View logs for specific service +docker-compose logs -f postgres + +# Reset database (removes all data) +docker-compose down -v +docker-compose up -d + +# Rebuild containers after code changes +docker-compose build --no-cache +docker-compose up -d + +# Connect to PostgreSQL +docker exec -it digitalsafe-postgres psql -U digitalsafe -d digitalsafe + +# Connect to Redis +docker exec -it digitalsafe-redis redis-cli -a redis_dev_password +``` + +## Troubleshooting + +### Port already in use +If you get port conflicts, either stop the conflicting service or modify the ports in `docker-compose.yml`. + +### Database not initializing +The init script only runs on first startup. To re-run it: +```bash +docker-compose down -v # Remove volumes +docker-compose up -d # Start fresh +``` + +### Server can't connect to services +Make sure services are healthy: +```bash +docker-compose ps +``` + +All services should show "healthy" or "Up" status. diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..912f40c --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,116 @@ +services: + # PostgreSQL Database + postgres: + image: postgres:16-alpine + container_name: digitalsafe-postgres + environment: + POSTGRES_USER: ${POSTGRES_USER:-digitalsafe} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-digitalsafe_password} + POSTGRES_DB: ${POSTGRES_DB:-digitalsafe} + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + - ./init-db.sql:/docker-entrypoint-initdb.d/init-db.sql:ro + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-digitalsafe} -d ${POSTGRES_DB:-digitalsafe}"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - digitalsafe-network + + # Redis Cache + redis: + image: redis:7-alpine + container_name: digitalsafe-redis + command: redis-server --requirepass ${REDIS_PASSWORD:-redis_password} + ports: + - "6379:6379" + volumes: + - redis_data:/data + healthcheck: + test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD:-redis_password}", "ping"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - digitalsafe-network + + # Azurite - Azure Storage Emulator + azurite: + image: mcr.microsoft.com/azure-storage/azurite + container_name: digitalsafe-azurite + command: azurite --blobHost 0.0.0.0 --queueHost 0.0.0.0 --tableHost 0.0.0.0 --loose + ports: + - "10000:10000" # Blob service + - "10001:10001" # Queue service + - "10002:10002" # Table service + volumes: + - azurite_data:/data + networks: + - digitalsafe-network + + # Server (Express API) + server: + build: + context: .. + dockerfile: docker/Dockerfile.server + container_name: digitalsafe-server + environment: + - ENVIRONMENT=DEVELOPMENT + - PORT=2999 + - AUTH_TOKEN=${AUTH_TOKEN:-dev_auth_token_12345} + - POSTGRES_HOST=postgres + - POSTGRES_USER=${POSTGRES_USER:-digitalsafe} + - POSTGRES_DB_NAME=${POSTGRES_DB:-digitalsafe} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-digitalsafe_password} + - POSTGRES_SSL_MODE=disable + - DIGITAL_SAFE_DOMAIN=${DIGITAL_SAFE_DOMAIN:-localhost} + - AZURE_CONNECTION_STRING=DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://azurite:10000/devstoreaccount1; + - AZURE_ACCOUNT_NAME=devstoreaccount1 + - AZURE_ACCOUNT_KEY=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw== + - REDIS_URL=redis://redis:6379 + - REDIS_PASSWORD=${REDIS_PASSWORD:-redis_password} + - REDIS_USE_TLS=false + - KUTT_URL=${KUTT_URL:-https://old.tinylink.me/} + ports: + - "2999:2999" + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + azurite: + condition: service_started + networks: + - digitalsafe-network + + # Client (Vite Dev Server) - Optional for development + client: + build: + context: .. + dockerfile: docker/Dockerfile.client + container_name: digitalsafe-client + environment: + - VITE_API_URL=http://server:2999 + ports: + - "5173:5173" + volumes: + - ../client/src:/app/client/src:ro + - ../client/public:/app/client/public:ro + depends_on: + - server + networks: + - digitalsafe-network + profiles: + - with-client + +volumes: + postgres_data: + redis_data: + azurite_data: + +networks: + digitalsafe-network: + driver: bridge diff --git a/docker/init-db.sql b/docker/init-db.sql new file mode 100644 index 0000000..bdaada1 --- /dev/null +++ b/docker/init-db.sql @@ -0,0 +1,75 @@ +-- Digital Safe Database Initialization Script +-- This script combines all table definitions and migrations + +-- ============================================ +-- Table 1: digital_safe_path +-- ============================================ +CREATE TABLE IF NOT EXISTS digital_safe_path ( + path text NOT NULL PRIMARY KEY, + created timestamp WITH time zone NOT NULL DEFAULT now(), + sealed boolean NOT NULL DEFAULT false +); + +-- ============================================ +-- Table 2: csrf_token +-- ============================================ +CREATE TABLE IF NOT EXISTS csrf_token ( + csrf text PRIMARY KEY CHECK (LENGTH(csrf) = 36), + created timestamp WITH time zone NOT NULL DEFAULT now() +); + +-- ============================================ +-- Table 3: digital_safe +-- ============================================ +CREATE TABLE IF NOT EXISTS digital_safe ( + -- `pubkey_base32` is the user's base32-encoded public key that also + -- serves as the uniquely identifier for this Safe. In Azure Blob + -- Storage, the folder that stores a user's Safe should have the + -- name of their `pubkey_base32`. + pubkey_base32 text NOT NULL PRIMARY KEY, + + -- Basically used for 2FA; the user needs the right public key and + -- "username" / safe name to successfully login. + username text NOT NULL, + + created timestamp WITH time zone NOT NULL DEFAULT now(), + + -- CSRF token reference + csrf text UNIQUE REFERENCES csrf_token(csrf) ON DELETE SET NULL, + + -- Username hash for lookup + username_hash text NOT NULL DEFAULT '', + + -- Signing public key + signing_pubkey_base64 text NOT NULL DEFAULT '' +); + +-- ============================================ +-- Constraints +-- ============================================ +ALTER TABLE digital_safe +ADD CONSTRAINT digital_safe_check_pubkey_base32_length +CHECK (LENGTH(pubkey_base32) <= 100); + +ALTER TABLE digital_safe +ADD CONSTRAINT digital_safe_check_signing_pubkey_base64_length +CHECK (LENGTH(signing_pubkey_base64) <= 100); + +-- ============================================ +-- Indexes for better performance +-- ============================================ +CREATE INDEX IF NOT EXISTS idx_digital_safe_username_hash +ON digital_safe(username_hash); + +CREATE INDEX IF NOT EXISTS idx_digital_safe_created +ON digital_safe(created); + +CREATE INDEX IF NOT EXISTS idx_csrf_token_created +ON csrf_token(created); + +-- ============================================ +-- Grant permissions (if using separate user) +-- ============================================ +-- These are commented out as docker-compose creates the user with full permissions +-- GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO digitalsafe; +-- GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO digitalsafe; diff --git a/server/config.ts b/server/config.ts index e1d6c9e..1e1d157 100644 --- a/server/config.ts +++ b/server/config.ts @@ -5,7 +5,7 @@ if (!process.env.ENVIRONMENT) { export const config = { REDIS_USE_TLS: process.env.REDIS_USE_TLS !== 'false', - POSTGRES_SSL_MODE: 'verify-full', + POSTGRES_SSL_MODE: process.env.POSTGRES_SSL_MODE || 'verify-full', ENVIRONMENT: process.env.ENVIRONMENT || 'DEVELOPMENT', PORT: process.env.PORT, AUTH_TOKEN: process.env.AUTH_TOKEN, From d4a6f22a364a3da851178866e3bda04939298824 Mon Sep 17 00:00:00 2001 From: ashggan Date: Tue, 20 Jan 2026 11:16:15 +0200 Subject: [PATCH 2/2] fix(docker): Use non-root user for security Add USER node directive to Dockerfiles to fix Trivy DS002 high-severity vulnerability about running containers as root. Co-Authored-By: Claude Opus 4.5 --- docker/Dockerfile.client | 3 +++ docker/Dockerfile.server | 3 +++ 2 files changed, 6 insertions(+) diff --git a/docker/Dockerfile.client b/docker/Dockerfile.client index 69381b4..036680a 100644 --- a/docker/Dockerfile.client +++ b/docker/Dockerfile.client @@ -16,5 +16,8 @@ COPY client/ ./ # Expose Vite dev server port EXPOSE 5173 +# Use non-root user for security +USER node + # Start Vite dev server with host binding for Docker CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"] diff --git a/docker/Dockerfile.server b/docker/Dockerfile.server index 16fed7c..8bca735 100644 --- a/docker/Dockerfile.server +++ b/docker/Dockerfile.server @@ -29,5 +29,8 @@ WORKDIR /app/server/dist # Expose port EXPOSE 2999 +# Use non-root user for security +USER node + # Start the server CMD ["node", "./server.js"]