diff --git a/src/dotnet/QuantEngine.Infrastructure/Data/DbConnectionFactory.cs b/src/dotnet/QuantEngine.Infrastructure/Data/DbConnectionFactory.cs index ad47d10..e280bf8 100644 --- a/src/dotnet/QuantEngine.Infrastructure/Data/DbConnectionFactory.cs +++ b/src/dotnet/QuantEngine.Infrastructure/Data/DbConnectionFactory.cs @@ -8,18 +8,28 @@ namespace QuantEngine.Infrastructure.Data IDbConnection CreateConnection(); } - public class DbConnectionFactory : IDbConnectionFactory + public class DbConnectionFactory : IDbConnectionFactory, IDisposable { - private readonly string _connectionString; + private readonly NpgsqlDataSource _dataSource; public DbConnectionFactory(string connectionString) { - _connectionString = connectionString; + _dataSource = NpgsqlDataSource.Create(connectionString); + } + + public DbConnectionFactory(NpgsqlDataSource dataSource) + { + _dataSource = dataSource; } public IDbConnection CreateConnection() { - return new NpgsqlConnection(_connectionString); + return _dataSource.CreateConnection(); + } + + public void Dispose() + { + _dataSource.Dispose(); } } } diff --git a/src/dotnet/QuantEngine.Web/Pages/Account/Login.cshtml b/src/dotnet/QuantEngine.Web/Pages/Account/Login.cshtml index 9b90276..f31e7ca 100644 --- a/src/dotnet/QuantEngine.Web/Pages/Account/Login.cshtml +++ b/src/dotnet/QuantEngine.Web/Pages/Account/Login.cshtml @@ -223,6 +223,7 @@ }
+ @Html.AntiForgeryToken()
(new DbConnectionFactory(connectionString)); +var dataSource = NpgsqlDataSource.Create(connectionString); +builder.Services.AddSingleton(dataSource); +builder.Services.AddSingleton(new DbConnectionFactory(dataSource)); builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -248,7 +250,7 @@ app.MapPost("/api/auth/login", async (JsonElement payload, HttpContext httpConte new Microsoft.AspNetCore.Http.CookieOptions { HttpOnly = true, - Secure = httpContext.Request.IsHttps, + Secure = true, SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax, Expires = devExpiresAt, Path = "/" @@ -303,8 +305,8 @@ app.MapPost("/api/auth/login", async (JsonElement payload, HttpContext httpConte new Microsoft.AspNetCore.Http.CookieOptions { HttpOnly = true, - Secure = httpContext.Request.IsHttps, // Only secure on HTTPS - SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax, // Lax for localhost + Secure = true, // Force Secure Cookie for SSL standard + SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax, Expires = expiresAt, Path = "/" } @@ -443,7 +445,6 @@ app.MapPost("/api/auth/admin/reset-password", async (HttpContext context, JsonEl }); }).DisableAntiforgery(); -// Operational Report serving API (WASM safe file loading substitute) app.MapGet("/api/operational-report", async (IWebHostEnvironment env) => { var path = Path.GetFullPath(Path.Combine(env.ContentRootPath, "..", "..", "..", "Temp", "operational_report.json")); @@ -452,8 +453,8 @@ app.MapGet("/api/operational-report", async (IWebHostEnvironment env) => return Results.NotFound(new { gate = "FAIL", error = "operational_report_missing" }); } var json = await File.ReadAllTextAsync(path); - using var doc = JsonDocument.Parse(json); - return Results.Ok(doc.RootElement); + // Directly return raw JSON string with correct Content-Type to bypass using-disposed JSON document serializer issue + return Results.Content(json, "application/json"); }); app.MapGet("/api/history/{domain}", async (string domain, int? limit, IPostgresqlHistorySnapshotReader reader) => @@ -505,7 +506,6 @@ app.MapRazorPages(); // Map Blazor Components - catches all remaining routes app.MapRazorComponents() .AddInteractiveWebAssemblyRenderMode() - .AddInteractiveServerRenderMode() .AddAdditionalAssemblies(typeof(QuantEngine.Web.Client._Imports).Assembly); app.Run();