From 539ad79f183f18d2a7ef2b5fc73c6c59641cb591 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Jasi=C5=84ski?= Date: Tue, 26 May 2026 19:55:47 +0200 Subject: [PATCH 01/12] feat(infra): configure beta deployment to Test environment with hardened security and feature flags --- .env.test.template | 41 ++++++++ .gitignore | 1 + docker-compose.test.yml | 97 +++++++++++++++++++ .../Persistence/DbInitializer.cs | 3 +- .../DependencyInjection.cs | 9 +- .../Pages/Account/Login.razor | 17 +++- .../Pages/Account/Register.razor | 10 ++ src/NexusReader.UI.Shared/_Imports.razor | 1 + src/NexusReader.Web/Program.cs | 37 ++++++- src/NexusReader.Web/appsettings.Test.json | 13 +++ 10 files changed, 223 insertions(+), 6 deletions(-) create mode 100644 .env.test.template create mode 100644 docker-compose.test.yml create mode 100644 src/NexusReader.Web/appsettings.Test.json diff --git a/.env.test.template b/.env.test.template new file mode 100644 index 0000000..a4f765b --- /dev/null +++ b/.env.test.template @@ -0,0 +1,41 @@ +# =================================================================== +# NexusReader — Test Environment Variables +# =================================================================== +# Copy this file to `.env` and fill in the values before deployment: +# cp .env.test.template .env +# +# Then deploy with: +# docker compose -f docker-compose.test.yml up -d --build +# =================================================================== + +# === PostgreSQL === +POSTGRES_USER=nexus_user +POSTGRES_PASSWORD=CHANGE_ME_TO_STRONG_PASSWORD +POSTGRES_DB=nexus_test_db +POSTGRES_PORT=5433 + +# === Neo4j === +NEO4J_USERNAME=neo4j +NEO4J_PASSWORD=CHANGE_ME_TO_STRONG_PASSWORD + +# === Qdrant (leave empty to disable API key auth) === +QDRANT_API_KEY= + +# === Web App === +WEB_PORT=5050 + +# === Google OAuth (placeholder for test) === +GOOGLE_CLIENT_ID=placeholder +GOOGLE_CLIENT_SECRET=placeholder + +# === Gemini AI (placeholder for test) === +GOOGLE_AI_API_KEY=placeholder + +# === Admin Seed Password === +NEXUS_ADMIN_PASSWORD=aQ13EdSw2 + +# === Non-standard ports for auxiliary services === +QDRANT_HTTP_PORT=6343 +QDRANT_GRPC_PORT=6344 +NEO4J_HTTP_PORT=7484 +NEO4J_BOLT_PORT=7697 diff --git a/.gitignore b/.gitignore index 57f1b86..7fc6051 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ Thumbs.db *.epub .fake +.env src/NexusReader.Web/nexus.db src/NexusReader.Web/wwwroot/covers/ src/NexusReader.Web/wwwroot/uploads/ diff --git a/docker-compose.test.yml b/docker-compose.test.yml new file mode 100644 index 0000000..5f36acf --- /dev/null +++ b/docker-compose.test.yml @@ -0,0 +1,97 @@ +services: + db: + image: pgvector/pgvector:pg17 + container_name: nexus-db-test + environment: + POSTGRES_USER: ${POSTGRES_USER:-nexus_user} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required} + POSTGRES_DB: ${POSTGRES_DB:-nexus_test_db} + ports: + - "${POSTGRES_PORT:-5433}:5432" + volumes: + - pgdata_test:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-nexus_user} -d ${POSTGRES_DB:-nexus_test_db}"] + interval: 5s + timeout: 5s + retries: 5 + networks: + - nexus-test + restart: unless-stopped + + web: + build: + context: . + dockerfile: Dockerfile + container_name: nexus-web-test + ports: + - "${WEB_PORT:-5050}:5000" + environment: + - ASPNETCORE_ENVIRONMENT=Test + - ConnectionStrings__PostgresConnection=Host=db;Database=${POSTGRES_DB:-nexus_test_db};Username=${POSTGRES_USER:-nexus_user};Password=${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required} + - ConnectionStrings__QdrantConnection=http://qdrant:6334 + - ConnectionStrings__Neo4jConnection=bolt://neo4j:7687 + - Neo4j__Username=${NEO4J_USERNAME:-neo4j} + - Neo4j__Password=${NEO4J_PASSWORD:?NEO4J_PASSWORD is required} + - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID:-placeholder} + - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET:-placeholder} + - Ai__Google__ApiKey=${GOOGLE_AI_API_KEY:-placeholder} + - NEXUS_ADMIN_PASSWORD=${NEXUS_ADMIN_PASSWORD:-aQ13EdSw2} + depends_on: + db: + condition: service_healthy + qdrant: + condition: service_healthy + neo4j: + condition: service_healthy + networks: + - nexus-test + restart: unless-stopped + + qdrant: + image: qdrant/qdrant:latest + container_name: nexus-qdrant-test + environment: + - QDRANT__SERVICE__API_KEY=${QDRANT_API_KEY:-} + ports: + - "${QDRANT_HTTP_PORT:-6343}:6333" + - "${QDRANT_GRPC_PORT:-6344}:6334" + volumes: + - qdrant_test_data:/qdrant/storage + healthcheck: + test: ["CMD-SHELL", "bash -c 'exec 3<>/dev/tcp/127.0.0.1/6333'"] + interval: 5s + timeout: 5s + retries: 5 + networks: + - nexus-test + restart: unless-stopped + + neo4j: + image: neo4j:5-community + container_name: nexus-neo4j-test + environment: + - NEO4J_AUTH=${NEO4J_USERNAME:-neo4j}/${NEO4J_PASSWORD:?NEO4J_PASSWORD is required} + ports: + - "${NEO4J_HTTP_PORT:-7484}:7474" + - "${NEO4J_BOLT_PORT:-7697}:7687" + volumes: + - neo4j_test_data:/data + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://localhost:7474 || exit 1"] + interval: 10s + timeout: 10s + retries: 10 + start_period: 30s + networks: + - nexus-test + restart: unless-stopped + +volumes: + pgdata_test: + qdrant_test_data: + neo4j_test_data: + +networks: + nexus-test: + driver: bridge diff --git a/src/NexusReader.Data/Persistence/DbInitializer.cs b/src/NexusReader.Data/Persistence/DbInitializer.cs index 0e86317..93d30de 100644 --- a/src/NexusReader.Data/Persistence/DbInitializer.cs +++ b/src/NexusReader.Data/Persistence/DbInitializer.cs @@ -68,7 +68,8 @@ public static class DbInitializer SecurityStamp = Guid.NewGuid().ToString() }; - adminUser.PasswordHash = passwordHasher.HashPassword(adminUser, "Admin123!"); + var adminPassword = Environment.GetEnvironmentVariable("NEXUS_ADMIN_PASSWORD") ?? "Admin123!"; + adminUser.PasswordHash = passwordHasher.HashPassword(adminUser, adminPassword); dbContext.Users.Add(adminUser); await dbContext.SaveChangesAsync(); diff --git a/src/NexusReader.Infrastructure/DependencyInjection.cs b/src/NexusReader.Infrastructure/DependencyInjection.cs index 9c39cca..a72162c 100644 --- a/src/NexusReader.Infrastructure/DependencyInjection.cs +++ b/src/NexusReader.Infrastructure/DependencyInjection.cs @@ -56,9 +56,14 @@ public static class DependencyInjection var qdrantUrl = configuration.GetConnectionString("QdrantConnection") ?? "http://localhost:6334"; services.AddSingleton(sp => new QdrantClient(new Uri(qdrantUrl))); - // Neo4j Driver registration + // Neo4j Driver registration (supports optional authentication) var neo4jUrl = configuration.GetConnectionString("Neo4jConnection") ?? "bolt://localhost:7687"; - services.AddSingleton(sp => GraphDatabase.Driver(neo4jUrl, AuthTokens.None)); + var neo4jUser = configuration["Neo4j:Username"]; + var neo4jPass = configuration["Neo4j:Password"]; + var neo4jAuth = !string.IsNullOrEmpty(neo4jUser) + ? AuthTokens.Basic(neo4jUser, neo4jPass ?? string.Empty) + : AuthTokens.None; + services.AddSingleton(sp => GraphDatabase.Driver(neo4jUrl, neo4jAuth)); // Hangfire registration if (!string.IsNullOrEmpty(pgConnectionString)) diff --git a/src/NexusReader.UI.Shared/Pages/Account/Login.razor b/src/NexusReader.UI.Shared/Pages/Account/Login.razor index f7db582..9484478 100644 --- a/src/NexusReader.UI.Shared/Pages/Account/Login.razor +++ b/src/NexusReader.UI.Shared/Pages/Account/Login.razor @@ -7,6 +7,7 @@ @inject IIdentityService IdentityService @inject NavigationManager NavigationManager @inject IJSRuntime JS +@inject IConfiguration Configuration