using Tiku.Application.Auth; using Tiku.Infrastructure.Commerce; using Tiku.Infrastructure.Storage; namespace Tiku.Api.Configuration; internal static class ExternalServiceOptionsExtensions { internal static IServiceCollection AddExternalServiceOptions( this IServiceCollection services, IConfiguration configuration, IHostEnvironment environment) { services.Configure( configuration.GetSection(ObjectStorageOptions.SectionName)); services.Configure( configuration.GetSection(AliyunOssOptions.SectionName)); services.PostConfigure(options => { options.DefaultProvider = configuration["STORAGE_DEFAULT_PROVIDER"] ?? options.DefaultProvider; options.DefaultBucket = configuration["STORAGE_DEFAULT_BUCKET"] ?? options.DefaultBucket; options.PublicBaseUrl = configuration["STORAGE_PUBLIC_BASE_URL"] ?? options.PublicBaseUrl; options.AllowedMimePrefixes = SplitLegacyList( configuration["STORAGE_ALLOWED_MIME_PREFIXES"], options.AllowedMimePrefixes); options.AllowedMimeTypes = SplitLegacyList( configuration["STORAGE_ALLOWED_MIME_TYPES"], options.AllowedMimeTypes); options.RequireTenantPrefix = bool.TryParse( configuration["STORAGE_REQUIRE_TENANT_PREFIX"], out var requireTenantPrefix) ? requireTenantPrefix : options.RequireTenantPrefix; options.MaxUploadBytes = long.TryParse( configuration["STORAGE_MAX_UPLOAD_BYTES"], out var maxUploadBytes) ? maxUploadBytes : options.MaxUploadBytes; }); services.PostConfigure(options => { options.Region = configuration["ALIYUN_OSS_REGION"] ?? options.Region; options.Endpoint = configuration["ALIYUN_OSS_ENDPOINT"] ?? options.Endpoint; options.AccessKeyId = configuration["ALIYUN_OSS_ACCESS_KEY_ID"] ?? options.AccessKeyId; options.AccessKeySecret = configuration["ALIYUN_OSS_ACCESS_KEY_SECRET"] ?? options.AccessKeySecret; options.SecurityToken = configuration["ALIYUN_OSS_STS_TOKEN"] ?? options.SecurityToken; options.UseInternalEndpoint = bool.TryParse( configuration["ALIYUN_OSS_INTERNAL"], out var useInternalEndpoint) ? useInternalEndpoint : options.UseInternalEndpoint; }); services.AddOptions() .Bind(configuration.GetSection(TenantSecretEncryptionOptions.SectionName)) .PostConfigure(options => { options.KeyId = configuration["TIKU_TENANT_SECRET_KEY_ID"] ?? options.KeyId; options.MasterKey = configuration["TIKU_TENANT_SECRET_MASTER_KEY"] ?? options.MasterKey; }) .Validate( TenantSecretEncryptionOptions.BeValid, "Tenant secret encryption requires a key ID and a base64-encoded 32-byte master key.") .Validate( options => !environment.IsProduction() || !TenantSecretEncryptionOptions.IsDevelopmentDefault(options), "Production tenant secret encryption cannot use the development master key.") .ValidateOnStart(); services.AddOptions() .Bind(configuration.GetSection(SmsSecurityOptions.SectionName)) .PostConfigure(options => { options.CodePepper = configuration["TIKU_SMS_CODE_PEPPER"] ?? options.CodePepper; }) .Validate( SmsSecurityOptions.BeValid, "SMS security requires a pepper of at least 32 characters, exactly five verification attempts, " + "and positive tenant, phone, IP, and device rate limits.") .ValidateOnStart(); return services; } private static string[] SplitLegacyList(string? value, string[] fallback) => string.IsNullOrWhiteSpace(value) ? fallback : value.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); }