Fix all 45 audited bugs from bug-fixes.md

Verified every finding against the code, then fixed correctness (redirects,
advancement reset, duplicate heats, high-jump attempt entry, placement
ranking with tie handling, delete-dependency 500s), security (secrets out of
config, config-driven admin seed, login lockout, role authorization with
school scoping, heat-time IDOR, forwarded headers, last-admin guards),
schema integrity (nullable+filtered ExistingStudentId, unique indexes for
rounds/heats/bar heights via SchemaIntegrityFixes migration), performance
(N+1 removal in high jump/reports/standings/dashboard, SQL-side student
paging), and hygiene (duplicate notifications, auto-dismiss scope, local
bootstrap-icons, orphaned files, test-data.sql tournament creation).

FluentValidation is now registered; AutoMapper bumped to 14.0.0 (advisory
fully patched only in licence-changed 15.1.1 — documented). 11 new tests;
63/63 passing. Credential rotation and deploy-time DB_CONNECTION_STRING are
required manual follow-ups, documented in bug-fixes.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-11 08:30:53 -04:00
parent 78c526ee35
commit 810f721e48
93 changed files with 3251 additions and 1606 deletions

View File

@@ -61,12 +61,14 @@ public class TournamentController : Controller
}
}
[Authorize(Roles = "Admin,Official")]
[HttpGet]
public IActionResult Create()
{
return View(new TournamentCreateDto());
}
[Authorize(Roles = "Admin,Official")]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(TournamentCreateDto dto)
@@ -89,6 +91,7 @@ public class TournamentController : Controller
}
}
[Authorize(Roles = "Admin,Official")]
[HttpGet]
public async Task<IActionResult> Edit(int id)
{
@@ -118,6 +121,7 @@ public class TournamentController : Controller
}
}
[Authorize(Roles = "Admin,Official")]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(TournamentUpdateDto dto)
@@ -144,6 +148,7 @@ public class TournamentController : Controller
}
}
[Authorize(Roles = "Admin,Official")]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Delete(int id)
@@ -165,6 +170,7 @@ public class TournamentController : Controller
return RedirectToAction(nameof(Index));
}
[Authorize(Roles = "Admin,Official")]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddEventLevel(TournamentEventLevelCreateDto dto)
@@ -186,6 +192,7 @@ public class TournamentController : Controller
return RedirectToAction(nameof(Details), new { id = dto.TournamentId });
}
[Authorize(Roles = "Admin,Official")]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RemoveEventLevel(int tournamentEventLevelId, int id)
@@ -207,6 +214,7 @@ public class TournamentController : Controller
return RedirectToAction(nameof(Details), new { id });
}
[Authorize(Roles = "Admin,Official")]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UpdateStatus(int id, TournamentStatus status)
@@ -228,6 +236,7 @@ public class TournamentController : Controller
return RedirectToAction(nameof(Details), new { id });
}
[Authorize(Roles = "Admin,Official")]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Archive(int id)
@@ -249,6 +258,7 @@ public class TournamentController : Controller
return RedirectToAction(nameof(Index));
}
[Authorize(Roles = "Admin,Official")]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Unarchive(int id)
@@ -270,6 +280,7 @@ public class TournamentController : Controller
return RedirectToAction(nameof(Index));
}
[Authorize(Roles = "Admin,Official")]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ToggleAgeWaiver(int tournamentEventLevelId, int id)