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