Implemented a professional loading screen for the PWA to eliminate the "hang and wait" experience during app initialization. Changes: - Added full-screen gradient loading overlay with TeleShop branding - Implemented animated triple-ring spinner with smooth animations - Added automatic removal after PWA initialization (500ms fade-out) - Included 5-second fallback timeout to prevent infinite loading - Updated service worker cache version to v2 - Enhanced JWT validation to detect test/temporary keys - Updated appsettings.json with secure JWT key Design Features: - Purple/blue gradient background matching brand colors - Pulsing logo animation for visual interest - Staggered spinner rings with cubic-bezier easing - Fade-in-out loading text animation - Mobile-responsive design (scales appropriately on all devices) Technical Implementation: - Loading screen visible by default (no FOUC) - Removed via JavaScript when PWA manager initialization completes - Graceful fade-out animation before DOM removal - Console logging for debugging 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
170 lines
6.3 KiB
C#
170 lines
6.3 KiB
C#
using Microsoft.Extensions.Options;
|
|
|
|
namespace LittleShop.Services;
|
|
|
|
/// <summary>
|
|
/// Validates critical configuration settings on startup to prevent security issues
|
|
/// </summary>
|
|
public class ConfigurationValidationService
|
|
{
|
|
private readonly IConfiguration _configuration;
|
|
private readonly IWebHostEnvironment _environment;
|
|
private readonly ILogger<ConfigurationValidationService> _logger;
|
|
|
|
public ConfigurationValidationService(
|
|
IConfiguration configuration,
|
|
IWebHostEnvironment environment,
|
|
ILogger<ConfigurationValidationService> logger)
|
|
{
|
|
_configuration = configuration;
|
|
_environment = environment;
|
|
_logger = logger;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validates all critical configuration settings on startup
|
|
/// Throws exceptions for security-critical misconfigurations
|
|
/// </summary>
|
|
public void ValidateConfiguration()
|
|
{
|
|
_logger.LogInformation("🔍 Validating application configuration...");
|
|
|
|
// JWT validation is critical in production, optional in development/testing
|
|
if (_environment.IsProduction() || !string.IsNullOrEmpty(_configuration["Jwt:Key"]))
|
|
{
|
|
ValidateJwtConfiguration();
|
|
}
|
|
else if (_environment.IsDevelopment())
|
|
{
|
|
_logger.LogWarning("⚠️ JWT validation skipped in development. Configure Jwt:Key for production readiness.");
|
|
}
|
|
|
|
ValidateSilverPayConfiguration();
|
|
ValidateProductionSafeguards();
|
|
ValidateEnvironmentConfiguration();
|
|
|
|
_logger.LogInformation("✅ Configuration validation completed successfully");
|
|
}
|
|
|
|
private void ValidateJwtConfiguration()
|
|
{
|
|
var jwtKey = _configuration["Jwt:Key"];
|
|
|
|
if (string.IsNullOrEmpty(jwtKey))
|
|
{
|
|
throw new InvalidOperationException("🚨 CRITICAL: JWT Key not configured. Set Jwt:Key in appsettings.json");
|
|
}
|
|
|
|
// Check for the old hardcoded keys
|
|
if (jwtKey.Contains("ThisIsASuperSecretKey") || jwtKey.Contains("ThisIsATemporary"))
|
|
{
|
|
throw new InvalidOperationException("🚨 CRITICAL: Default JWT key detected. Generate a new secure key!");
|
|
}
|
|
|
|
// Require minimum key length for security
|
|
if (jwtKey.Length < 32)
|
|
{
|
|
throw new InvalidOperationException("🚨 CRITICAL: JWT key too short. Must be at least 32 characters.");
|
|
}
|
|
|
|
_logger.LogInformation("✅ JWT configuration validated");
|
|
}
|
|
|
|
private void ValidateSilverPayConfiguration()
|
|
{
|
|
var baseUrl = _configuration["SilverPay:BaseUrl"];
|
|
var apiKey = _configuration["SilverPay:ApiKey"];
|
|
|
|
if (string.IsNullOrEmpty(baseUrl))
|
|
{
|
|
throw new InvalidOperationException("🚨 CRITICAL: SilverPay BaseUrl not configured");
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(apiKey))
|
|
{
|
|
throw new InvalidOperationException("🚨 CRITICAL: SilverPay ApiKey not configured");
|
|
}
|
|
|
|
// Check for test/mock indicators in production
|
|
if (_environment.IsProduction())
|
|
{
|
|
if (baseUrl.Contains("localhost") || baseUrl.Contains("127.0.0.1"))
|
|
{
|
|
throw new InvalidOperationException("🚨 CRITICAL: SilverPay configured with localhost in production!");
|
|
}
|
|
|
|
if (apiKey.Contains("test") || apiKey.Contains("mock") || apiKey.Contains("demo"))
|
|
{
|
|
_logger.LogWarning("⚠️ WARNING: SilverPay API key contains test/mock indicators in production");
|
|
}
|
|
}
|
|
|
|
_logger.LogInformation("✅ SilverPay configuration validated");
|
|
}
|
|
|
|
private void ValidateProductionSafeguards()
|
|
{
|
|
// Ensure no mock services can be accidentally enabled
|
|
var mockServiceConfig = _configuration.GetSection("SilverPay").GetChildren()
|
|
.Where(x => x.Key.ToLower().Contains("mock") || x.Key.ToLower().Contains("test"))
|
|
.ToList();
|
|
|
|
if (mockServiceConfig.Any())
|
|
{
|
|
foreach (var config in mockServiceConfig)
|
|
{
|
|
_logger.LogWarning("⚠️ Found mock/test configuration: {Key} = {Value}", config.Key, config.Value);
|
|
}
|
|
}
|
|
|
|
// In production, absolutely no mock configurations should exist
|
|
if (_environment.IsProduction())
|
|
{
|
|
var useMockService = _configuration.GetValue<bool>("SilverPay:UseMockService", false);
|
|
if (useMockService)
|
|
{
|
|
throw new InvalidOperationException("🚨 CRITICAL: Mock service enabled in production! Set SilverPay:UseMockService to false");
|
|
}
|
|
|
|
// Check for any configuration that might enable testing/mocking
|
|
var dangerousConfigs = new[]
|
|
{
|
|
"Testing:Enabled",
|
|
"Mock:Enabled",
|
|
"Development:MockPayments",
|
|
"Debug:MockServices"
|
|
};
|
|
|
|
foreach (var configKey in dangerousConfigs)
|
|
{
|
|
if (_configuration.GetValue<bool>(configKey, false))
|
|
{
|
|
throw new InvalidOperationException($"🚨 CRITICAL: Dangerous test configuration enabled in production: {configKey}");
|
|
}
|
|
}
|
|
}
|
|
|
|
_logger.LogInformation("✅ Production safeguards validated");
|
|
}
|
|
|
|
private void ValidateEnvironmentConfiguration()
|
|
{
|
|
// Log current environment for verification
|
|
_logger.LogInformation("🌍 Environment: {Environment}", _environment.EnvironmentName);
|
|
|
|
// Validate database connection
|
|
var connectionString = _configuration.GetConnectionString("DefaultConnection");
|
|
if (string.IsNullOrEmpty(connectionString))
|
|
{
|
|
throw new InvalidOperationException("🚨 CRITICAL: Database connection string not configured");
|
|
}
|
|
|
|
// Check for development database in production
|
|
if (_environment.IsProduction() && connectionString.Contains("littleshop.db"))
|
|
{
|
|
_logger.LogWarning("⚠️ WARNING: Using SQLite database in production. Consider PostgreSQL/SQL Server for production.");
|
|
}
|
|
|
|
_logger.LogInformation("✅ Environment configuration validated");
|
|
}
|
|
} |