fix(configuration-database): resolve ConfigurationDatabase-002..007 — remove hardcoded sa creds, fail-fast no-arg DI, encrypt secret columns, resilient audit serialization

This commit is contained in:
Joseph Doherty
2026-05-16 21:11:24 -04:00
parent 8fc04d43c2
commit 0c82ffcbe6
17 changed files with 2029 additions and 40 deletions

View File

@@ -6,20 +6,50 @@ namespace ScadaLink.ConfigurationDatabase;
/// <summary>
/// Factory for creating DbContext instances at design time (used by dotnet ef tooling).
/// Reads connection string from Host's appsettings.Central.json.
/// Resolves the connection string from the Host's appsettings files, or — for environments
/// where those files are not present — from the
/// <c>SCADALINK_DESIGNTIME_CONNECTIONSTRING</c> environment variable.
/// </summary>
/// <remarks>
/// There is deliberately no hardcoded fallback connection string. A credential literal in
/// source is committed to version control, encourages copy-paste of <c>sa</c> /
/// <c>TrustServerCertificate=True</c> into real environments, and can silently point
/// <c>dotnet ef</c> tooling at an unintended database. If no connection string can be
/// resolved, this factory fails loudly with an actionable message.
/// </remarks>
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<ScadaLinkDbContext>
{
private const string EnvironmentVariableName = "SCADALINK_DESIGNTIME_CONNECTIONSTRING";
private const string ConfigurationKey = "ScadaLink:Database:ConfigurationDb";
public ScadaLinkDbContext CreateDbContext(string[] args)
{
var configuration = new ConfigurationBuilder()
.SetBasePath(Path.Combine(Directory.GetCurrentDirectory(), "..", "ScadaLink.Host"))
.AddJsonFile("appsettings.json", optional: true)
.AddJsonFile("appsettings.Central.json", optional: true)
.Build();
var configurationBuilder = new ConfigurationBuilder();
var connectionString = configuration["ScadaLink:Database:ConfigurationDb"]
?? "Server=localhost,1433;Database=ScadaLink_Config;User Id=sa;Password=YourPassword;TrustServerCertificate=True";
// The Host's appsettings files are an optional source — only wire them up when the
// Host directory actually exists, otherwise SetBasePath throws DirectoryNotFoundException
// (e.g. when this factory is exercised from a test runner with no sibling Host folder).
var hostDirectory = Path.Combine(Directory.GetCurrentDirectory(), "..", "ScadaLink.Host");
if (Directory.Exists(hostDirectory))
{
configurationBuilder
.SetBasePath(hostDirectory)
.AddJsonFile("appsettings.json", optional: true)
.AddJsonFile("appsettings.Central.json", optional: true);
}
var configuration = configurationBuilder.Build();
var connectionString = configuration[ConfigurationKey]
?? Environment.GetEnvironmentVariable(EnvironmentVariableName);
if (string.IsNullOrWhiteSpace(connectionString))
{
throw new InvalidOperationException(
"No design-time database connection string was found. Set the configuration " +
$"key '{ConfigurationKey}' in the Host's appsettings file, or set the " +
$"'{EnvironmentVariableName}' environment variable, before running dotnet ef tooling.");
}
var optionsBuilder = new DbContextOptionsBuilder<ScadaLinkDbContext>();
optionsBuilder.UseSqlServer(connectionString);