diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml
index af752cb..fdae500 100644
--- a/.gitea/workflows/ci.yml
+++ b/.gitea/workflows/ci.yml
@@ -14,7 +14,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
- dotnet-version: '8.0.x'
+ dotnet-version: '10.0.x'
- name: Restore
run: dotnet restore InboxIntel.sln
- name: Build
@@ -45,7 +45,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
- dotnet-version: '8.0.x'
+ dotnet-version: '10.0.x'
- name: dotnet format (verify only)
run: dotnet format InboxIntel.sln --verify-no-changes
@@ -67,7 +67,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
- dotnet-version: '8.0.x'
+ dotnet-version: '10.0.x'
- name: Wait for Postgres
run: |
for i in $(seq 1 30); do
diff --git a/.gitea/workflows/security.yml b/.gitea/workflows/security.yml
index fce1e4f..c9a354e 100644
--- a/.gitea/workflows/security.yml
+++ b/.gitea/workflows/security.yml
@@ -31,7 +31,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
- dotnet-version: '8.0.x'
+ dotnet-version: '10.0.x'
- name: Restore
run: dotnet restore InboxIntel.sln
- name: .NET vulnerable packages (fail on any)
diff --git a/Directory.Build.props b/Directory.Build.props
index 626a974..bf8c979 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,6 +1,6 @@
- net8.0
+ net10.0
enable
enable
latest
diff --git a/src/InboxIntel.Api/Dockerfile b/src/InboxIntel.Api/Dockerfile
index 384d9e0..a366fc2 100644
--- a/src/InboxIntel.Api/Dockerfile
+++ b/src/InboxIntel.Api/Dockerfile
@@ -1,5 +1,5 @@
# Multi-stage build for the ASP.NET Core API.
-FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
+FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
# Copy solution + project files first for layer-cached restore.
@@ -13,7 +13,7 @@ RUN dotnet restore src/InboxIntel.Api/InboxIntel.Api.csproj
COPY src/ src/
RUN dotnet publish src/InboxIntel.Api/InboxIntel.Api.csproj -c Release -o /app/publish /p:UseAppHost=false
-FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
+FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
WORKDIR /app
COPY --from=build /app/publish .
diff --git a/src/InboxIntel.Api/InboxIntel.Api.csproj b/src/InboxIntel.Api/InboxIntel.Api.csproj
index cf8de7e..8b126b4 100644
--- a/src/InboxIntel.Api/InboxIntel.Api.csproj
+++ b/src/InboxIntel.Api/InboxIntel.Api.csproj
@@ -5,10 +5,10 @@
210c6d96-c7e4-4ee9-8982-8b91424979b8
-
-
+
+
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/src/InboxIntel.Api/Program.cs b/src/InboxIntel.Api/Program.cs
index bb50ae0..d1d58ce 100644
--- a/src/InboxIntel.Api/Program.cs
+++ b/src/InboxIntel.Api/Program.cs
@@ -38,8 +38,8 @@ var dp = builder.Services.AddDataProtection()
var dpCertPath = builder.Configuration["DataProtection:CertificatePath"];
if (!string.IsNullOrWhiteSpace(dpCertPath))
{
- dp.ProtectKeysWithCertificate(new System.Security.Cryptography.X509Certificates.X509Certificate2(
- dpCertPath, builder.Configuration["DataProtection:CertificatePassword"]));
+ dp.ProtectKeysWithCertificate(System.Security.Cryptography.X509Certificates.X509CertificateLoader
+ .LoadPkcs12FromFile(dpCertPath, builder.Configuration["DataProtection:CertificatePassword"]));
}
builder.Services.AddApplication();
@@ -201,15 +201,14 @@ var forwardedOptions = new ForwardedHeadersOptions
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost,
ForwardLimit = app.Configuration.GetValue("ForwardedHeaders:ForwardLimit") ?? 1
};
-forwardedOptions.KnownNetworks.Clear();
+forwardedOptions.KnownIPNetworks.Clear();
forwardedOptions.KnownProxies.Clear();
var trustedNetworks = app.Configuration.GetSection("ForwardedHeaders:KnownNetworks").Get()
?? new[] { "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8", "::1/128" };
foreach (var cidr in trustedNetworks)
{
- var parts = cidr.Split('/');
- if (parts.Length == 2 && System.Net.IPAddress.TryParse(parts[0], out var prefix) && int.TryParse(parts[1], out var len))
- forwardedOptions.KnownNetworks.Add(new Microsoft.AspNetCore.HttpOverrides.IPNetwork(prefix, len));
+ if (System.Net.IPNetwork.TryParse(cidr, out var network))
+ forwardedOptions.KnownIPNetworks.Add(network);
}
app.UseForwardedHeaders(forwardedOptions);
diff --git a/src/InboxIntel.Application/InboxIntel.Application.csproj b/src/InboxIntel.Application/InboxIntel.Application.csproj
index ed04e5a..0576473 100644
--- a/src/InboxIntel.Application/InboxIntel.Application.csproj
+++ b/src/InboxIntel.Application/InboxIntel.Application.csproj
@@ -6,13 +6,13 @@
-
+
-
+
-
-
+
+
diff --git a/src/InboxIntel.Domain/InboxIntel.Domain.csproj b/src/InboxIntel.Domain/InboxIntel.Domain.csproj
index 955204b..727f931 100644
--- a/src/InboxIntel.Domain/InboxIntel.Domain.csproj
+++ b/src/InboxIntel.Domain/InboxIntel.Domain.csproj
@@ -6,7 +6,7 @@
-
-
+
+
diff --git a/src/InboxIntel.Infrastructure/InboxIntel.Infrastructure.csproj b/src/InboxIntel.Infrastructure/InboxIntel.Infrastructure.csproj
index 402a50a..2a0a80a 100644
--- a/src/InboxIntel.Infrastructure/InboxIntel.Infrastructure.csproj
+++ b/src/InboxIntel.Infrastructure/InboxIntel.Infrastructure.csproj
@@ -4,18 +4,18 @@
InboxIntel.Infrastructure
-
-
-
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
-
-
-
+
+
+
+
@@ -24,9 +24,9 @@
-
-
-
+
+
+
diff --git a/tests/InboxIntel.IntegrationTests/AuditFixesTests.cs b/tests/InboxIntel.IntegrationTests/AuditFixesTests.cs
index 9cc9b6e..fe608a0 100644
--- a/tests/InboxIntel.IntegrationTests/AuditFixesTests.cs
+++ b/tests/InboxIntel.IntegrationTests/AuditFixesTests.cs
@@ -25,7 +25,7 @@ namespace InboxIntel.IntegrationTests;
/// endpoints (model validation, per-user rate limits) without a real Google login.
public class TestAuthHandler : AuthenticationHandler
{
- public const string Scheme = "Test";
+ public new const string Scheme = "Test";
// Stable across requests so per-user rate-limit partitions accumulate correctly.
public static readonly string Uid = Guid.NewGuid().ToString();
@@ -52,6 +52,9 @@ public class AuditTestAppFactory : WebApplicationFactory
builder.ConfigureHostConfiguration(cfg => cfg.AddInMemoryCollection(new Dictionary
{
["Database:AutoMigrate"] = "false",
+ // Npgsql 10 eagerly validates the connection string when the DbContext is
+ // resolved (8.x was lazy); these tests never connect, but the string must parse.
+ ["ConnectionStrings:Postgres"] = "Host=localhost;Database=test;Username=test;Password=test",
["GoogleOAuth:ClientId"] = "test-client-id",
["GoogleOAuth:ClientSecret"] = "test-client-secret",
// H-2: make the auth policy trip on the 3rd request within the window.
diff --git a/tests/InboxIntel.IntegrationTests/AuthEndpointsTests.cs b/tests/InboxIntel.IntegrationTests/AuthEndpointsTests.cs
index fcd59e2..bed6589 100644
--- a/tests/InboxIntel.IntegrationTests/AuthEndpointsTests.cs
+++ b/tests/InboxIntel.IntegrationTests/AuthEndpointsTests.cs
@@ -19,6 +19,9 @@ public class TestAppFactory : WebApplicationFactory
builder.ConfigureHostConfiguration(cfg => cfg.AddInMemoryCollection(new Dictionary
{
["Database:AutoMigrate"] = "false",
+ // Npgsql 10 eagerly validates the connection string when the DbContext is
+ // resolved (8.x was lazy); these tests never connect, but the string must parse.
+ ["ConnectionStrings:Postgres"] = "Host=localhost;Database=test;Username=test;Password=test",
// Dummy OAuth creds so the Google challenge produces a real 302 redirect
// (an empty ClientId can make the handler throw instead of redirecting).
["GoogleOAuth:ClientId"] = "test-client-id",
diff --git a/tests/InboxIntel.IntegrationTests/InboxIntel.IntegrationTests.csproj b/tests/InboxIntel.IntegrationTests/InboxIntel.IntegrationTests.csproj
index 28ff553..69f6157 100644
--- a/tests/InboxIntel.IntegrationTests/InboxIntel.IntegrationTests.csproj
+++ b/tests/InboxIntel.IntegrationTests/InboxIntel.IntegrationTests.csproj
@@ -7,8 +7,8 @@
-
-
+
+