From 1b4622230077769523cf414dda0ed9be3f3074de Mon Sep 17 00:00:00 2001 From: SysAdmin Date: Sun, 28 Sep 2025 18:52:05 +0100 Subject: [PATCH] Security hardening: Fix critical JWT, rate limiting, and deployment issues --- LittleShop/LittleShop.csproj | 1 + LittleShop/Program.cs | 92 +++++++++++++++++++++++++++++++--- force-upgrade-production-db.sh | 52 ++++++++++++++++--- 3 files changed, 130 insertions(+), 15 deletions(-) diff --git a/LittleShop/LittleShop.csproj b/LittleShop/LittleShop.csproj index 17ce1c0..2853bfe 100644 --- a/LittleShop/LittleShop.csproj +++ b/LittleShop/LittleShop.csproj @@ -33,6 +33,7 @@ + \ No newline at end of file diff --git a/LittleShop/Program.cs b/LittleShop/Program.cs index bd16a73..71b4d20 100644 --- a/LittleShop/Program.cs +++ b/LittleShop/Program.cs @@ -6,6 +6,7 @@ using LittleShop.Data; using LittleShop.Services; using FluentValidation; using Serilog; +using AspNetCoreRateLimit; var builder = WebApplication.CreateBuilder(args); @@ -42,8 +43,58 @@ else options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection"))); } +// Rate Limiting - protect anonymous endpoints +builder.Services.AddMemoryCache(); +builder.Services.Configure(options => +{ + options.EnableEndpointRateLimiting = true; + options.StackBlockedRequests = false; + options.HttpStatusCode = 429; + options.RealIpHeader = "X-Real-IP"; + options.ClientIdHeader = "X-ClientId"; + options.GeneralRules = new List + { + new AspNetCoreRateLimit.RateLimitRule + { + Endpoint = "*/api/orders/by-identity/*", + Period = "1m", + Limit = 10 + }, + new AspNetCoreRateLimit.RateLimitRule + { + Endpoint = "*/api/orders/by-customer/*", + Period = "1m", + Limit = 10 + }, + new AspNetCoreRateLimit.RateLimitRule + { + Endpoint = "*", + Period = "1s", + Limit = 10 + }, + new AspNetCoreRateLimit.RateLimitRule + { + Endpoint = "*", + Period = "1m", + Limit = 100 + } + }; +}); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); + // Authentication - Cookie for Admin Panel, JWT for API -var jwtKey = builder.Configuration["Jwt:Key"] ?? "ThisIsASuperSecretKeyForJWTAuthenticationThatIsDefinitelyLongerThan32Characters!"; +var jwtKey = builder.Configuration["Jwt:Key"]; +if (string.IsNullOrEmpty(jwtKey)) +{ + Log.Fatal("🚨 SECURITY: Jwt:Key configuration is missing. Application cannot start securely."); + throw new InvalidOperationException( + "JWT:Key must be configured in environment variables or user secrets. " + + "Set the Jwt__Key environment variable or use: dotnet user-secrets set \"Jwt:Key\" \"\""); +} + var jwtIssuer = builder.Configuration["Jwt:Issuer"] ?? "LittleShop"; var jwtAudience = builder.Configuration["Jwt:Audience"] ?? "LittleShop"; @@ -251,6 +302,9 @@ if (!app.Environment.IsDevelopment()) app.UseHsts(); // Use HSTS for production security } +// Add rate limiting middleware (after CORS, before authentication) +app.UseIpRateLimiting(); + app.UseStaticFiles(); // Enable serving static files app.UseAuthentication(); app.UseAuthorization(); @@ -298,9 +352,27 @@ app.MapGet("/api/version", () => using (var scope = app.Services.CreateScope()) { var context = scope.ServiceProvider.GetRequiredService(); - - // Ensure database is created (temporary while fixing migrations) - context.Database.EnsureCreated(); + + // Use proper migrations in production, EnsureCreated only for development/testing + if (app.Environment.IsProduction()) + { + Log.Information("Production environment: Applying database migrations..."); + try + { + context.Database.Migrate(); + Log.Information("Database migrations applied successfully"); + } + catch (Exception ex) + { + Log.Fatal(ex, "Database migration failed. Application cannot start."); + throw; + } + } + else + { + Log.Information("Development/Testing environment: Using EnsureCreated"); + context.Database.EnsureCreated(); + } // Seed default admin user var authService = scope.ServiceProvider.GetRequiredService(); @@ -310,10 +382,14 @@ using (var scope = app.Services.CreateScope()) var dataSeeder = scope.ServiceProvider.GetRequiredService(); await dataSeeder.SeedSampleDataAsync(); - // Seed system settings - enable test currencies for development - var systemSettings = scope.ServiceProvider.GetRequiredService(); - await systemSettings.SetTestCurrencyEnabledAsync("TBTC", true); - await systemSettings.SetTestCurrencyEnabledAsync("TLTC", true); + // Seed system settings - enable test currencies only in development + if (app.Environment.IsDevelopment()) + { + Log.Information("Development environment: Enabling test currencies"); + var systemSettings = scope.ServiceProvider.GetRequiredService(); + await systemSettings.SetTestCurrencyEnabledAsync("TBTC", true); + await systemSettings.SetTestCurrencyEnabledAsync("TLTC", true); + } } Log.Information("LittleShop API starting up..."); diff --git a/force-upgrade-production-db.sh b/force-upgrade-production-db.sh index ea5d685..88b9cba 100644 --- a/force-upgrade-production-db.sh +++ b/force-upgrade-production-db.sh @@ -1,28 +1,44 @@ #!/bin/bash -# Force upgrade production database - WARNING: This will DELETE all data! -# Use this script when data is not critical and you need a clean migration +# Force upgrade production database - Preserves configuration and wallet data +# This script will recreate the database structure while keeping critical config set -e # Exit on error PRODUCTION_DB_PATH="/opt/littleshop/littleshop-production.db" BACKUP_PATH="/opt/littleshop/backups/littleshop-production-backup-$(date +%Y%m%d_%H%M%S).db" +CONFIG_EXPORT="/tmp/littleshop-config-export.sql" -echo "🔧 Force Upgrade Production Database" -echo "=====================================" +echo "🔧 Force Upgrade Production Database (Preserving Config)" +echo "========================================================" echo "" -echo "⚠️ WARNING: This will DELETE all existing data!" echo "📍 Database: $PRODUCTION_DB_PATH" +echo "💾 Will preserve: SystemSettings, Users, BotRegistrations" echo "" # Create backups directory if it doesn't exist mkdir -p /opt/littleshop/backups -# Backup existing database if it exists +# Backup and export configuration if database exists if [ -f "$PRODUCTION_DB_PATH" ]; then echo "📦 Backing up existing database..." cp "$PRODUCTION_DB_PATH" "$BACKUP_PATH" echo "✅ Backup saved to: $BACKUP_PATH" echo "" + + echo "💾 Exporting configuration data..." + + # Export critical tables to SQL + sqlite3 "$PRODUCTION_DB_PATH" <<'EXPORT_SQL' > "$CONFIG_EXPORT" +.mode insert SystemSettings +SELECT * FROM SystemSettings WHERE 1=1; +.mode insert Users +SELECT * FROM Users WHERE 1=1; +.mode insert BotRegistrations +SELECT * FROM BotRegistrations WHERE 1=1; +EXPORT_SQL + + echo "✅ Configuration exported to: $CONFIG_EXPORT" + echo "" fi # Remove old database files @@ -39,20 +55,42 @@ cd /opt/littleshop dotnet ef database update --project LittleShop.csproj if [ $? -eq 0 ]; then + echo "✅ Migrations applied successfully" echo "" + + # Restore configuration data if export exists + if [ -f "$CONFIG_EXPORT" ]; then + echo "♻️ Restoring configuration data..." + + # Import the exported data + sqlite3 "$PRODUCTION_DB_PATH" < "$CONFIG_EXPORT" + + echo "✅ Configuration data restored" + echo " - SystemSettings (wallet addresses, API keys)" + echo " - Users (admin accounts)" + echo " - BotRegistrations (Telegram bot config)" + echo "" + + # Clean up export file + rm -f "$CONFIG_EXPORT" + fi + echo "✅ Database successfully upgraded!" - echo "📋 All migrations applied successfully" echo "" echo "📊 Database info:" echo " Path: $PRODUCTION_DB_PATH" echo " Backup: $BACKUP_PATH" echo "" echo "🎉 Production database is now ready!" + echo "" + echo "⚠️ Note: Product catalog and orders were reset" + echo " Configuration and users were preserved" else echo "" echo "❌ Migration failed!" echo "🔄 Restoring from backup..." cp "$BACKUP_PATH" "$PRODUCTION_DB_PATH" echo "✅ Backup restored" + rm -f "$CONFIG_EXPORT" exit 1 fi \ No newline at end of file