Files
tiku-backend.net/Tiku.Api/Configuration/NetworkConfigurationExtensions.cs
xiong 2c4a0bad6c
Some checks failed
ci / release-gate (push) Has been cancelled
fix:修复部分数据库查询问题
2026-08-03 14:22:06 +08:00

119 lines
5.8 KiB
C#

using System.Net;
using Microsoft.AspNetCore.HttpOverrides;
using Tiku.Api.Background;
using Tiku.Api.Options;
using Tiku.Application.PlatformAdmin;
using Tiku.Application.Tenancy;
namespace Tiku.Api.Configuration;
internal static class NetworkConfigurationExtensions
{
internal static IServiceCollection AddNetworkConfiguration(
this IServiceCollection services,
IConfiguration configuration,
IHostEnvironment environment)
{
services.AddOptions<TenantResolutionOptions>()
.Bind(configuration.GetSection(TenantResolutionOptions.SectionName))
.Validate(
options => OptionsValidation.BeValidTenantResolutionOptions(options, configuration,
environment.IsProduction()),
"Production requires formal platform hosts, non-wildcard AllowedHosts, and trusted proxy addresses.")
.ValidateOnStart();
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor |
ForwardedHeaders.XForwardedHost |
ForwardedHeaders.XForwardedProto;
options.ForwardLimit = 1;
options.KnownProxies.Clear();
options.KnownIPNetworks.Clear();
var resolution = configuration
.GetSection(TenantResolutionOptions.SectionName)
.Get<TenantResolutionOptions>() ?? new TenantResolutionOptions();
foreach (var address in resolution.TrustedProxyAddresses)
if (IPAddress.TryParse(address, out var proxy))
options.KnownProxies.Add(proxy);
});
services.AddOptions<DomainLifecycleOptions>()
.Bind(configuration.GetSection("TenantDomains"))
.Validate(options => environment.IsDevelopment() || !options.EnableDevelopmentLocalhostBypass,
"The .localhost domain lifecycle bypass can only be enabled in Development.")
.Validate(options => options.PollSeconds is >= 1 and <= 3600 &&
options.MaxIdlePollSeconds is >= 1 and <= 3600 &&
options.MaxIdlePollSeconds >= options.PollSeconds,
"Tenant domain polling intervals are invalid.")
.ValidateOnStart();
if (environment.IsDevelopment()) services.AddHostedService<DevelopmentTenantDomainLifecycleHostedService>();
services.AddOptions<TenantProvisioningOptions>()
.Bind(configuration.GetSection(TenantProvisioningOptions.SectionName))
.Validate(options => !string.IsNullOrWhiteSpace(options.DefaultBaseOfferingCode) &&
options.DefaultTrialDays is >= 1 and <= 365 &&
options.OwnerActivationMinutes is >= 5 and <= 1440 &&
options.OwnerActivationUrlTemplate.Contains("{host}", StringComparison.Ordinal) &&
Uri.TryCreate(
options.OwnerActivationUrlTemplate.Replace("{host}", "tenant.example.com",
StringComparison.Ordinal),
UriKind.Absolute,
out var activationOrigin) &&
activationOrigin.Scheme == Uri.UriSchemeHttps &&
ValidateDevelopmentActivationTemplate(options, environment.IsDevelopment()),
"Tenant provisioning requires a default offering code, valid trial/activation durations, an HTTPS owner activation URL template, and permits an HTTP template only for Development .localhost sites.")
.ValidateOnStart();
if (environment.IsProduction()) services.AddHostedService<TenantProvisioningStartupValidator>();
services.AddOptions<CorsOptions>()
.Bind(configuration.GetSection(CorsOptions.SectionName))
.ValidateDataAnnotations()
.Validate(
OptionsValidation.BeValidCorsOptions,
"CORS origins must be absolute HTTP/HTTPS origins, and credentials require explicit origins.")
.ValidateOnStart();
var corsOptions = configuration
.GetSection(CorsOptions.SectionName)
.Get<CorsOptions>() ?? new CorsOptions();
services.AddCors(options =>
{
options.AddPolicy(CorsOptions.PolicyName, policy =>
{
var origins = corsOptions.AllowedOrigins
.Where(origin => !string.IsNullOrWhiteSpace(origin))
.Select(origin => origin.Trim().TrimEnd('/'))
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
if (origins.Length > 0) policy.WithOrigins(origins);
policy
.WithHeaders(corsOptions.AllowedHeaders)
.WithMethods(corsOptions.AllowedMethods);
if (corsOptions.AllowCredentials) policy.AllowCredentials();
});
});
return services;
}
private static bool ValidateDevelopmentActivationTemplate(
TenantProvisioningOptions options,
bool isDevelopment)
{
if (string.IsNullOrWhiteSpace(options.DevelopmentLocalhostOwnerActivationUrlTemplate)) return true;
return isDevelopment &&
options.DevelopmentLocalhostOwnerActivationUrlTemplate.Contains("{host}", StringComparison.Ordinal) &&
Uri.TryCreate(
options.DevelopmentLocalhostOwnerActivationUrlTemplate.Replace(
"{host}", "tenant.localhost", StringComparison.Ordinal),
UriKind.Absolute,
out var developmentOrigin) &&
(developmentOrigin.Scheme == Uri.UriSchemeHttp ||
developmentOrigin.Scheme == Uri.UriSchemeHttps);
}
}