refactor(api): re-configure secure CORS in options

This commit is contained in:
Collin Barrett 2025-06-01 13:58:48 -05:00 committed by Collin Barrett
parent 8ee6ac17fd
commit a883477cb6
7 changed files with 66 additions and 28 deletions

View file

@ -0,0 +1,27 @@
namespace FilterLists.Directory.Api.Cors;
internal static class CorsConfiguration
{
internal static readonly Action<Microsoft.AspNetCore.Cors.Infrastructure.CorsOptions, IConfiguration> SetupAction =
(options, configuration) =>
{
var corsOptions = configuration.GetSection(CorsOptions.Cors).Get<CorsOptions>() ??
throw new InvalidOperationException("CORS options are not configured.");
options.AddDefaultPolicy(policy => policy
.SetIsOriginAllowed(origin =>
{
if (corsOptions.AllowedOrigins.Any(o => string.Equals(o, origin, StringComparison.OrdinalIgnoreCase)))
return true;
if (corsOptions.StagingOriginPatterns is not null &&
origin.StartsWith(corsOptions.StagingOriginPatterns.Start, StringComparison.OrdinalIgnoreCase) &&
origin.EndsWith(corsOptions.StagingOriginPatterns.End, StringComparison.OrdinalIgnoreCase))
return true;
return false;
})
.WithMethods("GET")
.AllowAnyHeader());
};
}

View file

@ -0,0 +1,17 @@
namespace FilterLists.Directory.Api.Cors;
internal record CorsOptions
{
public const string Cors = "Cors";
public string[] AllowedOrigins { get; init; } = [];
public StagingOriginPatternsSettings? StagingOriginPatterns { get; init; }
}
/// <summary>
/// Azure Static Web Apps staging environment origin patterns.
/// </summary>
internal record StagingOriginPatternsSettings
{
public required string Start { get; init; }
public required string End { get; init; }
}

View file

@ -1,21 +0,0 @@
using Microsoft.AspNetCore.Cors.Infrastructure;
namespace FilterLists.Directory.Api;
internal static class CorsConfiguration
{
private const string ProductionClientOrigin = "https://filterlists.com";
private const string StagingClientOriginStart = "https://nice-water-05873140f";
private const string StagingClientOriginEnd = ".eastus2.5.azurestaticapps.net";
private const string LocalClientOrigin = "http://localhost:3000";
internal static readonly Action<CorsOptions> SetupAction = options => options.AddDefaultPolicy(policy =>
{
policy.SetIsOriginAllowed(origin =>
origin is ProductionClientOrigin or LocalClientOrigin ||
(origin.StartsWith(StagingClientOriginStart, StringComparison.InvariantCulture) &&
origin.EndsWith(StagingClientOriginEnd, StringComparison.InvariantCulture)))
.WithMethods("GET")
.AllowAnyHeader();
});
}

View file

@ -1,4 +1,5 @@
using FilterLists.Directory.Api;
using FilterLists.Directory.Api.Cors;
using FilterLists.Directory.Api.OpenApi;
using FilterLists.Directory.Application;
@ -6,7 +7,7 @@
builder.WebHost.ConfigureKestrel(serverOptions => serverOptions.AddServerHeader = false);
builder.AddServiceDefaults();
builder.Services.AddCors(CorsConfiguration.SetupAction);
builder.Services.AddCors(options => CorsConfiguration.SetupAction(options, builder.Configuration));
builder.Services.AddProblemDetails();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(OpenApiGenConfiguration.SetupAction);
@ -16,8 +17,8 @@
app.UseExceptionHandler();
app.UseCors();
app.MapEndpoints();
app.MapDefaultEndpoints();
app.MapEndpoints();
app.UseSwagger(o => o.RouteTemplate = "{documentName}/openapi.json");
app.UseSwaggerUI(SwaggerUiConfiguration.SetupAction);

View file

@ -4,5 +4,10 @@
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"Cors": {
"AllowedOrigins": [
"http://localhost:3000"
]
}
}
}

View file

@ -5,5 +5,14 @@
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
"AllowedHosts": "*",
"Cors": {
"AllowedOrigins": [
"https://filterlists.com"
],
"StagingOriginPatterns": {
"Start": "https://nice-water-05873140f-",
"End": ".eastus2.5.azurestaticapps.net"
}
}
}

View file

@ -63,9 +63,9 @@ public static TBuilder ConfigureOpenTelemetry<TBuilder>(this TBuilder builder) w
.WithTracing(tracing =>
{
tracing.AddSource(builder.Environment.ApplicationName)
.AddAspNetCoreInstrumentation(tracing =>
.AddAspNetCoreInstrumentation(options =>
// Exclude health check requests from tracing
tracing.Filter = context =>
options.Filter = context =>
!context.Request.Path.StartsWithSegments(HealthEndpointPath)
&& !context.Request.Path.StartsWithSegments(AlivenessEndpointPath)
)