Implement code review fixes and improvements

- Move admin credentials from hardcoded values to configuration
- Add rate limiting (5/min) to login endpoint for brute force protection
- Extract CleanJsonResponse to shared JsonResponseHelper class
- Add DateHelpers.MonthsBetween utility and consolidate date calculations
- Update PdfReportService to use ScoreThresholds constants
- Remove 5 unused shared components (EmploymentTable, FlagsList, etc.)
- Clean up unused CSS from MainLayout.razor.css
- Create IPdfReportService interface for better testability
- Add authentication requirement to Hangfire dashboard in development
- Seal EducationVerifierService class

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-21 02:09:26 +01:00
parent 3a33119bea
commit 45ca5f6a05
19 changed files with 141 additions and 1445 deletions

View File

@@ -1,6 +1,8 @@
using System.Threading.RateLimiting;
using Hangfire;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.EntityFrameworkCore;
using Serilog;
using TrueCV.Infrastructure;
@@ -35,7 +37,7 @@ try
builder.Services.AddInfrastructure(builder.Configuration);
// Add Web services
builder.Services.AddScoped<PdfReportService>();
builder.Services.AddScoped<IPdfReportService, PdfReportService>();
// Add Identity with secure password requirements
builder.Services.AddIdentity<ApplicationUser, IdentityRole<Guid>>(options =>
@@ -69,25 +71,52 @@ try
builder.Services.AddHealthChecks()
.AddDbContextCheck<ApplicationDbContext>("database");
// Add rate limiting for login endpoint
builder.Services.AddRateLimiter(options =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
options.AddFixedWindowLimiter("login", limiterOptions =>
{
limiterOptions.PermitLimit = 5;
limiterOptions.Window = TimeSpan.FromMinutes(1);
limiterOptions.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
limiterOptions.QueueLimit = 0;
});
});
var app = builder.Build();
// Seed default admin user and clear company cache
using (var scope = app.Services.CreateScope())
{
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
var defaultEmail = "admin@truecv.local";
var defaultPassword = "TrueCV_Admin123!";
var defaultEmail = builder.Configuration["DefaultAdmin:Email"];
var defaultPassword = builder.Configuration["DefaultAdmin:Password"];
if (await userManager.FindByEmailAsync(defaultEmail) == null)
if (!string.IsNullOrEmpty(defaultEmail) && !string.IsNullOrEmpty(defaultPassword))
{
var adminUser = new ApplicationUser
if (await userManager.FindByEmailAsync(defaultEmail) == null)
{
UserName = defaultEmail,
Email = defaultEmail,
EmailConfirmed = true
};
await userManager.CreateAsync(adminUser, defaultPassword);
Log.Information("Created default admin user: {Email}", defaultEmail);
var adminUser = new ApplicationUser
{
UserName = defaultEmail,
Email = defaultEmail,
EmailConfirmed = true
};
var result = await userManager.CreateAsync(adminUser, defaultPassword);
if (result.Succeeded)
{
Log.Information("Created default admin user: {Email}", defaultEmail);
}
else
{
Log.Warning("Failed to create admin user: {Errors}", string.Join(", ", result.Errors.Select(e => e.Description)));
}
}
}
else
{
Log.Information("No default admin credentials configured - skipping admin user seeding");
}
// Clear company cache on startup to ensure fresh API lookups
@@ -127,13 +156,14 @@ try
app.UseAuthentication();
app.UseAuthorization();
app.UseRateLimiter();
// Add Hangfire Dashboard (only in development)
// Add Hangfire Dashboard (only in development, requires authentication)
if (app.Environment.IsDevelopment())
{
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
Authorization = [] // Allow anonymous access in development
Authorization = [new HangfireAuthorizationFilter()]
});
}
@@ -173,7 +203,7 @@ try
Log.Warning("Failed login attempt for {Email}", email);
return Results.Redirect("/account/login?error=Invalid+email+or+password.");
}
});
}).RequireRateLimiting("login");
// Logout endpoint
app.MapPost("/account/logout", async (SignInManager<ApplicationUser> signInManager) =>