Security hardening: Fix critical JWT, rate limiting, and deployment issues
This commit is contained in:
parent
586d491b83
commit
1b46222300
@ -33,6 +33,7 @@
|
|||||||
<PackageReference Include="NBitcoin" Version="7.0.37" />
|
<PackageReference Include="NBitcoin" Version="7.0.37" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="WebPush" Version="1.0.12" />
|
<PackageReference Include="WebPush" Version="1.0.12" />
|
||||||
|
<PackageReference Include="AspNetCoreRateLimit" Version="5.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@ -6,6 +6,7 @@ using LittleShop.Data;
|
|||||||
using LittleShop.Services;
|
using LittleShop.Services;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
using AspNetCoreRateLimit;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
@ -42,8 +43,58 @@ else
|
|||||||
options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection")));
|
options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rate Limiting - protect anonymous endpoints
|
||||||
|
builder.Services.AddMemoryCache();
|
||||||
|
builder.Services.Configure<AspNetCoreRateLimit.IpRateLimitOptions>(options =>
|
||||||
|
{
|
||||||
|
options.EnableEndpointRateLimiting = true;
|
||||||
|
options.StackBlockedRequests = false;
|
||||||
|
options.HttpStatusCode = 429;
|
||||||
|
options.RealIpHeader = "X-Real-IP";
|
||||||
|
options.ClientIdHeader = "X-ClientId";
|
||||||
|
options.GeneralRules = new List<AspNetCoreRateLimit.RateLimitRule>
|
||||||
|
{
|
||||||
|
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<AspNetCoreRateLimit.IIpPolicyStore, AspNetCoreRateLimit.MemoryCacheIpPolicyStore>();
|
||||||
|
builder.Services.AddSingleton<AspNetCoreRateLimit.IRateLimitCounterStore, AspNetCoreRateLimit.MemoryCacheRateLimitCounterStore>();
|
||||||
|
builder.Services.AddSingleton<AspNetCoreRateLimit.IRateLimitConfiguration, AspNetCoreRateLimit.RateLimitConfiguration>();
|
||||||
|
builder.Services.AddSingleton<AspNetCoreRateLimit.IProcessingStrategy, AspNetCoreRateLimit.AsyncKeyLockProcessingStrategy>();
|
||||||
|
|
||||||
// Authentication - Cookie for Admin Panel, JWT for API
|
// 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\" \"<your-secure-key>\"");
|
||||||
|
}
|
||||||
|
|
||||||
var jwtIssuer = builder.Configuration["Jwt:Issuer"] ?? "LittleShop";
|
var jwtIssuer = builder.Configuration["Jwt:Issuer"] ?? "LittleShop";
|
||||||
var jwtAudience = builder.Configuration["Jwt:Audience"] ?? "LittleShop";
|
var jwtAudience = builder.Configuration["Jwt:Audience"] ?? "LittleShop";
|
||||||
|
|
||||||
@ -251,6 +302,9 @@ if (!app.Environment.IsDevelopment())
|
|||||||
app.UseHsts(); // Use HSTS for production security
|
app.UseHsts(); // Use HSTS for production security
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add rate limiting middleware (after CORS, before authentication)
|
||||||
|
app.UseIpRateLimiting();
|
||||||
|
|
||||||
app.UseStaticFiles(); // Enable serving static files
|
app.UseStaticFiles(); // Enable serving static files
|
||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
@ -299,8 +353,26 @@ using (var scope = app.Services.CreateScope())
|
|||||||
{
|
{
|
||||||
var context = scope.ServiceProvider.GetRequiredService<LittleShopContext>();
|
var context = scope.ServiceProvider.GetRequiredService<LittleShopContext>();
|
||||||
|
|
||||||
// Ensure database is created (temporary while fixing migrations)
|
// 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();
|
context.Database.EnsureCreated();
|
||||||
|
}
|
||||||
|
|
||||||
// Seed default admin user
|
// Seed default admin user
|
||||||
var authService = scope.ServiceProvider.GetRequiredService<IAuthService>();
|
var authService = scope.ServiceProvider.GetRequiredService<IAuthService>();
|
||||||
@ -310,10 +382,14 @@ using (var scope = app.Services.CreateScope())
|
|||||||
var dataSeeder = scope.ServiceProvider.GetRequiredService<IDataSeederService>();
|
var dataSeeder = scope.ServiceProvider.GetRequiredService<IDataSeederService>();
|
||||||
await dataSeeder.SeedSampleDataAsync();
|
await dataSeeder.SeedSampleDataAsync();
|
||||||
|
|
||||||
// Seed system settings - enable test currencies for development
|
// 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<ISystemSettingsService>();
|
var systemSettings = scope.ServiceProvider.GetRequiredService<ISystemSettingsService>();
|
||||||
await systemSettings.SetTestCurrencyEnabledAsync("TBTC", true);
|
await systemSettings.SetTestCurrencyEnabledAsync("TBTC", true);
|
||||||
await systemSettings.SetTestCurrencyEnabledAsync("TLTC", true);
|
await systemSettings.SetTestCurrencyEnabledAsync("TLTC", true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Information("LittleShop API starting up...");
|
Log.Information("LittleShop API starting up...");
|
||||||
|
|||||||
@ -1,28 +1,44 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Force upgrade production database - WARNING: This will DELETE all data!
|
# Force upgrade production database - Preserves configuration and wallet data
|
||||||
# Use this script when data is not critical and you need a clean migration
|
# This script will recreate the database structure while keeping critical config
|
||||||
|
|
||||||
set -e # Exit on error
|
set -e # Exit on error
|
||||||
|
|
||||||
PRODUCTION_DB_PATH="/opt/littleshop/littleshop-production.db"
|
PRODUCTION_DB_PATH="/opt/littleshop/littleshop-production.db"
|
||||||
BACKUP_PATH="/opt/littleshop/backups/littleshop-production-backup-$(date +%Y%m%d_%H%M%S).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 "🔧 Force Upgrade Production Database (Preserving Config)"
|
||||||
echo "====================================="
|
echo "========================================================"
|
||||||
echo ""
|
echo ""
|
||||||
echo "⚠️ WARNING: This will DELETE all existing data!"
|
|
||||||
echo "📍 Database: $PRODUCTION_DB_PATH"
|
echo "📍 Database: $PRODUCTION_DB_PATH"
|
||||||
|
echo "💾 Will preserve: SystemSettings, Users, BotRegistrations"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Create backups directory if it doesn't exist
|
# Create backups directory if it doesn't exist
|
||||||
mkdir -p /opt/littleshop/backups
|
mkdir -p /opt/littleshop/backups
|
||||||
|
|
||||||
# Backup existing database if it exists
|
# Backup and export configuration if database exists
|
||||||
if [ -f "$PRODUCTION_DB_PATH" ]; then
|
if [ -f "$PRODUCTION_DB_PATH" ]; then
|
||||||
echo "📦 Backing up existing database..."
|
echo "📦 Backing up existing database..."
|
||||||
cp "$PRODUCTION_DB_PATH" "$BACKUP_PATH"
|
cp "$PRODUCTION_DB_PATH" "$BACKUP_PATH"
|
||||||
echo "✅ Backup saved to: $BACKUP_PATH"
|
echo "✅ Backup saved to: $BACKUP_PATH"
|
||||||
echo ""
|
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
|
fi
|
||||||
|
|
||||||
# Remove old database files
|
# Remove old database files
|
||||||
@ -39,20 +55,42 @@ cd /opt/littleshop
|
|||||||
dotnet ef database update --project LittleShop.csproj
|
dotnet ef database update --project LittleShop.csproj
|
||||||
|
|
||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "✅ Migrations applied successfully"
|
||||||
echo ""
|
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 "✅ Database successfully upgraded!"
|
||||||
echo "📋 All migrations applied successfully"
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "📊 Database info:"
|
echo "📊 Database info:"
|
||||||
echo " Path: $PRODUCTION_DB_PATH"
|
echo " Path: $PRODUCTION_DB_PATH"
|
||||||
echo " Backup: $BACKUP_PATH"
|
echo " Backup: $BACKUP_PATH"
|
||||||
echo ""
|
echo ""
|
||||||
echo "🎉 Production database is now ready!"
|
echo "🎉 Production database is now ready!"
|
||||||
|
echo ""
|
||||||
|
echo "⚠️ Note: Product catalog and orders were reset"
|
||||||
|
echo " Configuration and users were preserved"
|
||||||
else
|
else
|
||||||
echo ""
|
echo ""
|
||||||
echo "❌ Migration failed!"
|
echo "❌ Migration failed!"
|
||||||
echo "🔄 Restoring from backup..."
|
echo "🔄 Restoring from backup..."
|
||||||
cp "$BACKUP_PATH" "$PRODUCTION_DB_PATH"
|
cp "$BACKUP_PATH" "$PRODUCTION_DB_PATH"
|
||||||
echo "✅ Backup restored"
|
echo "✅ Backup restored"
|
||||||
|
rm -f "$CONFIG_EXPORT"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
Loading…
Reference in New Issue
Block a user